FileStreamCompletionSource.Win32.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  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!) : // allocated when buffer was created, and buffer is non-null
  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 FileStream (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 FileStreamCompletionSource 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. Debug.Assert(state is FileStream || state is FileStreamCompletionSource);
  123. FileStreamCompletionSource completionSource = state is FileStream fs ?
  124. fs._currentOverlappedOwner! : // must be owned
  125. (FileStreamCompletionSource)state!;
  126. Debug.Assert(completionSource != null);
  127. Debug.Assert(completionSource._overlapped == pOverlapped, "Overlaps don't match");
  128. // Handle reading from & writing to closed pipes. While I'm not sure
  129. // this is entirely necessary anymore, maybe it's possible for
  130. // an async read on a pipe to be issued and then the pipe is closed,
  131. // returning this error. This may very well be necessary.
  132. ulong packedResult;
  133. if (errorCode != 0 && errorCode != ERROR_BROKEN_PIPE && errorCode != ERROR_NO_DATA)
  134. {
  135. packedResult = ((ulong)ResultError | errorCode);
  136. }
  137. else
  138. {
  139. packedResult = ((ulong)ResultSuccess | numBytes);
  140. }
  141. // Stow the result so that other threads can observe it
  142. // And, if no other thread is registering cancellation, continue
  143. if (NoResult == Interlocked.Exchange(ref completionSource._result, (long)packedResult))
  144. {
  145. // Successfully set the state, attempt to take back the callback
  146. if (Interlocked.Exchange(ref completionSource._result, CompletedCallback) != NoResult)
  147. {
  148. // Successfully got the callback, finish the callback
  149. completionSource.CompleteCallback(packedResult);
  150. }
  151. // else: Some other thread stole the result, so now it is responsible to finish the callback
  152. }
  153. // else: Some other thread is registering a cancellation, so it *must* finish the callback
  154. }
  155. private void CompleteCallback(ulong packedResult)
  156. {
  157. // Free up the native resource and cancellation registration
  158. CancellationToken cancellationToken = _cancellationRegistration.Token; // access before disposing registration
  159. ReleaseNativeResource();
  160. // Unpack the result and send it to the user
  161. long result = (long)(packedResult & ResultMask);
  162. if (result == ResultError)
  163. {
  164. int errorCode = unchecked((int)(packedResult & uint.MaxValue));
  165. if (errorCode == Interop.Errors.ERROR_OPERATION_ABORTED)
  166. {
  167. TrySetCanceled(cancellationToken.IsCancellationRequested ? cancellationToken : new CancellationToken(true));
  168. }
  169. else
  170. {
  171. TrySetException(Win32Marshal.GetExceptionForWin32Error(errorCode));
  172. }
  173. }
  174. else
  175. {
  176. Debug.Assert(result == ResultSuccess, "Unknown result");
  177. TrySetResult((int)(packedResult & uint.MaxValue) + _numBufferedBytes);
  178. }
  179. }
  180. private static void Cancel(object? state)
  181. {
  182. // WARNING: This may potentially be called under a lock (during cancellation registration)
  183. Debug.Assert(state is FileStreamCompletionSource, "Unknown state passed to cancellation");
  184. FileStreamCompletionSource completionSource = (FileStreamCompletionSource)state;
  185. Debug.Assert(completionSource._overlapped != null && !completionSource.Task.IsCompleted, "IO should not have completed yet");
  186. // If the handle is still valid, attempt to cancel the IO
  187. if (!completionSource._stream._fileHandle.IsInvalid &&
  188. !Interop.Kernel32.CancelIoEx(completionSource._stream._fileHandle, completionSource._overlapped))
  189. {
  190. int errorCode = Marshal.GetLastWin32Error();
  191. // ERROR_NOT_FOUND is returned if CancelIoEx cannot find the request to cancel.
  192. // This probably means that the IO operation has completed.
  193. if (errorCode != Interop.Errors.ERROR_NOT_FOUND)
  194. {
  195. throw Win32Marshal.GetExceptionForWin32Error(errorCode);
  196. }
  197. }
  198. }
  199. public static FileStreamCompletionSource Create(FileStream stream, int numBufferedBytesRead, ReadOnlyMemory<byte> memory)
  200. {
  201. // If the memory passed in is the stream's internal buffer, we can use the base FileStreamCompletionSource,
  202. // which has a PreAllocatedOverlapped with the memory already pinned. Otherwise, we use the derived
  203. // MemoryFileStreamCompletionSource, which Retains the memory, which will result in less pinning in the case
  204. // where the underlying memory is backed by pre-pinned buffers.
  205. return MemoryMarshal.TryGetArray(memory, out ArraySegment<byte> buffer) && ReferenceEquals(buffer.Array, stream._buffer) ?
  206. new FileStreamCompletionSource(stream, numBufferedBytesRead, buffer.Array) :
  207. new MemoryFileStreamCompletionSource(stream, numBufferedBytesRead, memory);
  208. }
  209. }
  210. /// <summary>
  211. /// Extends <see cref="FileStreamCompletionSource"/> with to support disposing of a
  212. /// <see cref="MemoryHandle"/> when the operation has completed. This should only be used
  213. /// when memory doesn't wrap a byte[].
  214. /// </summary>
  215. private sealed class MemoryFileStreamCompletionSource : FileStreamCompletionSource
  216. {
  217. private MemoryHandle _handle; // mutable struct; do not make this readonly
  218. internal MemoryFileStreamCompletionSource(FileStream stream, int numBufferedBytes, ReadOnlyMemory<byte> memory) :
  219. base(stream, numBufferedBytes, bytes: null) // this type handles the pinning, so null is passed for bytes
  220. {
  221. _handle = memory.Pin();
  222. }
  223. internal override void ReleaseNativeResource()
  224. {
  225. _handle.Dispose();
  226. base.ReleaseNativeResource();
  227. }
  228. }
  229. }
  230. }