FileStreamCompletionSource.Win32.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  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. using System.Buffers;
  5. using System.Diagnostics;
  6. using System.Runtime.InteropServices;
  7. using System.Threading;
  8. using System.Threading.Tasks;
  9. namespace System.IO
  10. {
  11. public partial class FileStream : Stream
  12. {
  13. // This is an internal object extending TaskCompletionSource with fields
  14. // for all of the relevant data necessary to complete the IO operation.
  15. // This is used by IOCallback and all of the async methods.
  16. private unsafe class FileStreamCompletionSource : TaskCompletionSource<int>
  17. {
  18. private const long NoResult = 0;
  19. private const long ResultSuccess = (long)1 << 32;
  20. private const long ResultError = (long)2 << 32;
  21. private const long RegisteringCancellation = (long)4 << 32;
  22. private const long CompletedCallback = (long)8 << 32;
  23. private const ulong ResultMask = ((ulong)uint.MaxValue) << 32;
  24. private static Action<object> s_cancelCallback;
  25. private readonly FileStream _stream;
  26. private readonly int _numBufferedBytes;
  27. private CancellationTokenRegistration _cancellationRegistration;
  28. #if DEBUG
  29. private bool _cancellationHasBeenRegistered;
  30. #endif
  31. private NativeOverlapped* _overlapped; // Overlapped class responsible for operations in progress when an appdomain unload occurs
  32. private long _result; // Using long since this needs to be used in Interlocked APIs
  33. // Using RunContinuationsAsynchronously for compat reasons (old API used Task.Factory.StartNew for continuations)
  34. protected FileStreamCompletionSource(FileStream stream, int numBufferedBytes, byte[] bytes)
  35. : base(TaskCreationOptions.RunContinuationsAsynchronously)
  36. {
  37. _numBufferedBytes = numBufferedBytes;
  38. _stream = stream;
  39. _result = NoResult;
  40. // Create the native overlapped. We try to use the preallocated overlapped if possible: it's possible if the byte
  41. // buffer is null (there's nothing to pin) or the same one that's associated with the preallocated overlapped (and
  42. // thus is already pinned) and if no one else is currently using the preallocated overlapped. This is the fast-path
  43. // for cases where the user-provided buffer is smaller than the FileStream's buffer (such that the FileStream's
  44. // buffer is used) and where operations on the FileStream are not being performed concurrently.
  45. Debug.Assert((bytes == null || ReferenceEquals(bytes, _stream._buffer)));
  46. // The _preallocatedOverlapped is null if the internal buffer was never created, so we check for
  47. // a non-null bytes before using the stream's _preallocatedOverlapped
  48. _overlapped = bytes != null && _stream.CompareExchangeCurrentOverlappedOwner(this, null) == null ?
  49. _stream._fileHandle.ThreadPoolBinding.AllocateNativeOverlapped(_stream._preallocatedOverlapped) :
  50. _stream._fileHandle.ThreadPoolBinding.AllocateNativeOverlapped(s_ioCallback, this, bytes);
  51. Debug.Assert(_overlapped != null, "AllocateNativeOverlapped returned null");
  52. }
  53. internal NativeOverlapped* Overlapped
  54. {
  55. get { return _overlapped; }
  56. }
  57. public void SetCompletedSynchronously(int numBytes)
  58. {
  59. ReleaseNativeResource();
  60. TrySetResult(numBytes + _numBufferedBytes);
  61. }
  62. public void RegisterForCancellation(CancellationToken cancellationToken)
  63. {
  64. #if DEBUG
  65. Debug.Assert(cancellationToken.CanBeCanceled);
  66. Debug.Assert(!_cancellationHasBeenRegistered, "Cannot register for cancellation twice");
  67. _cancellationHasBeenRegistered = true;
  68. #endif
  69. // Quick check to make sure the IO hasn't completed
  70. if (_overlapped != null)
  71. {
  72. var cancelCallback = s_cancelCallback;
  73. if (cancelCallback == null) s_cancelCallback = cancelCallback = Cancel;
  74. // Register the cancellation only if the IO hasn't completed
  75. long packedResult = Interlocked.CompareExchange(ref _result, RegisteringCancellation, NoResult);
  76. if (packedResult == NoResult)
  77. {
  78. _cancellationRegistration = cancellationToken.UnsafeRegister(cancelCallback, this);
  79. // Switch the result, just in case IO completed while we were setting the registration
  80. packedResult = Interlocked.Exchange(ref _result, NoResult);
  81. }
  82. else if (packedResult != CompletedCallback)
  83. {
  84. // Failed to set the result, IO is in the process of completing
  85. // Attempt to take the packed result
  86. packedResult = Interlocked.Exchange(ref _result, NoResult);
  87. }
  88. // If we have a callback that needs to be completed
  89. if ((packedResult != NoResult) && (packedResult != CompletedCallback) && (packedResult != RegisteringCancellation))
  90. {
  91. CompleteCallback((ulong)packedResult);
  92. }
  93. }
  94. }
  95. internal virtual void ReleaseNativeResource()
  96. {
  97. // Ensure that cancellation has been completed and cleaned up.
  98. _cancellationRegistration.Dispose();
  99. // Free the overlapped.
  100. // NOTE: The cancellation must *NOT* be running at this point, or it may observe freed memory
  101. // (this is why we disposed the registration above).
  102. if (_overlapped != null)
  103. {
  104. _stream._fileHandle.ThreadPoolBinding.FreeNativeOverlapped(_overlapped);
  105. _overlapped = null;
  106. }
  107. // Ensure we're no longer set as the current completion source (we may not have been to begin with).
  108. // Only one operation at a time is eligible to use the preallocated overlapped,
  109. _stream.CompareExchangeCurrentOverlappedOwner(null, this);
  110. }
  111. // When doing IO asynchronously (i.e. _isAsync==true), this callback is
  112. // called by a free thread in the threadpool when the IO operation
  113. // completes.
  114. internal static unsafe void IOCallback(uint errorCode, uint numBytes, NativeOverlapped* pOverlapped)
  115. {
  116. // Extract the completion source from the overlapped. The state in the overlapped
  117. // will either be a Win32FileStream (in the case where the preallocated overlapped was used),
  118. // in which case the operation being completed is its _currentOverlappedOwner, or it'll
  119. // be directly the FileStreamCompletion that's completing (in the case where the preallocated
  120. // overlapped was already in use by another operation).
  121. object state = ThreadPoolBoundHandle.GetNativeOverlappedState(pOverlapped);
  122. FileStream fs = state as FileStream;
  123. FileStreamCompletionSource completionSource = fs != null ?
  124. fs._currentOverlappedOwner :
  125. (FileStreamCompletionSource)state;
  126. Debug.Assert(completionSource._overlapped == pOverlapped, "Overlaps don't match");
  127. // Handle reading from & writing to closed pipes. While I'm not sure
  128. // this is entirely necessary anymore, maybe it's possible for
  129. // an async read on a pipe to be issued and then the pipe is closed,
  130. // returning this error. This may very well be necessary.
  131. ulong packedResult;
  132. if (errorCode != 0 && errorCode != ERROR_BROKEN_PIPE && errorCode != ERROR_NO_DATA)
  133. {
  134. packedResult = ((ulong)ResultError | errorCode);
  135. }
  136. else
  137. {
  138. packedResult = ((ulong)ResultSuccess | numBytes);
  139. }
  140. // Stow the result so that other threads can observe it
  141. // And, if no other thread is registering cancellation, continue
  142. if (NoResult == Interlocked.Exchange(ref completionSource._result, (long)packedResult))
  143. {
  144. // Successfully set the state, attempt to take back the callback
  145. if (Interlocked.Exchange(ref completionSource._result, CompletedCallback) != NoResult)
  146. {
  147. // Successfully got the callback, finish the callback
  148. completionSource.CompleteCallback(packedResult);
  149. }
  150. // else: Some other thread stole the result, so now it is responsible to finish the callback
  151. }
  152. // else: Some other thread is registering a cancellation, so it *must* finish the callback
  153. }
  154. private void CompleteCallback(ulong packedResult)
  155. {
  156. // Free up the native resource and cancellation registration
  157. CancellationToken cancellationToken = _cancellationRegistration.Token; // access before disposing registration
  158. ReleaseNativeResource();
  159. // Unpack the result and send it to the user
  160. long result = (long)(packedResult & ResultMask);
  161. if (result == ResultError)
  162. {
  163. int errorCode = unchecked((int)(packedResult & uint.MaxValue));
  164. if (errorCode == Interop.Errors.ERROR_OPERATION_ABORTED)
  165. {
  166. TrySetCanceled(cancellationToken.IsCancellationRequested ? cancellationToken : new CancellationToken(true));
  167. }
  168. else
  169. {
  170. TrySetException(Win32Marshal.GetExceptionForWin32Error(errorCode));
  171. }
  172. }
  173. else
  174. {
  175. Debug.Assert(result == ResultSuccess, "Unknown result");
  176. TrySetResult((int)(packedResult & uint.MaxValue) + _numBufferedBytes);
  177. }
  178. }
  179. private static void Cancel(object state)
  180. {
  181. // WARNING: This may potentially be called under a lock (during cancellation registration)
  182. FileStreamCompletionSource completionSource = state as FileStreamCompletionSource;
  183. Debug.Assert(completionSource != null, "Unknown state passed to cancellation");
  184. Debug.Assert(completionSource._overlapped != null && !completionSource.Task.IsCompleted, "IO should not have completed yet");
  185. // If the handle is still valid, attempt to cancel the IO
  186. if (!completionSource._stream._fileHandle.IsInvalid &&
  187. !Interop.Kernel32.CancelIoEx(completionSource._stream._fileHandle, completionSource._overlapped))
  188. {
  189. int errorCode = Marshal.GetLastWin32Error();
  190. // ERROR_NOT_FOUND is returned if CancelIoEx cannot find the request to cancel.
  191. // This probably means that the IO operation has completed.
  192. if (errorCode != Interop.Errors.ERROR_NOT_FOUND)
  193. {
  194. throw Win32Marshal.GetExceptionForWin32Error(errorCode);
  195. }
  196. }
  197. }
  198. public static FileStreamCompletionSource Create(FileStream stream, int numBufferedBytesRead, ReadOnlyMemory<byte> memory)
  199. {
  200. // If the memory passed in is the stream's internal buffer, we can use the base FileStreamCompletionSource,
  201. // which has a PreAllocatedOverlapped with the memory already pinned. Otherwise, we use the derived
  202. // MemoryFileStreamCompletionSource, which Retains the memory, which will result in less pinning in the case
  203. // where the underlying memory is backed by pre-pinned buffers.
  204. return MemoryMarshal.TryGetArray(memory, out ArraySegment<byte> buffer) && ReferenceEquals(buffer.Array, stream._buffer) ?
  205. new FileStreamCompletionSource(stream, numBufferedBytesRead, buffer.Array) :
  206. new MemoryFileStreamCompletionSource(stream, numBufferedBytesRead, memory);
  207. }
  208. }
  209. /// <summary>
  210. /// Extends <see cref="FileStreamCompletionSource"/> with to support disposing of a
  211. /// <see cref="MemoryHandle"/> when the operation has completed. This should only be used
  212. /// when memory doesn't wrap a byte[].
  213. /// </summary>
  214. private sealed class MemoryFileStreamCompletionSource : FileStreamCompletionSource
  215. {
  216. private MemoryHandle _handle; // mutable struct; do not make this readonly
  217. internal MemoryFileStreamCompletionSource(FileStream stream, int numBufferedBytes, ReadOnlyMemory<byte> memory) :
  218. base(stream, numBufferedBytes, bytes: null) // this type handles the pinning, so null is passed for bytes
  219. {
  220. _handle = memory.Pin();
  221. }
  222. internal override void ReleaseNativeResource()
  223. {
  224. _handle.Dispose();
  225. base.ReleaseNativeResource();
  226. }
  227. }
  228. }
  229. }