1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System.Diagnostics;
6 using System.Runtime.ExceptionServices;
7 using System.Runtime.InteropServices.WindowsRuntime;
8 using System.Runtime.InteropServices;
9 using System.Threading.Tasks;
10 using System.Threading;
11 using Windows.Foundation;
12 using Windows.Storage.Streams;
13 
14 namespace System.IO
15 {
16     #region class StreamOperationAsyncResult
17 
18     internal abstract partial class StreamOperationAsyncResult : IAsyncResult
19     {
20         private AsyncCallback _userCompletionCallback = null;
21         private Object _userAsyncStateInfo = null;
22 
23         private IAsyncInfo _asyncStreamOperation = null;
24 
25         private volatile bool _completed = false;
26         private volatile bool _callbackInvoked = false;
27         private volatile ManualResetEvent _waitHandle = null;
28 
29         private Int64 _bytesCompleted = 0;
30 
31         private ExceptionDispatchInfo _errorInfo = null;
32 
33         private readonly bool _processCompletedOperationInCallback;
34         private IAsyncInfo _completedOperation = null;
35 
36 
StreamOperationAsyncResult(IAsyncInfo asyncStreamOperation, AsyncCallback userCompletionCallback, Object userAsyncStateInfo, bool processCompletedOperationInCallback)37         protected internal StreamOperationAsyncResult(IAsyncInfo asyncStreamOperation,
38                                                       AsyncCallback userCompletionCallback, Object userAsyncStateInfo,
39                                                       bool processCompletedOperationInCallback)
40         {
41             if (asyncStreamOperation == null)
42                 throw new ArgumentNullException("asyncReadOperation");
43 
44             _userCompletionCallback = userCompletionCallback;
45             _userAsyncStateInfo = userAsyncStateInfo;
46 
47             _asyncStreamOperation = asyncStreamOperation;
48 
49             _completed = false;
50             _callbackInvoked = false;
51 
52             _bytesCompleted = 0;
53 
54             _errorInfo = null;
55 
56             _processCompletedOperationInCallback = processCompletedOperationInCallback;
57         }
58 
59 
60         public object AsyncState
61         {
62             get { return _userAsyncStateInfo; }
63         }
64 
65 
66         internal bool ProcessCompletedOperationInCallback
67         {
68             get { return _processCompletedOperationInCallback; }
69         }
70 
71 
72 #pragma warning disable 420  // "a reference to a volatile field will not be treated as volatile"
73 
74         public WaitHandle AsyncWaitHandle
75         {
76             get
77             {
78                 ManualResetEvent wh = _waitHandle;
79                 if (wh != null)
80                     return wh;
81 
82                 // What if someone calls this public property and decides to wait on it?
83                 // > Use 'completed' in the ctor - this way the handle wait will return as appropriate.
84                 wh = new ManualResetEvent(_completed);
85 
86                 ManualResetEvent otherHandle = Interlocked.CompareExchange(ref _waitHandle, wh, null);
87 
88                 // We lost the race. Dispose OUR handle and return OTHER handle:
89                 if (otherHandle != null)
90                 {
91                     wh.Dispose();
92                     return otherHandle;
93                 }
94 
95                 // We won the race. Return OUR new handle:
96                 return wh;
97             }
98         }
99 
100 #pragma warning restore 420  // "a reference to a volatile field will not be treated as volatile"
101 
102 
103         public bool CompletedSynchronously
104         {
105             get { return false; }
106         }
107 
108 
109         public bool IsCompleted
110         {
111             get { return _completed; }
112         }
113 
114 
Wait()115         internal void Wait()
116         {
117             if (_completed)
118                 return;
119 
120             WaitHandle wh = AsyncWaitHandle;
121 
122             while (_completed == false)
123                 wh.WaitOne();
124         }
125 
126 
127         internal Int64 BytesCompleted
128         {
129             get { return _bytesCompleted; }
130         }
131 
132 
133         internal bool HasError
134         {
135             get { return _errorInfo != null; }
136         }
137 
138 
ThrowCachedError()139         internal void ThrowCachedError()
140         {
141             if (_errorInfo == null)
142                 return;
143 
144             _errorInfo.Throw();
145         }
146 
147 
CancelStreamOperation()148         internal bool CancelStreamOperation()
149         {
150             if (_callbackInvoked)
151                 return false;
152 
153             if (_asyncStreamOperation != null)
154             {
155                 _asyncStreamOperation.Cancel();
156                 _asyncStreamOperation = null;
157             }
158 
159             return true;
160         }
161 
CloseStreamOperation()162         internal void CloseStreamOperation()
163         {
164             try
165             {
166                 if (_asyncStreamOperation != null)
167                     _asyncStreamOperation.Close();
168             }
169             catch { }
170             _asyncStreamOperation = null;
171         }
172 
173 
~StreamOperationAsyncResult()174         ~StreamOperationAsyncResult()
175         {
176             // This finalisation is not critical, but we can still make an effort to notify the underlying WinRT stream
177             // that we are not any longer interested in the results:
178             CancelStreamOperation();
179         }
180 
181 
ProcessConcreteCompletedOperation(IAsyncInfo completedOperation, out Int64 bytesCompleted)182         internal abstract void ProcessConcreteCompletedOperation(IAsyncInfo completedOperation, out Int64 bytesCompleted);
183 
184 
ProcessCompletedOperation_InvalidOperationThrowHelper(ExceptionDispatchInfo errInfo, String errMsg)185         private static void ProcessCompletedOperation_InvalidOperationThrowHelper(ExceptionDispatchInfo errInfo, String errMsg)
186         {
187             Exception errInfoSrc = (errInfo == null) ? null : errInfo.SourceException;
188 
189             if (errInfoSrc == null)
190                 throw new InvalidOperationException(errMsg);
191             else
192                 throw new InvalidOperationException(errMsg, errInfoSrc);
193         }
194 
195 
ProcessCompletedOperation()196         internal void ProcessCompletedOperation()
197         {
198             // The error handling is slightly tricky here:
199             // Before processing the IO results, we are verifying some basic assumptions and if they do not hold, we are
200             // throwing InvalidOperation. However, by the time this method is called, we might have already stored something
201             // into errorInfo, e.g. if an error occurred in StreamOperationCompletedCallback. If that is the case, then that
202             // previous exception might include some important info relevant for detecting the problem. So, we take that
203             // previous exception and attach it as the inner exception to the InvalidOperationException being thrown.
204             // In cases where we have a good understanding of the previously saved errorInfo, and we know for sure that it
205             // the immediate reason for the state validation to fail, we can avoid throwing InvalidOperation altogether
206             // and only rethrow the errorInfo.
207 
208             if (!_callbackInvoked)
209                 ProcessCompletedOperation_InvalidOperationThrowHelper(_errorInfo, SR.InvalidOperation_CannotCallThisMethodInCurrentState);
210 
211             if (!_processCompletedOperationInCallback && !_completed)
212                 ProcessCompletedOperation_InvalidOperationThrowHelper(_errorInfo, SR.InvalidOperation_CannotCallThisMethodInCurrentState);
213 
214             if (_completedOperation == null)
215             {
216                 ExceptionDispatchInfo errInfo = _errorInfo;
217                 Exception errInfoSrc = (errInfo == null) ? null : errInfo.SourceException;
218 
219                 // See if errorInfo is set because we observed completedOperation == null previously (being slow is Ok on error path):
220                 if (errInfoSrc != null && errInfoSrc is NullReferenceException
221                         && SR.NullReference_IOCompletionCallbackCannotProcessNullAsyncInfo.Equals(errInfoSrc.Message))
222                 {
223                     errInfo.Throw();
224                 }
225                 else
226                 {
227                     throw new InvalidOperationException(SR.InvalidOperation_CannotCallThisMethodInCurrentState);
228                 }
229             }
230 
231             if (_completedOperation.Id != _asyncStreamOperation.Id)
232                 ProcessCompletedOperation_InvalidOperationThrowHelper(_errorInfo, SR.InvalidOperation_UnexpectedAsyncOperationID);
233 
234             if (_completedOperation.Status == AsyncStatus.Error)
235             {
236                 _bytesCompleted = 0;
237                 ThrowWithIOExceptionDispatchInfo(_completedOperation.ErrorCode);
238             }
239 
240             ProcessConcreteCompletedOperation(_completedOperation, out _bytesCompleted);
241         }
242 
243 
StreamOperationCompletedCallback(IAsyncInfo completedOperation, AsyncStatus unusedCompletionStatus)244         internal void StreamOperationCompletedCallback(IAsyncInfo completedOperation, AsyncStatus unusedCompletionStatus)
245         {
246             try
247             {
248                 if (_callbackInvoked)
249                     throw new InvalidOperationException(SR.InvalidOperation_MultipleIOCompletionCallbackInvocation);
250 
251                 _callbackInvoked = true;
252 
253                 // This happens in rare stress cases in Console mode and the WinRT folks said they are unlikely to fix this in Dev11.
254                 // Moreover, this can happen if the underlying WinRT stream has a faulty user implementation.
255                 // If we did not do this check, we would either get the same exception without the explaining message when dereferencing
256                 // completedOperation later, or we will get an InvalidOperation when processing the Op. With the check, they will be
257                 // aggregated and the user will know what went wrong.
258                 if (completedOperation == null)
259                     throw new NullReferenceException(SR.NullReference_IOCompletionCallbackCannotProcessNullAsyncInfo);
260 
261                 _completedOperation = completedOperation;
262 
263                 // processCompletedOperationInCallback == false indicates that the stream is doing a blocking wait on the waitHandle of this IAsyncResult.
264                 // In that case calls on completedOperation may deadlock if completedOperation is not free threaded.
265                 // By setting processCompletedOperationInCallback to false the stream that created this IAsyncResult indicated that it
266                 // will call ProcessCompletedOperation after the waitHandle is signalled to fetch the results.
267 
268                 if (_processCompletedOperationInCallback)
269                     ProcessCompletedOperation();
270             }
271             catch (Exception ex)
272             {
273                 _bytesCompleted = 0;
274                 _errorInfo = ExceptionDispatchInfo.Capture(ex);
275             }
276             finally
277             {
278                 _completed = true;
279                 Interlocked.MemoryBarrier();
280                 // From this point on, AsyncWaitHandle would create a handle that is readily set,
281                 // so we do not need to check if it is being produced asynchronously.
282                 if (_waitHandle != null)
283                     _waitHandle.Set();
284             }
285 
286             if (_userCompletionCallback != null)
287                 _userCompletionCallback(this);
288         }
289     }  // class StreamOperationAsyncResult
290 
291     #endregion class StreamOperationAsyncResult
292 
293 
294     #region class StreamReadAsyncResult
295 
296     internal class StreamReadAsyncResult : StreamOperationAsyncResult
297     {
298         private IBuffer _userBuffer = null;
299 
StreamReadAsyncResult(IAsyncOperationWithProgress<IBuffer, UInt32> asyncStreamReadOperation, IBuffer buffer, AsyncCallback userCompletionCallback, Object userAsyncStateInfo, bool processCompletedOperationInCallback)300         internal StreamReadAsyncResult(IAsyncOperationWithProgress<IBuffer, UInt32> asyncStreamReadOperation, IBuffer buffer,
301                                        AsyncCallback userCompletionCallback, Object userAsyncStateInfo,
302                                        bool processCompletedOperationInCallback)
303 
304             : base(asyncStreamReadOperation, userCompletionCallback, userAsyncStateInfo, processCompletedOperationInCallback)
305         {
306             if (buffer == null)
307                 throw new ArgumentNullException(nameof(buffer));
308 
309             _userBuffer = buffer;
310             asyncStreamReadOperation.Completed = this.StreamOperationCompletedCallback;
311         }
312 
313 
ProcessConcreteCompletedOperation(IAsyncInfo completedOperation, out Int64 bytesCompleted)314         internal override void ProcessConcreteCompletedOperation(IAsyncInfo completedOperation, out Int64 bytesCompleted)
315         {
316             ProcessConcreteCompletedOperation((IAsyncOperationWithProgress<IBuffer, UInt32>)completedOperation, out bytesCompleted);
317         }
318 
319 
ProcessConcreteCompletedOperation(IAsyncOperationWithProgress<IBuffer, UInt32> completedOperation, out Int64 bytesCompleted)320         private void ProcessConcreteCompletedOperation(IAsyncOperationWithProgress<IBuffer, UInt32> completedOperation, out Int64 bytesCompleted)
321         {
322             IBuffer resultBuffer = completedOperation.GetResults();
323             Debug.Assert(resultBuffer != null);
324 
325             WinRtIOHelper.EnsureResultsInUserBuffer(_userBuffer, resultBuffer);
326             bytesCompleted = _userBuffer.Length;
327         }
328     }  // class StreamReadAsyncResult
329 
330     #endregion class StreamReadAsyncResult
331 
332 
333     #region class StreamWriteAsyncResult
334 
335     internal class StreamWriteAsyncResult : StreamOperationAsyncResult
336     {
StreamWriteAsyncResult(IAsyncOperationWithProgress<UInt32, UInt32> asyncStreamWriteOperation, AsyncCallback userCompletionCallback, Object userAsyncStateInfo, bool processCompletedOperationInCallback)337         internal StreamWriteAsyncResult(IAsyncOperationWithProgress<UInt32, UInt32> asyncStreamWriteOperation,
338                                         AsyncCallback userCompletionCallback, Object userAsyncStateInfo,
339                                         bool processCompletedOperationInCallback)
340 
341             : base(asyncStreamWriteOperation, userCompletionCallback, userAsyncStateInfo, processCompletedOperationInCallback)
342         {
343             asyncStreamWriteOperation.Completed = this.StreamOperationCompletedCallback;
344         }
345 
346 
ProcessConcreteCompletedOperation(IAsyncInfo completedOperation, out Int64 bytesCompleted)347         internal override void ProcessConcreteCompletedOperation(IAsyncInfo completedOperation, out Int64 bytesCompleted)
348         {
349             ProcessConcreteCompletedOperation((IAsyncOperationWithProgress<UInt32, UInt32>)completedOperation, out bytesCompleted);
350         }
351 
352 
ProcessConcreteCompletedOperation(IAsyncOperationWithProgress<UInt32, UInt32> completedOperation, out Int64 bytesCompleted)353         private void ProcessConcreteCompletedOperation(IAsyncOperationWithProgress<UInt32, UInt32> completedOperation, out Int64 bytesCompleted)
354         {
355             UInt32 bytesWritten = completedOperation.GetResults();
356             bytesCompleted = bytesWritten;
357         }
358     }  // class StreamWriteAsyncResult
359 
360     #endregion class StreamWriteAsyncResult
361 
362 
363     #region class StreamFlushAsyncResult
364 
365     internal class StreamFlushAsyncResult : StreamOperationAsyncResult
366     {
StreamFlushAsyncResult(IAsyncOperation<Boolean> asyncStreamFlushOperation, bool processCompletedOperationInCallback)367         internal StreamFlushAsyncResult(IAsyncOperation<Boolean> asyncStreamFlushOperation, bool processCompletedOperationInCallback)
368 
369             : base(asyncStreamFlushOperation, null, null, processCompletedOperationInCallback)
370         {
371             asyncStreamFlushOperation.Completed = this.StreamOperationCompletedCallback;
372         }
373 
374 
ProcessConcreteCompletedOperation(IAsyncInfo completedOperation, out Int64 bytesCompleted)375         internal override void ProcessConcreteCompletedOperation(IAsyncInfo completedOperation, out Int64 bytesCompleted)
376         {
377             ProcessConcreteCompletedOperation((IAsyncOperation<Boolean>)completedOperation, out bytesCompleted);
378         }
379 
380 
ProcessConcreteCompletedOperation(IAsyncOperation<Boolean> completedOperation, out Int64 bytesCompleted)381         private void ProcessConcreteCompletedOperation(IAsyncOperation<Boolean> completedOperation, out Int64 bytesCompleted)
382         {
383             Boolean success = completedOperation.GetResults();
384             bytesCompleted = (success ? 0 : -1);
385         }
386     }  // class StreamFlushAsyncResult
387     #endregion class StreamFlushAsyncResult
388 }  // namespace
389 
390 // StreamOperationAsyncResult.cs
391