public static CompletionSource Create(Net5CompatFileStreamStrategy strategy, PreAllocatedOverlapped?preallocatedOverlapped, int numBufferedBytesRead, ReadOnlyMemory <byte> memory) { // If the memory passed in is the strategy's internal buffer, we can use the base FileStreamCompletionSource, // which has a PreAllocatedOverlapped with the memory already pinned. Otherwise, we use the derived // MemoryFileStreamCompletionSource, which Retains the memory, which will result in less pinning in the case // where the underlying memory is backed by pre-pinned buffers. return(preallocatedOverlapped != null && MemoryMarshal.TryGetArray(memory, out ArraySegment <byte> buffer) && preallocatedOverlapped.IsUserObject(buffer.Array) // preallocatedOverlapped is allocated when BufferedStream|Net5CompatFileStreamStrategy allocates the buffer ? new CompletionSource(strategy, preallocatedOverlapped, numBufferedBytesRead, buffer.Array) : new MemoryFileStreamCompletionSource(strategy, numBufferedBytesRead, memory)); }
private long _result; // Using long since this needs to be used in Interlocked APIs // Using RunContinuationsAsynchronously for compat reasons (old API used Task.Factory.StartNew for continuations) internal CompletionSource(Net5CompatFileStreamStrategy strategy, PreAllocatedOverlapped?preallocatedOverlapped, int numBufferedBytes, byte[]?bytes) : base(TaskCreationOptions.RunContinuationsAsynchronously) { _numBufferedBytes = numBufferedBytes; _strategy = strategy; _result = TaskSourceCodes.NoResult; // The _preallocatedOverlapped is null if the internal buffer was never created, so we check for // a non-null bytes before using the stream's _preallocatedOverlapped _overlapped = bytes != null && strategy.CompareExchangeCurrentOverlappedOwner(this, null) == null ? strategy._fileHandle.ThreadPoolBinding !.AllocateNativeOverlapped(preallocatedOverlapped !) : // allocated when buffer was created, and buffer is non-null strategy._fileHandle.ThreadPoolBinding !.AllocateNativeOverlapped(s_ioCallback, this, bytes); Debug.Assert(_overlapped != null, "AllocateNativeOverlapped returned null"); }
// When doing IO asynchronously (i.e. _isAsync==true), this callback is // called by a free thread in the threadpool when the IO operation // completes. internal static void IOCallback(uint errorCode, uint numBytes, NativeOverlapped *pOverlapped) { // Extract the completion source from the overlapped. The state in the overlapped // will either be a FileStreamStrategy (in the case where the preallocated overlapped was used), // in which case the operation being completed is its _currentOverlappedOwner, or it'll // be directly the FileStreamCompletionSource that's completing (in the case where the preallocated // overlapped was already in use by another operation). object?state = ThreadPoolBoundHandle.GetNativeOverlappedState(pOverlapped); Debug.Assert(state is Net5CompatFileStreamStrategy || state is CompletionSource); CompletionSource completionSource = state switch { Net5CompatFileStreamStrategy strategy => strategy._currentOverlappedOwner !, // must be owned _ => (CompletionSource)state }; Debug.Assert(completionSource != null); Debug.Assert(completionSource._overlapped == pOverlapped, "Overlaps don't match"); // Handle reading from & writing to closed pipes. While I'm not sure // this is entirely necessary anymore, maybe it's possible for // an async read on a pipe to be issued and then the pipe is closed, // returning this error. This may very well be necessary. ulong packedResult; if (errorCode != 0 && errorCode != Interop.Errors.ERROR_BROKEN_PIPE && errorCode != Interop.Errors.ERROR_NO_DATA) { packedResult = ((ulong)TaskSourceCodes.ResultError | errorCode); } else { packedResult = ((ulong)TaskSourceCodes.ResultSuccess | numBytes); } // Stow the result so that other threads can observe it // And, if no other thread is registering cancellation, continue if (TaskSourceCodes.NoResult == Interlocked.Exchange(ref completionSource._result, (long)packedResult)) { // Successfully set the state, attempt to take back the callback if (Interlocked.Exchange(ref completionSource._result, TaskSourceCodes.CompletedCallback) != TaskSourceCodes.NoResult) { // Successfully got the callback, finish the callback completionSource.CompleteCallback(packedResult); } // else: Some other thread stole the result, so now it is responsible to finish the callback } // else: Some other thread is registering a cancellation, so it *must* finish the callback }
private MemoryHandle _handle; // mutable struct; do not make this readonly internal MemoryFileStreamCompletionSource(Net5CompatFileStreamStrategy strategy, int numBufferedBytes, ReadOnlyMemory <byte> memory) : base(strategy, null, numBufferedBytes, null) // this type handles the pinning, so null is passed for bytes { _handle = memory.Pin(); }