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.