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