[System]: WebConnection: improve chunked reads and async callbacks.
authorMartin Baulig <martin.baulig@xamarin.com>
Fri, 22 May 2015 03:11:14 +0000 (05:11 +0200)
committerMartin Baulig <martin.baulig@xamarin.com>
Wed, 14 Oct 2015 19:18:27 +0000 (15:18 -0400)
Make sure we always complete the async operations on error and
also send callbacks outside the lock.

(cherry picked from commit 9027f0ad28bf733ebb8ac9964c78c22853ca93c7)

mcs/class/System/System.Net/SimpleAsyncResult.cs
mcs/class/System/System.Net/WebAsyncData.cs [new file with mode: 0644]
mcs/class/System/System.Net/WebAsyncResult.cs
mcs/class/System/System.Net/WebConnection.cs
mcs/class/System/System.Net/WebConnectionStream.cs
mcs/class/System/System.dll.sources
mcs/class/System/mobile_System.dll.sources

index b18d2adf34a12d089e466a006cb031a7c5ccf7e4..ae562648cfd2879f99b5a07e13e4bb9a0e195884 100644 (file)
@@ -49,7 +49,7 @@ namespace System.Net
                Exception exc;
                object locker = new object ();
 
-               SimpleAsyncResult (SimpleAsyncCallback cb)
+               protected SimpleAsyncResult (SimpleAsyncCallback cb)
                {
                        this.cb = cb;
                }
diff --git a/mcs/class/System/System.Net/WebAsyncData.cs b/mcs/class/System/System.Net/WebAsyncData.cs
new file mode 100644 (file)
index 0000000..fc07e52
--- /dev/null
@@ -0,0 +1,68 @@
+//
+// WebAsyncData.cs
+//
+// Author:
+//       Martin Baulig <martin.baulig@xamarin.com>
+//
+// Copyright (c) 2015 Xamarin, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+using System.IO;
+using System.Threading;
+
+namespace System.Net
+{
+       class WebAsyncData
+       {
+               readonly HttpWebRequest request;
+               readonly Stream stream;
+               readonly byte[] buffer;
+               readonly int offset, size;
+
+               public WebAsyncData (HttpWebRequest request, Stream stream, byte[] buffer, int offset, int size)
+               {
+                       this.request = request;
+                       this.stream = stream;
+                       this.buffer = buffer;
+                       this.offset = offset;
+                       this.size = size;
+               }
+
+               public HttpWebRequest Request {
+                       get { return request; }
+               }
+
+               public Stream Stream {
+                       get { return stream; }
+               }
+
+               public byte[] Buffer {
+                       get { return buffer; }
+               }
+
+               public int Offset {
+                       get { return offset; }
+               }
+
+               public int Size {
+                       get { return size; }
+               }
+       }
+}
+
index 52fc1a5967e9dd79f557f620acce84d2259f30de..5ec8a96eb610fcd84be3c24d0f59cd50f05cd831 100644 (file)
@@ -65,6 +65,15 @@ namespace System.Net
                        this.size = size;
                }
 
+               internal WebAsyncResult (HttpWebRequest request, byte[] buffer, int offset, int size, SimpleAsyncCallback cb)
+                       : base (cb)
+               {
+                       this.AsyncObject = request;
+                       this.buffer = buffer;
+                       this.offset = offset;
+                       this.size = size;
+               }
+
                internal void Reset ()
                {
                        this.nbytes = 0;
index fcee594ebcf50331e59976fa1645c32d1ca9c602..7ce79ab7ca3849f88a9effdd6e1d5ae4815bf738 100644 (file)
@@ -909,91 +909,126 @@ namespace System.Net
                        return true;
                }
 
-
-               internal IAsyncResult BeginRead (HttpWebRequest request, byte [] buffer, int offset, int size, AsyncCallback cb, object state)
+               internal void ReadAsync (HttpWebRequest request, byte [] buffer, int offset, int size, WebAsyncResult result)
                {
+                       bool error = false;
                        Stream s = null;
                        lock (this) {
                                if (Data.request != request)
-                                       throw new ObjectDisposedException (typeof (NetworkStream).FullName);
-                               if (nstream == null)
-                                       return null;
+                                       error = true;
                                s = nstream;
                        }
 
-                       IAsyncResult result = null;
-                       if (!chunkedRead || (!chunkStream.DataAvailable && chunkStream.WantMore)) {
-                               try {
-                                       result = s.BeginRead (buffer, offset, size, cb, state);
-                                       cb = null;
-                               } catch (Exception) {
-                                       HandleError (WebExceptionStatus.ReceiveFailure, null, "chunked BeginRead");
-                                       throw;
-                               }
+                       if (error) {
+                               result.SetCompleted (true, new ObjectDisposedException (typeof(NetworkStream).FullName));
+                               result.DoCallback ();
+                               return;
+                       } else if (s == null) {
+                               result.SetCompleted (true);
+                               result.DoCallback ();
+                               return;
                        }
 
-                       if (chunkedRead) {
-                               WebAsyncResult wr = new WebAsyncResult (cb, state, buffer, offset, size);
-                               wr.InnerAsyncResult = result;
-                               if (result == null) {
-                                       // Will be completed from the data in ChunkStream
-                                       wr.SetCompleted (true, (Exception) null);
-                                       wr.DoCallback ();
-                               }
-                               return wr;
+                       var data = new WebAsyncData (request, s, buffer, offset, size);
+                       ReadAsync (data, result);
+               }
+
+               void ReadAsync (WebAsyncData data, WebAsyncResult result)
+               {
+                       bool running;
+                       try {
+                               running = ReadAsyncInner (data, result);
+                       } catch (Exception ex) {
+                               result.SetCompleted (true, ex);
+                               running = false;
                        }
+                       if (!running)
+                               result.DoCallback ();
+               }
 
-                       return result;
+               bool ReadAsyncInner (WebAsyncData data, WebAsyncResult result)
+               {
+                       if (chunkedRead && (chunkStream.DataAvailable || !chunkStream.WantMore)) {
+                               ReadAsyncInnerCB (data, result, null);
+                               return false;
+                       }
+
+                       result.InnerAsyncResult = data.Stream.BeginRead (data.Buffer, data.Offset, data.Size, inner => ReadAsyncInnerCB (data, result, inner), null);
+                       return result.InnerAsyncResult != null && !result.InnerAsyncResult.CompletedSynchronously;
                }
-               
-               internal int EndRead (HttpWebRequest request, IAsyncResult result)
+
+               bool ReadAsyncInnerCB (WebAsyncData data, WebAsyncResult result, IAsyncResult innerResult)
                {
+                       var synch = innerResult != null ? innerResult.CompletedSynchronously : false;
+
+                       bool error = false;
                        Stream s = null;
                        lock (this) {
-                               if (Data.request != request)
-                                       throw new ObjectDisposedException (typeof (NetworkStream).FullName);
-                               if (nstream == null)
-                                       throw new ObjectDisposedException (typeof (NetworkStream).FullName);
+                               if (Data.request != data.Request || nstream == null)
+                                       error = true;
                                s = nstream;
                        }
 
+                       if (error) {
+                               result.SetCompleted (synch, new ObjectDisposedException (typeof(NetworkStream).FullName));
+                               result.DoCallback ();
+                               return false;
+                       }
+
                        int nbytes = 0;
-                       bool done = false;
-                       WebAsyncResult wr = null;
-                       IAsyncResult nsAsync = ((WebAsyncResult) result).InnerAsyncResult;
-                       if (chunkedRead && (nsAsync is WebAsyncResult)) {
-                               wr = (WebAsyncResult) nsAsync;
-                               IAsyncResult inner = wr.InnerAsyncResult;
-                               if (inner != null && !(inner is WebAsyncResult)) {
-                                       nbytes = s.EndRead (inner);
-                                       done = nbytes == 0;
-                               }
-                       } else if (!(nsAsync is WebAsyncResult)) {
-                               nbytes = s.EndRead (nsAsync);
-                               wr = (WebAsyncResult) result;
-                               done = nbytes == 0;
+                       try {
+                               if (innerResult != null)
+                                       nbytes = s.EndRead (innerResult);
+                       } catch (Exception ex) {
+                               result.SetCompleted (synch, ex);
+                               result.DoCallback ();
+                               return false;
                        }
 
-                       if (chunkedRead) {
-                               try {
-                                       chunkStream.WriteAndReadBack (wr.Buffer, wr.Offset, wr.Size, ref nbytes);
-                                       if (!done && nbytes == 0 && chunkStream.WantMore)
-                                               nbytes = EnsureRead (wr.Buffer, wr.Offset, wr.Size);
-                               } catch (Exception e) {
-                                       if (e is WebException)
-                                               throw e;
+                       var done = nbytes == 0;
 
-                                       throw new WebException ("Invalid chunked data.", e,
-                                                               WebExceptionStatus.ServerProtocolViolation, null);
-                               }
+                       if (!chunkedRead) {
+                               result.SetCompleted (synch, nbytes);
+                               result.DoCallback ();
+                               return false;
+                       }
 
-                               if ((done || nbytes == 0) && chunkStream.ChunkLeft != 0) {
-                                       HandleError (WebExceptionStatus.ReceiveFailure, null, "chunked EndRead");
-                                       throw new WebException ("Read error", null, WebExceptionStatus.ReceiveFailure, null);
+                       try {
+                               chunkStream.WriteAndReadBack (data.Buffer, data.Offset, data.Size, ref nbytes);
+                       } catch (Exception e) {
+                               if (!(e is WebException))
+                                       e = new WebException ("Invalid chunked data.", e, WebExceptionStatus.ServerProtocolViolation, null);
+                               result.SetCompleted (synch, e);
+                               result.DoCallback ();
+                               return false;
+                       }
+
+                       try {
+                               if (!done && nbytes == 0 && chunkStream.WantMore)
+                                       return ReadAsyncInner (data, result);
+                       } catch (Exception e) {
+                               if (!(e is WebException))
+                                       e = new WebException ("Invalid chunked data.", e, WebExceptionStatus.ServerProtocolViolation, null);
+                               result.SetCompleted (synch, e);
+                               result.DoCallback ();
+                               return false;
+                       }
+
+                       if (done || nbytes == 0) {
+                               if (chunkStream.ChunkLeft != 0) {
+                                       result.SetCompleted (synch, new WebException ("Read error", null, WebExceptionStatus.ConnectionClosed, null));
+                                       result.DoCallback ();
+                                       return false;
+                               } else if (chunkStream.WantMore) {
+                                       result.SetCompleted (synch, new IOException ("Connection closed"));
+                                       result.DoCallback ();
+                                       return false;
                                }
                        }
 
-                       return (nbytes != 0) ? nbytes : -1;
+                       result.SetCompleted (synch, nbytes);
+                       result.DoCallback ();
+                       return false;
                }
 
                // To be called on chunkedRead when we can read no data from the ChunkStream yet
index 295ee6757dc9c6df48202cbe65feb22dc4fdf17d..6510de5a098a8c6a2026419b95d7b88b2bfe2061 100644 (file)
@@ -359,6 +359,7 @@ namespace System.Net
                        }
 
                        WebAsyncResult result = new WebAsyncResult (cb, state, buffer, offset, size);
+                       result.AsyncObject = request;
                        if (totalRead >= contentLength) {
                                result.SetCompleted (true, -1);
                                result.DoCallback ();
@@ -388,7 +389,7 @@ namespace System.Net
                                size = (int)(contentLength - totalRead);
 
                        if (!read_eof) {
-                               result.InnerAsyncResult = cnc.BeginRead (request, buffer, offset, size, cb, result);
+                               cnc.ReadAsync (request, buffer, offset, size, result);
                        } else {
                                result.SetCompleted (true, result.NBytes);
                                result.DoCallback ();
@@ -399,53 +400,35 @@ namespace System.Net
                public override int EndRead (IAsyncResult r)
                {
                        WebAsyncResult result = (WebAsyncResult) r;
-                       if (result.EndCalled) {
-                               int xx = result.NBytes;
-                               return (xx >= 0) ? xx : 0;
-                       }
+                       int nb = result.NBytes;
 
+                       if (result.EndCalled)
+                               return (nb >= 0) ? nb : 0;
                        result.EndCalled = true;
 
-                       if (!result.IsCompleted) {
-                               int nbytes = -1;
-                               try {
-                                       nbytes = cnc.EndRead (request, result);
-                               } catch (Exception exc) {
-                                       lock (locker) {
-                                               pendingReads--;
-                                               if (pendingReads == 0)
-                                                       pending.Set ();
-                                       }
-
-                                       nextReadCalled = true;
-                                       cnc.Close (true);
-                                       result.SetCompleted (false, exc);
-                                       result.DoCallback ();
-                                       throw;
-                               }
-
-                               if (nbytes < 0) {
-                                       nbytes = 0;
-                                       read_eof = true;
-                               }
-
-                               totalRead += nbytes;
-                               result.SetCompleted (false, nbytes + result.NBytes);
-                               result.DoCallback ();
-                               if (nbytes == 0)
-                                       contentLength = totalRead;
-                       }
-
                        lock (locker) {
                                pendingReads--;
                                if (pendingReads == 0)
                                        pending.Set ();
                        }
 
+                       if (result.GotException) {
+                               nextReadCalled = true;
+                               cnc.Close (true);
+                               throw result.Exception;
+                       }
+
+                       if (nb < 0) {
+                               read_eof = true;
+                       } else {
+                               totalRead += result.NBytes;
+                               if (nb == 0)
+                                       contentLength = totalRead;
+                       }
+
                        if (totalRead >= contentLength && !nextReadCalled)
                                ReadAll ();
 
-                       int nb = result.NBytes;
                        return (nb >= 0) ? nb : 0;
                }
 
index 9e55a99d2ccde4aef0b08e7ea0f243efa7abd5c7..a42b0c8fd51caadac0880ea4f4a9b7dc6b4a5420 100644 (file)
@@ -455,6 +455,7 @@ System.Net.Sockets/UdpReceiveResult.cs
 System/NetTcpStyleUriParser.cs
 System.Net/TransportContext.cs
 System.Net/TransportType.cs
+System.Net/WebAsyncData.cs
 System.Net/WebAsyncResult.cs
 System.Net/WebConnection.cs
 System.Net/WebConnectionData.cs
index 1e45f421a8a4d9180f2a8ea149669bc0ee351c9b..ecbf8cf7250f0654285c11e9b796b086e5b39a09 100644 (file)
@@ -228,6 +228,7 @@ System.Net/SimpleAsyncResult.cs
 System.Net/SocketAddress.cs
 System.Net/TransportContext.cs
 System.Net/TransportType.cs
+System.Net/WebAsyncData.cs
 System.Net/WebAsyncResult.cs
 System.Net/WebConnection.cs
 System.Net/WebConnectionData.cs