diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index c67c432163c73d..736e86c5bfa828 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -1100,10 +1100,10 @@ Common\Interop\Interop.ResultCode.cs - + Common\Interop\Interop.TimeZoneDisplayNameType.cs - + Common\Interop\Interop.TimeZoneInfo.cs @@ -1652,8 +1652,9 @@ + - + diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/AsyncWindowsFileStreamStrategy.ValueTaskSource.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/AsyncWindowsFileStreamStrategy.ValueTaskSource.cs new file mode 100644 index 00000000000000..35a9a2e51736b3 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/AsyncWindowsFileStreamStrategy.ValueTaskSource.cs @@ -0,0 +1,251 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Buffers; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks.Sources; +using TaskSourceCodes = System.IO.Strategies.FileStreamHelpers.TaskSourceCodes; + +namespace System.IO.Strategies +{ + internal sealed partial class AsyncWindowsFileStreamStrategy : WindowsFileStreamStrategy + { + /// + /// Type that helps reduce allocations for FileStream.ReadAsync and FileStream.WriteAsync. + /// + private unsafe class ValueTaskSource : IValueTaskSource, IValueTaskSource + { + internal static readonly IOCompletionCallback s_ioCallback = IOCallback; + + private readonly AsyncWindowsFileStreamStrategy _strategy; + + private ManualResetValueTaskSourceCore _source; // mutable struct; do not make this readonly + private NativeOverlapped* _overlapped; + private CancellationTokenRegistration _cancellationRegistration; + private long _result; // Using long since this needs to be used in Interlocked APIs +#if DEBUG + private bool _cancellationHasBeenRegistered; +#endif + + public static ValueTaskSource Create( + AsyncWindowsFileStreamStrategy strategy, + PreAllocatedOverlapped? preallocatedOverlapped, + ReadOnlyMemory memory) + { + // If the memory passed in is the strategy's internal buffer, we can use the base AwaitableProvider, + // which has a PreAllocatedOverlapped with the memory already pinned. Otherwise, we use the derived + // MemoryAwaitableProvider, 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 buffer) && + preallocatedOverlapped.IsUserObject(buffer.Array) ? + new ValueTaskSource(strategy, preallocatedOverlapped, buffer.Array) : + new MemoryValueTaskSource(strategy, memory); + } + + protected ValueTaskSource( + AsyncWindowsFileStreamStrategy strategy, + PreAllocatedOverlapped? preallocatedOverlapped, + byte[]? bytes) + { + _strategy = strategy; + _result = TaskSourceCodes.NoResult; + + _source = default; + _source.RunContinuationsAsynchronously = true; + + _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"); + } + + internal NativeOverlapped* Overlapped => _overlapped; + public ValueTaskSourceStatus GetStatus(short token) => _source.GetStatus(token); + public void OnCompleted(Action continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags) => _source.OnCompleted(continuation, state, token, flags); + void IValueTaskSource.GetResult(short token) => _source.GetResult(token); + int IValueTaskSource.GetResult(short token) => _source.GetResult(token); + internal short Version => _source.Version; + + internal void RegisterForCancellation(CancellationToken cancellationToken) + { +#if DEBUG + Debug.Assert(cancellationToken.CanBeCanceled); + Debug.Assert(!_cancellationHasBeenRegistered, "Cannot register for cancellation twice"); + _cancellationHasBeenRegistered = true; +#endif + + // Quick check to make sure the IO hasn't completed + if (_overlapped != null) + { + // Register the cancellation only if the IO hasn't completed + long packedResult = Interlocked.CompareExchange(ref _result, TaskSourceCodes.RegisteringCancellation, TaskSourceCodes.NoResult); + if (packedResult == TaskSourceCodes.NoResult) + { + _cancellationRegistration = cancellationToken.UnsafeRegister((s, token) => Cancel(token), this); + + // Switch the result, just in case IO completed while we were setting the registration + packedResult = Interlocked.Exchange(ref _result, TaskSourceCodes.NoResult); + } + else if (packedResult != TaskSourceCodes.CompletedCallback) + { + // Failed to set the result, IO is in the process of completing + // Attempt to take the packed result + packedResult = Interlocked.Exchange(ref _result, TaskSourceCodes.NoResult); + } + + // If we have a callback that needs to be completed + if ((packedResult != TaskSourceCodes.NoResult) && (packedResult != TaskSourceCodes.CompletedCallback) && (packedResult != TaskSourceCodes.RegisteringCancellation)) + { + CompleteCallback((ulong)packedResult); + } + } + } + + internal virtual void ReleaseNativeResource() + { + // Ensure that cancellation has been completed and cleaned up. + _cancellationRegistration.Dispose(); + + // Free the overlapped. + // NOTE: The cancellation must *NOT* be running at this point, or it may observe freed memory + // (this is why we disposed the registration above). + if (_overlapped != null) + { + _strategy._fileHandle.ThreadPoolBinding!.FreeNativeOverlapped(_overlapped); + _overlapped = null; + } + + // Ensure we're no longer set as the current AwaitableProvider (we may not have been to begin with). + // Only one operation at a time is eligible to use the preallocated overlapped + _strategy.CompareExchangeCurrentOverlappedOwner(null, this); + } + + private static void IOCallback(uint errorCode, uint numBytes, NativeOverlapped* pOverlapped) + { + // Extract the AwaitableProvider from the overlapped. The state in the overlapped + // will either be a AsyncWindowsFileStreamStrategy (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 AwaitableProvider 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 AsyncWindowsFileStreamStrategy or ValueTaskSource); + ValueTaskSource valueTaskSource = state switch + { + AsyncWindowsFileStreamStrategy strategy => strategy._currentOverlappedOwner!, // must be owned + _ => (ValueTaskSource)state + }; + Debug.Assert(valueTaskSource != null); + Debug.Assert(valueTaskSource._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 (Interlocked.Exchange(ref valueTaskSource._result, (long)packedResult) == TaskSourceCodes.NoResult) + { + // Successfully set the state, attempt to take back the callback + if (Interlocked.Exchange(ref valueTaskSource._result, TaskSourceCodes.CompletedCallback) != TaskSourceCodes.NoResult) + { + // Successfully got the callback, finish the callback + valueTaskSource.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 void CompleteCallback(ulong packedResult) + { + CancellationToken cancellationToken = _cancellationRegistration.Token; + + ReleaseNativeResource(); + + // Unpack the result and send it to the user + long result = (long)(packedResult & TaskSourceCodes.ResultMask); + if (result == TaskSourceCodes.ResultError) + { + int errorCode = unchecked((int)(packedResult & uint.MaxValue)); + Exception e; + if (errorCode == Interop.Errors.ERROR_OPERATION_ABORTED) + { + CancellationToken ct = cancellationToken.IsCancellationRequested ? cancellationToken : new CancellationToken(canceled: true); + e = new OperationCanceledException(ct); + } + else + { + e = Win32Marshal.GetExceptionForWin32Error(errorCode); + } + e.SetCurrentStackTrace(); + _source.SetException(e); + } + else + { + Debug.Assert(result == TaskSourceCodes.ResultSuccess, "Unknown result"); + _source.SetResult((int)(packedResult & uint.MaxValue)); + } + } + + private void Cancel(CancellationToken token) + { + // WARNING: This may potentially be called under a lock (during cancellation registration) + Debug.Assert(_overlapped != null && GetStatus(Version) != ValueTaskSourceStatus.Succeeded, "IO should not have completed yet"); + + // If the handle is still valid, attempt to cancel the IO + if (!_strategy._fileHandle.IsInvalid && + !Interop.Kernel32.CancelIoEx(_strategy._fileHandle, _overlapped)) + { + int errorCode = Marshal.GetLastWin32Error(); + + // ERROR_NOT_FOUND is returned if CancelIoEx cannot find the request to cancel. + // This probably means that the IO operation has completed. + if (errorCode != Interop.Errors.ERROR_NOT_FOUND) + { + Exception e = new OperationCanceledException(SR.OperationCanceled, Win32Marshal.GetExceptionForWin32Error(errorCode), token); + e.SetCurrentStackTrace(); + _source.SetException(e); + } + } + } + } + + /// + /// Extends with to support disposing of a + /// when the operation has completed. This should only be used + /// when memory doesn't wrap a byte[]. + /// + private sealed class MemoryValueTaskSource : ValueTaskSource + { + private MemoryHandle _handle; // mutable struct; do not make this readonly + + // this type handles the pinning, so bytes are null + internal unsafe MemoryValueTaskSource(AsyncWindowsFileStreamStrategy strategy, ReadOnlyMemory memory) + : base(strategy, null, null) // this type handles the pinning, so null is passed for bytes to the base + { + _handle = memory.Pin(); + } + + internal override void ReleaseNativeResource() + { + _handle.Dispose(); + base.ReleaseNativeResource(); + } + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/AsyncWindowsFileStreamStrategy.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/AsyncWindowsFileStreamStrategy.cs index b7f24351a0bda4..d8b52fb01e0427 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/AsyncWindowsFileStreamStrategy.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/AsyncWindowsFileStreamStrategy.cs @@ -8,10 +8,10 @@ namespace System.IO.Strategies { - internal sealed partial class AsyncWindowsFileStreamStrategy : WindowsFileStreamStrategy, IFileStreamCompletionSourceStrategy + internal sealed partial class AsyncWindowsFileStreamStrategy : WindowsFileStreamStrategy { - private PreAllocatedOverlapped? _preallocatedOverlapped; // optimization for async ops to avoid per-op allocations - private FileStreamCompletionSource? _currentOverlappedOwner; // async op currently using the preallocated overlapped + private PreAllocatedOverlapped? _preallocatedOverlapped; // optimization for async ops to avoid per-op allocations + private ValueTaskSource? _currentOverlappedOwner; // async op currently using the preallocated overlapped internal AsyncWindowsFileStreamStrategy(SafeFileHandle handle, FileAccess access, FileShare share) : base(handle, access, share) @@ -108,26 +108,27 @@ internal override void OnBufferAllocated(byte[] buffer) { Debug.Assert(_preallocatedOverlapped == null); - _preallocatedOverlapped = new PreAllocatedOverlapped(FileStreamCompletionSource.s_ioCallback, this, buffer); + _preallocatedOverlapped = new PreAllocatedOverlapped(ValueTaskSource.s_ioCallback, this, buffer); } - SafeFileHandle IFileStreamCompletionSourceStrategy.FileHandle => _fileHandle; - - FileStreamCompletionSource? IFileStreamCompletionSourceStrategy.CurrentOverlappedOwner => _currentOverlappedOwner; - - FileStreamCompletionSource? IFileStreamCompletionSourceStrategy.CompareExchangeCurrentOverlappedOwner(FileStreamCompletionSource? newSource, FileStreamCompletionSource? existingSource) + private ValueTaskSource? CompareExchangeCurrentOverlappedOwner(ValueTaskSource? newSource, ValueTaskSource? existingSource) => Interlocked.CompareExchange(ref _currentOverlappedOwner, newSource, existingSource); public override int Read(byte[] buffer, int offset, int count) - => ReadAsyncInternal(new Memory(buffer, offset, count)).GetAwaiter().GetResult(); + { + ValueTask vt = ReadAsyncInternal(new Memory(buffer, offset, count), CancellationToken.None); + return vt.IsCompleted ? + vt.Result : + vt.AsTask().GetAwaiter().GetResult(); + } public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - => ReadAsyncInternal(new Memory(buffer, offset, count), cancellationToken); + => ReadAsyncInternal(new Memory(buffer, offset, count), cancellationToken).AsTask(); public override ValueTask ReadAsync(Memory destination, CancellationToken cancellationToken = default) - => new ValueTask(ReadAsyncInternal(destination, cancellationToken)); + => ReadAsyncInternal(destination, cancellationToken); - private unsafe Task ReadAsyncInternal(Memory destination, CancellationToken cancellationToken = default) + private unsafe ValueTask ReadAsyncInternal(Memory destination, CancellationToken cancellationToken = default) { if (!CanRead) { @@ -137,8 +138,8 @@ private unsafe Task ReadAsyncInternal(Memory destination, Cancellatio Debug.Assert(!_fileHandle.IsClosed, "!_handle.IsClosed"); // Create and store async stream class library specific data in the async result - FileStreamCompletionSource completionSource = FileStreamCompletionSource.Create(this, _preallocatedOverlapped, 0, destination); - NativeOverlapped* intOverlapped = completionSource.Overlapped; + ValueTaskSource valueTaskSource = ValueTaskSource.Create(this, _preallocatedOverlapped, destination); + NativeOverlapped* intOverlapped = valueTaskSource.Overlapped; // Calculate position in the file we should be at after the read is done long positionBefore = _filePosition; @@ -185,7 +186,7 @@ private unsafe Task ReadAsyncInternal(Memory destination, Cancellatio if (r == -1) { // For pipes, when they hit EOF, they will come here. - if (errorCode == ERROR_BROKEN_PIPE) + if (errorCode == Interop.Errors.ERROR_BROKEN_PIPE) { // Not an error, but EOF. AsyncFSCallback will NOT be // called. Call the user callback here. @@ -193,18 +194,19 @@ private unsafe Task ReadAsyncInternal(Memory destination, Cancellatio // We clear the overlapped status bit for this special case. // Failure to do so looks like we are freeing a pending overlapped later. intOverlapped->InternalLow = IntPtr.Zero; - completionSource.SetCompletedSynchronously(0); + valueTaskSource.ReleaseNativeResource(); + return new ValueTask(0); } - else if (errorCode != ERROR_IO_PENDING) + else if (errorCode != Interop.Errors.ERROR_IO_PENDING) { if (!_fileHandle.IsClosed && CanSeek) // Update Position - It could be anywhere. { _filePosition = positionBefore; } - completionSource.ReleaseNativeResource(); + valueTaskSource.ReleaseNativeResource(); - if (errorCode == ERROR_HANDLE_EOF) + if (errorCode == Interop.Errors.ERROR_HANDLE_EOF) { ThrowHelper.ThrowEndOfFileException(); } @@ -216,7 +218,7 @@ private unsafe Task ReadAsyncInternal(Memory destination, Cancellatio else if (cancellationToken.CanBeCanceled) // ERROR_IO_PENDING { // Only once the IO is pending do we register for cancellation - completionSource.RegisterForCancellation(cancellationToken); + valueTaskSource.RegisterForCancellation(cancellationToken); } } else @@ -230,7 +232,7 @@ private unsafe Task ReadAsyncInternal(Memory destination, Cancellatio // results. } - return completionSource.Task; + return new ValueTask(valueTaskSource, valueTaskSource.Version); } public override void Write(byte[] buffer, int offset, int count) @@ -242,12 +244,7 @@ public override Task WriteAsync(byte[] buffer, int offset, int count, Cancellati public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) => WriteAsyncInternal(buffer, cancellationToken); - public override Task FlushAsync(CancellationToken cancellationToken) => Task.CompletedTask; // no buffering = nothing to flush - - private ValueTask WriteAsyncInternal(ReadOnlyMemory source, CancellationToken cancellationToken) - => new ValueTask(WriteAsyncInternalCore(source, cancellationToken)); - - private unsafe Task WriteAsyncInternalCore(ReadOnlyMemory source, CancellationToken cancellationToken) + private unsafe ValueTask WriteAsyncInternal(ReadOnlyMemory source, CancellationToken cancellationToken) { if (!CanWrite) { @@ -257,8 +254,8 @@ private unsafe Task WriteAsyncInternalCore(ReadOnlyMemory source, Cancella Debug.Assert(!_fileHandle.IsClosed, "!_handle.IsClosed"); // Create and store async stream class library specific data in the async result - FileStreamCompletionSource completionSource = FileStreamCompletionSource.Create(this, _preallocatedOverlapped, 0, source); - NativeOverlapped* intOverlapped = completionSource.Overlapped; + ValueTaskSource valueTaskSource = ValueTaskSource.Create(this, _preallocatedOverlapped, source); + NativeOverlapped* intOverlapped = valueTaskSource.Overlapped; long positionBefore = _filePosition; if (CanSeek) @@ -291,23 +288,23 @@ private unsafe Task WriteAsyncInternalCore(ReadOnlyMemory source, Cancella if (r == -1) { // For pipes, when they are closed on the other side, they will come here. - if (errorCode == ERROR_NO_DATA) + if (errorCode == Interop.Errors.ERROR_NO_DATA) { // Not an error, but EOF. AsyncFSCallback will NOT be called. // Completing TCS and return cached task allowing the GC to collect TCS. - completionSource.SetCompletedSynchronously(0); - return Task.CompletedTask; + valueTaskSource.ReleaseNativeResource(); + return ValueTask.CompletedTask; } - else if (errorCode != ERROR_IO_PENDING) + else if (errorCode != Interop.Errors.ERROR_IO_PENDING) { if (!_fileHandle.IsClosed && CanSeek) // Update Position - It could be anywhere. { _filePosition = positionBefore; } - completionSource.ReleaseNativeResource(); + valueTaskSource.ReleaseNativeResource(); - if (errorCode == ERROR_HANDLE_EOF) + if (errorCode == Interop.Errors.ERROR_HANDLE_EOF) { ThrowHelper.ThrowEndOfFileException(); } @@ -319,7 +316,7 @@ private unsafe Task WriteAsyncInternalCore(ReadOnlyMemory source, Cancella else if (cancellationToken.CanBeCanceled) // ERROR_IO_PENDING { // Only once the IO is pending do we register for cancellation - completionSource.RegisterForCancellation(cancellationToken); + valueTaskSource.RegisterForCancellation(cancellationToken); } } else @@ -333,9 +330,11 @@ private unsafe Task WriteAsyncInternalCore(ReadOnlyMemory source, Cancella // results. } - return completionSource.Task; + return new ValueTask(valueTaskSource, valueTaskSource.Version); } + public override Task FlushAsync(CancellationToken cancellationToken) => Task.CompletedTask; // no buffering = nothing to flush + public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) { ValidateCopyToArguments(destination, bufferSize); diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamCompletionSource.Win32.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamCompletionSource.Win32.cs deleted file mode 100644 index 679d62422a4aef..00000000000000 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamCompletionSource.Win32.cs +++ /dev/null @@ -1,267 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Buffers; -using System.Diagnostics; -using System.Runtime.InteropServices; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Win32.SafeHandles; - -namespace System.IO.Strategies -{ - // to avoid code duplicaiton of FileStreamCompletionSource for Net5CompatFileStreamStrategy and AsyncWindowsFileStreamStrategy - // we have created the following interface that is a common contract for both of them - internal interface IFileStreamCompletionSourceStrategy - { - SafeFileHandle FileHandle { get; } - - FileStreamCompletionSource? CurrentOverlappedOwner { get; } - - FileStreamCompletionSource? CompareExchangeCurrentOverlappedOwner(FileStreamCompletionSource? newSource, FileStreamCompletionSource? existingSource); - } - - // This is an internal object extending TaskCompletionSource with fields - // for all of the relevant data necessary to complete the IO operation. - // This is used by IOCallback and all of the async methods. - internal unsafe class FileStreamCompletionSource : TaskCompletionSource - { - private const long NoResult = 0; - private const long ResultSuccess = (long)1 << 32; - private const long ResultError = (long)2 << 32; - private const long RegisteringCancellation = (long)4 << 32; - private const long CompletedCallback = (long)8 << 32; - private const ulong ResultMask = ((ulong)uint.MaxValue) << 32; - private const int ERROR_BROKEN_PIPE = 109; - private const int ERROR_NO_DATA = 232; - - internal static readonly unsafe IOCompletionCallback s_ioCallback = IOCallback; - - private static Action? s_cancelCallback; - - private readonly IFileStreamCompletionSourceStrategy _strategy; - private readonly int _numBufferedBytes; - private CancellationTokenRegistration _cancellationRegistration; -#if DEBUG - private bool _cancellationHasBeenRegistered; -#endif - private NativeOverlapped* _overlapped; // Overlapped class responsible for operations in progress when an appdomain unload occurs - 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 FileStreamCompletionSource(IFileStreamCompletionSourceStrategy strategy, PreAllocatedOverlapped? preallocatedOverlapped, - int numBufferedBytes, byte[]? bytes) : base(TaskCreationOptions.RunContinuationsAsynchronously) - { - _numBufferedBytes = numBufferedBytes; - _strategy = strategy; - _result = 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"); - } - - internal NativeOverlapped* Overlapped => _overlapped; - - public void SetCompletedSynchronously(int numBytes) - { - ReleaseNativeResource(); - TrySetResult(numBytes + _numBufferedBytes); - } - - public void RegisterForCancellation(CancellationToken cancellationToken) - { -#if DEBUG - Debug.Assert(cancellationToken.CanBeCanceled); - Debug.Assert(!_cancellationHasBeenRegistered, "Cannot register for cancellation twice"); - _cancellationHasBeenRegistered = true; -#endif - - // Quick check to make sure the IO hasn't completed - if (_overlapped != null) - { - Action? cancelCallback = s_cancelCallback ??= Cancel; - - // Register the cancellation only if the IO hasn't completed - long packedResult = Interlocked.CompareExchange(ref _result, RegisteringCancellation, NoResult); - if (packedResult == NoResult) - { - _cancellationRegistration = cancellationToken.UnsafeRegister(cancelCallback, this); - - // Switch the result, just in case IO completed while we were setting the registration - packedResult = Interlocked.Exchange(ref _result, NoResult); - } - else if (packedResult != CompletedCallback) - { - // Failed to set the result, IO is in the process of completing - // Attempt to take the packed result - packedResult = Interlocked.Exchange(ref _result, NoResult); - } - - // If we have a callback that needs to be completed - if ((packedResult != NoResult) && (packedResult != CompletedCallback) && (packedResult != RegisteringCancellation)) - { - CompleteCallback((ulong)packedResult); - } - } - } - - internal virtual void ReleaseNativeResource() - { - // Ensure that cancellation has been completed and cleaned up. - _cancellationRegistration.Dispose(); - - // Free the overlapped. - // NOTE: The cancellation must *NOT* be running at this point, or it may observe freed memory - // (this is why we disposed the registration above). - if (_overlapped != null) - { - _strategy.FileHandle.ThreadPoolBinding!.FreeNativeOverlapped(_overlapped); - _overlapped = null; - } - - // Ensure we're no longer set as the current completion source (we may not have been to begin with). - // Only one operation at a time is eligible to use the preallocated overlapped, - _strategy.CompareExchangeCurrentOverlappedOwner(null, this); - } - - // 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 IFileStreamCompletionSourceStrategy || state is FileStreamCompletionSource); - FileStreamCompletionSource completionSource = state switch - { - IFileStreamCompletionSourceStrategy strategy => strategy.CurrentOverlappedOwner!, // must be owned - _ => (FileStreamCompletionSource)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 != ERROR_BROKEN_PIPE && errorCode != ERROR_NO_DATA) - { - packedResult = ((ulong)ResultError | errorCode); - } - else - { - packedResult = ((ulong)ResultSuccess | numBytes); - } - - // Stow the result so that other threads can observe it - // And, if no other thread is registering cancellation, continue - if (NoResult == Interlocked.Exchange(ref completionSource._result, (long)packedResult)) - { - // Successfully set the state, attempt to take back the callback - if (Interlocked.Exchange(ref completionSource._result, CompletedCallback) != 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 void CompleteCallback(ulong packedResult) - { - // Free up the native resource and cancellation registration - CancellationToken cancellationToken = _cancellationRegistration.Token; // access before disposing registration - ReleaseNativeResource(); - - // Unpack the result and send it to the user - long result = (long)(packedResult & ResultMask); - if (result == ResultError) - { - int errorCode = unchecked((int)(packedResult & uint.MaxValue)); - if (errorCode == Interop.Errors.ERROR_OPERATION_ABORTED) - { - TrySetCanceled(cancellationToken.IsCancellationRequested ? cancellationToken : new CancellationToken(true)); - } - else - { - Exception e = Win32Marshal.GetExceptionForWin32Error(errorCode); - e.SetCurrentStackTrace(); - TrySetException(e); - } - } - else - { - Debug.Assert(result == ResultSuccess, "Unknown result"); - TrySetResult((int)(packedResult & uint.MaxValue) + _numBufferedBytes); - } - } - - private static void Cancel(object? state) - { - // WARNING: This may potentially be called under a lock (during cancellation registration) - - Debug.Assert(state is FileStreamCompletionSource, "Unknown state passed to cancellation"); - FileStreamCompletionSource completionSource = (FileStreamCompletionSource)state; - Debug.Assert(completionSource._overlapped != null && !completionSource.Task.IsCompleted, "IO should not have completed yet"); - - // If the handle is still valid, attempt to cancel the IO - if (!completionSource._strategy.FileHandle.IsInvalid && - !Interop.Kernel32.CancelIoEx(completionSource._strategy.FileHandle, completionSource._overlapped)) - { - int errorCode = Marshal.GetLastWin32Error(); - - // ERROR_NOT_FOUND is returned if CancelIoEx cannot find the request to cancel. - // This probably means that the IO operation has completed. - if (errorCode != Interop.Errors.ERROR_NOT_FOUND) - { - throw Win32Marshal.GetExceptionForWin32Error(errorCode); - } - } - } - - public static FileStreamCompletionSource Create(IFileStreamCompletionSourceStrategy strategy, PreAllocatedOverlapped? preallocatedOverlapped, - int numBufferedBytesRead, ReadOnlyMemory 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 buffer) - && preallocatedOverlapped.IsUserObject(buffer.Array) // preallocatedOverlapped is allocated when BufferedStream|Net5CompatFileStreamStrategy allocates the buffer - ? new FileStreamCompletionSource(strategy, preallocatedOverlapped, numBufferedBytesRead, buffer.Array) - : new MemoryFileStreamCompletionSource(strategy, numBufferedBytesRead, memory); - } - } - - /// - /// Extends with to support disposing of a - /// when the operation has completed. This should only be used - /// when memory doesn't wrap a byte[]. - /// - internal sealed class MemoryFileStreamCompletionSource : FileStreamCompletionSource - { - private MemoryHandle _handle; // mutable struct; do not make this readonly - - internal MemoryFileStreamCompletionSource(IFileStreamCompletionSourceStrategy strategy, int numBufferedBytes, ReadOnlyMemory memory) - : base(strategy, null, numBufferedBytes, null) // this type handles the pinning, so null is passed for bytes - { - _handle = memory.Pin(); - } - - internal override void ReleaseNativeResource() - { - _handle.Dispose(); - base.ReleaseNativeResource(); - } - } -} diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Windows.cs index fd3218f6eb4c3e..b35b8d7b01cb78 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Windows.cs @@ -14,10 +14,18 @@ namespace System.IO.Strategies // this type defines a set of stateless FileStream/FileStreamStrategy helper methods internal static partial class FileStreamHelpers { - internal const int ERROR_BROKEN_PIPE = 109; - internal const int ERROR_NO_DATA = 232; - private const int ERROR_HANDLE_EOF = 38; - private const int ERROR_IO_PENDING = 997; + // Async completion/return codes shared by: + // - AsyncWindowsFileStreamStrategy.ValueTaskSource + // - Net5CompatFileStreamStrategy.CompletionSource + internal static class TaskSourceCodes + { + internal const long NoResult = 0; + internal const long ResultSuccess = (long)1 << 32; + internal const long ResultError = (long)2 << 32; + internal const long RegisteringCancellation = (long)4 << 32; + internal const long CompletedCallback = (long)8 << 32; + internal const ulong ResultMask = ((ulong)uint.MaxValue) << 32; + } private static FileStreamStrategy ChooseStrategyCore(SafeFileHandle handle, FileAccess access, FileShare share, int bufferSize, bool isAsync) { @@ -481,11 +489,11 @@ internal static async Task AsyncModeCopyToAsync(SafeFileHandle handle, string? p { switch (errorCode) { - case ERROR_IO_PENDING: + case Interop.Errors.ERROR_IO_PENDING: // Async operation in progress. break; - case ERROR_BROKEN_PIPE: - case ERROR_HANDLE_EOF: + case Interop.Errors.ERROR_BROKEN_PIPE: + case Interop.Errors.ERROR_HANDLE_EOF: // We're at or past the end of the file, and the overlapped callback // won't be raised in these cases. Mark it as completed so that the await // below will see it as such. @@ -503,8 +511,8 @@ internal static async Task AsyncModeCopyToAsync(SafeFileHandle handle, string? p { case 0: // success break; - case ERROR_BROKEN_PIPE: // logically success with 0 bytes read (write end of pipe closed) - case ERROR_HANDLE_EOF: // logically success with 0 bytes read (read at end of file) + case Interop.Errors.ERROR_BROKEN_PIPE: // logically success with 0 bytes read (write end of pipe closed) + case Interop.Errors.ERROR_HANDLE_EOF: // logically success with 0 bytes read (read at end of file) Debug.Assert(readAwaitable._numBytes == 0, $"Expected 0 bytes read, got {readAwaitable._numBytes}"); break; case Interop.Errors.ERROR_OPERATION_ABORTED: // canceled diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/Net5CompatFileStreamStrategy.CompletionSource.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/Net5CompatFileStreamStrategy.CompletionSource.Windows.cs new file mode 100644 index 00000000000000..de5af4778a198a --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/Net5CompatFileStreamStrategy.CompletionSource.Windows.cs @@ -0,0 +1,250 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Buffers; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using TaskSourceCodes = System.IO.Strategies.FileStreamHelpers.TaskSourceCodes; + +namespace System.IO.Strategies +{ + internal sealed partial class Net5CompatFileStreamStrategy : FileStreamStrategy + { + // This is an internal object extending TaskCompletionSource with fields + // for all of the relevant data necessary to complete the IO operation. + // This is used by IOCallback and all of the async methods. + private unsafe class CompletionSource : TaskCompletionSource + { + internal static readonly unsafe IOCompletionCallback s_ioCallback = IOCallback; + + private static Action? s_cancelCallback; + + private readonly Net5CompatFileStreamStrategy _strategy; + private readonly int _numBufferedBytes; + private CancellationTokenRegistration _cancellationRegistration; +#if DEBUG + private bool _cancellationHasBeenRegistered; +#endif + private NativeOverlapped* _overlapped; // Overlapped class responsible for operations in progress when an appdomain unload occurs + 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"); + } + + internal NativeOverlapped* Overlapped => _overlapped; + + public void SetCompletedSynchronously(int numBytes) + { + ReleaseNativeResource(); + TrySetResult(numBytes + _numBufferedBytes); + } + + public void RegisterForCancellation(CancellationToken cancellationToken) + { +#if DEBUG + Debug.Assert(cancellationToken.CanBeCanceled); + Debug.Assert(!_cancellationHasBeenRegistered, "Cannot register for cancellation twice"); + _cancellationHasBeenRegistered = true; +#endif + + // Quick check to make sure the IO hasn't completed + if (_overlapped != null) + { + Action? cancelCallback = s_cancelCallback ??= Cancel; + + // Register the cancellation only if the IO hasn't completed + long packedResult = Interlocked.CompareExchange(ref _result, TaskSourceCodes.RegisteringCancellation, TaskSourceCodes.NoResult); + if (packedResult == TaskSourceCodes.NoResult) + { + _cancellationRegistration = cancellationToken.UnsafeRegister(cancelCallback, this); + + // Switch the result, just in case IO completed while we were setting the registration + packedResult = Interlocked.Exchange(ref _result, TaskSourceCodes.NoResult); + } + else if (packedResult != TaskSourceCodes.CompletedCallback) + { + // Failed to set the result, IO is in the process of completing + // Attempt to take the packed result + packedResult = Interlocked.Exchange(ref _result, TaskSourceCodes.NoResult); + } + + // If we have a callback that needs to be completed + if ((packedResult != TaskSourceCodes.NoResult) && (packedResult != TaskSourceCodes.CompletedCallback) && (packedResult != TaskSourceCodes.RegisteringCancellation)) + { + CompleteCallback((ulong)packedResult); + } + } + } + + internal virtual void ReleaseNativeResource() + { + // Ensure that cancellation has been completed and cleaned up. + _cancellationRegistration.Dispose(); + + // Free the overlapped. + // NOTE: The cancellation must *NOT* be running at this point, or it may observe freed memory + // (this is why we disposed the registration above). + if (_overlapped != null) + { + _strategy._fileHandle.ThreadPoolBinding!.FreeNativeOverlapped(_overlapped); + _overlapped = null; + } + + // Ensure we're no longer set as the current completion source (we may not have been to begin with). + // Only one operation at a time is eligible to use the preallocated overlapped, + _strategy.CompareExchangeCurrentOverlappedOwner(null, this); + } + + // 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 void CompleteCallback(ulong packedResult) + { + // Free up the native resource and cancellation registration + CancellationToken cancellationToken = _cancellationRegistration.Token; // access before disposing registration + ReleaseNativeResource(); + + // Unpack the result and send it to the user + long result = (long)(packedResult & TaskSourceCodes.ResultMask); + if (result == TaskSourceCodes.ResultError) + { + int errorCode = unchecked((int)(packedResult & uint.MaxValue)); + if (errorCode == Interop.Errors.ERROR_OPERATION_ABORTED) + { + TrySetCanceled(cancellationToken.IsCancellationRequested ? cancellationToken : new CancellationToken(true)); + } + else + { + Exception e = Win32Marshal.GetExceptionForWin32Error(errorCode); + e.SetCurrentStackTrace(); + TrySetException(e); + } + } + else + { + Debug.Assert(result == TaskSourceCodes.ResultSuccess, "Unknown result"); + TrySetResult((int)(packedResult & uint.MaxValue) + _numBufferedBytes); + } + } + + private static void Cancel(object? state) + { + // WARNING: This may potentially be called under a lock (during cancellation registration) + + Debug.Assert(state is CompletionSource, "Unknown state passed to cancellation"); + CompletionSource completionSource = (CompletionSource)state; + Debug.Assert(completionSource._overlapped != null && !completionSource.Task.IsCompleted, "IO should not have completed yet"); + + // If the handle is still valid, attempt to cancel the IO + if (!completionSource._strategy._fileHandle.IsInvalid && + !Interop.Kernel32.CancelIoEx(completionSource._strategy._fileHandle, completionSource._overlapped)) + { + int errorCode = Marshal.GetLastWin32Error(); + + // ERROR_NOT_FOUND is returned if CancelIoEx cannot find the request to cancel. + // This probably means that the IO operation has completed. + if (errorCode != Interop.Errors.ERROR_NOT_FOUND) + { + throw Win32Marshal.GetExceptionForWin32Error(errorCode); + } + } + } + + public static CompletionSource Create(Net5CompatFileStreamStrategy strategy, PreAllocatedOverlapped? preallocatedOverlapped, + int numBufferedBytesRead, ReadOnlyMemory 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 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); + } + } + + /// + /// Extends with to support disposing of a + /// when the operation has completed. This should only be used + /// when memory doesn't wrap a byte[]. + /// + private sealed class MemoryFileStreamCompletionSource : CompletionSource + { + private MemoryHandle _handle; // mutable struct; do not make this readonly + + internal MemoryFileStreamCompletionSource(Net5CompatFileStreamStrategy strategy, int numBufferedBytes, ReadOnlyMemory memory) + : base(strategy, null, numBufferedBytes, null) // this type handles the pinning, so null is passed for bytes + { + _handle = memory.Pin(); + } + + internal override void ReleaseNativeResource() + { + _handle.Dispose(); + base.ReleaseNativeResource(); + } + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/Net5CompatFileStreamStrategy.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/Net5CompatFileStreamStrategy.Windows.cs index 5ed9f5937aa21a..fead01218fb0c3 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/Net5CompatFileStreamStrategy.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/Net5CompatFileStreamStrategy.Windows.cs @@ -36,7 +36,7 @@ namespace System.IO.Strategies { - internal sealed partial class Net5CompatFileStreamStrategy : FileStreamStrategy, IFileStreamCompletionSourceStrategy + internal sealed partial class Net5CompatFileStreamStrategy : FileStreamStrategy { private bool _canSeek; private bool _isPipe; // Whether to disable async buffering code. @@ -44,7 +44,7 @@ internal sealed partial class Net5CompatFileStreamStrategy : FileStreamStrategy, private Task _activeBufferOperation = Task.CompletedTask; // tracks in-progress async ops using the buffer private PreAllocatedOverlapped? _preallocatedOverlapped; // optimization for async ops to avoid per-op allocations - private FileStreamCompletionSource? _currentOverlappedOwner; // async op currently using the preallocated overlapped + private CompletionSource? _currentOverlappedOwner; // async op currently using the preallocated overlapped private void Init(FileMode mode, FileShare share, string originalPath, FileOptions options) { @@ -427,13 +427,13 @@ private unsafe int ReadNative(Span buffer) if (r == -1) { // For pipes, ERROR_BROKEN_PIPE is the normal end of the pipe. - if (errorCode == ERROR_BROKEN_PIPE) + if (errorCode == Interop.Errors.ERROR_BROKEN_PIPE) { r = 0; } else { - if (errorCode == ERROR_INVALID_PARAMETER) + if (errorCode == Interop.Errors.ERROR_INVALID_PARAMETER) ThrowHelper.ThrowArgumentException_HandleNotSync(nameof(_fileHandle)); throw Win32Marshal.GetExceptionForWin32Error(errorCode, _path); @@ -544,14 +544,10 @@ partial void OnBufferAllocated() Debug.Assert(_preallocatedOverlapped == null); if (_useAsyncIO) - _preallocatedOverlapped = new PreAllocatedOverlapped(FileStreamCompletionSource.s_ioCallback, this, _buffer); + _preallocatedOverlapped = new PreAllocatedOverlapped(CompletionSource.s_ioCallback, this, _buffer); } - SafeFileHandle IFileStreamCompletionSourceStrategy.FileHandle => _fileHandle; - - FileStreamCompletionSource? IFileStreamCompletionSourceStrategy.CurrentOverlappedOwner => _currentOverlappedOwner; - - FileStreamCompletionSource? IFileStreamCompletionSourceStrategy.CompareExchangeCurrentOverlappedOwner(FileStreamCompletionSource? newSource, FileStreamCompletionSource? existingSource) + private CompletionSource? CompareExchangeCurrentOverlappedOwner(CompletionSource? newSource, CompletionSource? existingSource) => Interlocked.CompareExchange(ref _currentOverlappedOwner, newSource, existingSource); private void WriteSpan(ReadOnlySpan source) @@ -633,7 +629,7 @@ private unsafe void WriteCore(ReadOnlySpan source) if (r == -1) { // For pipes, ERROR_NO_DATA is not an error, but the pipe is closing. - if (errorCode == ERROR_NO_DATA) + if (errorCode == Interop.Errors.ERROR_NO_DATA) { r = 0; } @@ -642,7 +638,7 @@ private unsafe void WriteCore(ReadOnlySpan source) // ERROR_INVALID_PARAMETER may be returned for writes // where the position is too large or for synchronous writes // to a handle opened asynchronously. - if (errorCode == ERROR_INVALID_PARAMETER) + if (errorCode == Interop.Errors.ERROR_INVALID_PARAMETER) throw new IOException(SR.IO_FileTooLongOrHandleNotSync); throw Win32Marshal.GetExceptionForWin32Error(errorCode, _path); } @@ -761,7 +757,7 @@ private unsafe Task ReadNativeAsync(Memory destination, int numBuffer Debug.Assert(_useAsyncIO, "ReadNativeAsync doesn't work on synchronous file streams!"); // Create and store async stream class library specific data in the async result - FileStreamCompletionSource completionSource = FileStreamCompletionSource.Create(this, _preallocatedOverlapped, numBufferedBytesRead, destination); + CompletionSource completionSource = CompletionSource.Create(this, _preallocatedOverlapped, numBufferedBytesRead, destination); NativeOverlapped* intOverlapped = completionSource.Overlapped; // Calculate position in the file we should be at after the read is done @@ -817,7 +813,7 @@ private unsafe Task ReadNativeAsync(Memory destination, int numBuffer if (r == -1) { // For pipes, when they hit EOF, they will come here. - if (errorCode == ERROR_BROKEN_PIPE) + if (errorCode == Interop.Errors.ERROR_BROKEN_PIPE) { // Not an error, but EOF. AsyncFSCallback will NOT be // called. Call the user callback here. @@ -827,7 +823,7 @@ private unsafe Task ReadNativeAsync(Memory destination, int numBuffer intOverlapped->InternalLow = IntPtr.Zero; completionSource.SetCompletedSynchronously(0); } - else if (errorCode != ERROR_IO_PENDING) + else if (errorCode != Interop.Errors.ERROR_IO_PENDING) { if (!_fileHandle.IsClosed && CanSeek) // Update Position - It could be anywhere. { @@ -836,7 +832,7 @@ private unsafe Task ReadNativeAsync(Memory destination, int numBuffer completionSource.ReleaseNativeResource(); - if (errorCode == ERROR_HANDLE_EOF) + if (errorCode == Interop.Errors.ERROR_HANDLE_EOF) { ThrowHelper.ThrowEndOfFileException(); } @@ -979,7 +975,7 @@ private unsafe Task WriteAsyncInternalCore(ReadOnlyMemory source, Cancella Debug.Assert(_useAsyncIO, "WriteInternalCoreAsync doesn't work on synchronous file streams!"); // Create and store async stream class library specific data in the async result - FileStreamCompletionSource completionSource = FileStreamCompletionSource.Create(this, _preallocatedOverlapped, 0, source); + CompletionSource completionSource = CompletionSource.Create(this, _preallocatedOverlapped, 0, source); NativeOverlapped* intOverlapped = completionSource.Overlapped; if (CanSeek) @@ -1022,14 +1018,14 @@ private unsafe Task WriteAsyncInternalCore(ReadOnlyMemory source, Cancella if (r == -1) { // For pipes, when they are closed on the other side, they will come here. - if (errorCode == ERROR_NO_DATA) + if (errorCode == Interop.Errors.ERROR_NO_DATA) { // Not an error, but EOF. AsyncFSCallback will NOT be called. // Completing TCS and return cached task allowing the GC to collect TCS. completionSource.SetCompletedSynchronously(0); return Task.CompletedTask; } - else if (errorCode != ERROR_IO_PENDING) + else if (errorCode != Interop.Errors.ERROR_IO_PENDING) { if (!_fileHandle.IsClosed && CanSeek) // Update Position - It could be anywhere. { @@ -1038,7 +1034,7 @@ private unsafe Task WriteAsyncInternalCore(ReadOnlyMemory source, Cancella completionSource.ReleaseNativeResource(); - if (errorCode == ERROR_HANDLE_EOF) + if (errorCode == Interop.Errors.ERROR_HANDLE_EOF) { ThrowHelper.ThrowEndOfFileException(); } @@ -1067,13 +1063,6 @@ private unsafe Task WriteAsyncInternalCore(ReadOnlyMemory source, Cancella return completionSource.Task; } - // Error codes (not HRESULTS), from winerror.h - internal const int ERROR_BROKEN_PIPE = 109; - internal const int ERROR_NO_DATA = 232; - private const int ERROR_HANDLE_EOF = 38; - private const int ERROR_INVALID_PARAMETER = 87; - private const int ERROR_IO_PENDING = 997; - // __ConsoleStream also uses this code. private unsafe int ReadFileNative(SafeFileHandle handle, Span bytes, NativeOverlapped* overlapped, out int errorCode) { diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/SyncWindowsFileStreamStrategy.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/SyncWindowsFileStreamStrategy.cs index 3639b4b5fb4daf..ad96cdb4967b44 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/SyncWindowsFileStreamStrategy.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/SyncWindowsFileStreamStrategy.cs @@ -110,13 +110,13 @@ private unsafe int ReadSpan(Span destination) if (r == -1) { // For pipes, ERROR_BROKEN_PIPE is the normal end of the pipe. - if (errorCode == ERROR_BROKEN_PIPE) + if (errorCode == Interop.Errors.ERROR_BROKEN_PIPE) { r = 0; } else { - if (errorCode == ERROR_INVALID_PARAMETER) + if (errorCode == Interop.Errors.ERROR_INVALID_PARAMETER) ThrowHelper.ThrowArgumentException_HandleNotSync(nameof(_fileHandle)); throw Win32Marshal.GetExceptionForWin32Error(errorCode, _path); @@ -143,7 +143,7 @@ private unsafe void WriteSpan(ReadOnlySpan source) if (r == -1) { // For pipes, ERROR_NO_DATA is not an error, but the pipe is closing. - if (errorCode == ERROR_NO_DATA) + if (errorCode == Interop.Errors.ERROR_NO_DATA) { r = 0; } @@ -152,7 +152,7 @@ private unsafe void WriteSpan(ReadOnlySpan source) // ERROR_INVALID_PARAMETER may be returned for writes // where the position is too large or for synchronous writes // to a handle opened asynchronously. - if (errorCode == ERROR_INVALID_PARAMETER) + if (errorCode == Interop.Errors.ERROR_INVALID_PARAMETER) throw new IOException(SR.IO_FileTooLongOrHandleNotSync); throw Win32Marshal.GetExceptionForWin32Error(errorCode, _path); } diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/WindowsFileStreamStrategy.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/WindowsFileStreamStrategy.cs index d42494c19086d2..94686aa9c05326 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/WindowsFileStreamStrategy.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/WindowsFileStreamStrategy.cs @@ -10,13 +10,6 @@ namespace System.IO.Strategies // this type serves some basic functionality that is common for Async and Sync Windows File Stream Strategies internal abstract class WindowsFileStreamStrategy : FileStreamStrategy { - // Error codes (not HRESULTS), from winerror.h - internal const int ERROR_BROKEN_PIPE = 109; - internal const int ERROR_NO_DATA = 232; - protected const int ERROR_HANDLE_EOF = 38; - protected const int ERROR_INVALID_PARAMETER = 87; - protected const int ERROR_IO_PENDING = 997; - protected readonly SafeFileHandle _fileHandle; // only ever null if ctor throws protected readonly string? _path; // The path to the opened file. private readonly FileAccess _access; // What file was opened for.