diff --git a/src/coreclr/System.Private.CoreLib/src/System/Threading/ClrThreadPoolBoundHandle.cs b/src/coreclr/System.Private.CoreLib/src/System/Threading/ClrThreadPoolBoundHandle.cs index eff292e5840513..b188c8f8a162a9 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Threading/ClrThreadPoolBoundHandle.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Threading/ClrThreadPoolBoundHandle.cs @@ -124,14 +124,65 @@ public static ThreadPoolBoundHandle BindHandle(SafeHandle handle) /// This method was called after the was disposed. /// [CLSCompliant(false)] - public unsafe NativeOverlapped* AllocateNativeOverlapped(IOCompletionCallback callback, object? state, object? pinData) + public unsafe NativeOverlapped* AllocateNativeOverlapped(IOCompletionCallback callback, object? state, object? pinData) => + AllocateNativeOverlapped(callback, state, pinData, flowExecutionContext: true); + + /// + /// Returns an unmanaged pointer to a structure, specifying + /// a delegate that is invoked when the asynchronous I/O operation is complete, a user-provided + /// object providing context, and managed objects that serve as buffers. + /// + /// + /// An delegate that represents the callback method + /// invoked when the asynchronous I/O operation completes. + /// + /// + /// A user-provided object that distinguishes this from other + /// instances. Can be . + /// + /// + /// An object or array of objects representing the input or output buffer for the operation. Each + /// object represents a buffer, for example an array of bytes. Can be . + /// + /// + /// An unmanaged pointer to a structure. + /// + /// + /// + /// The unmanaged pointer returned by this method can be passed to the operating system in + /// overlapped I/O operations. The structure is fixed in + /// physical memory until is called. + /// + /// + /// The buffer or buffers specified in must be the same as those passed + /// to the unmanaged operating system function that performs the asynchronous I/O. + /// + /// + /// is not flowed to the invocation of the callback. + /// + /// + /// The buffers specified in are pinned for the duration of + /// the I/O operation. + /// + /// + /// + /// is . + /// + /// + /// This method was called after the was disposed. + /// + [CLSCompliant(false)] + public unsafe NativeOverlapped* UnsafeAllocateNativeOverlapped(IOCompletionCallback callback, object? state, object? pinData) => + AllocateNativeOverlapped(callback, state, pinData, flowExecutionContext: false); + + private unsafe NativeOverlapped* AllocateNativeOverlapped(IOCompletionCallback callback, object? state, object? pinData, bool flowExecutionContext) { if (callback == null) throw new ArgumentNullException(nameof(callback)); EnsureNotDisposed(); - ThreadPoolBoundHandleOverlapped overlapped = new ThreadPoolBoundHandleOverlapped(callback, state, pinData, preAllocated: null); + ThreadPoolBoundHandleOverlapped overlapped = new ThreadPoolBoundHandleOverlapped(callback, state, pinData, preAllocated: null, flowExecutionContext); overlapped._boundHandle = this; return overlapped._nativeOverlapped; } diff --git a/src/coreclr/System.Private.CoreLib/src/System/Threading/ClrThreadPoolBoundHandleOverlapped.cs b/src/coreclr/System.Private.CoreLib/src/System/Threading/ClrThreadPoolBoundHandleOverlapped.cs index bc2d082896170a..3fe953d5d55fa5 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Threading/ClrThreadPoolBoundHandleOverlapped.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Threading/ClrThreadPoolBoundHandleOverlapped.cs @@ -6,29 +6,32 @@ namespace System.Threading /// /// Overlapped subclass adding data needed by ThreadPoolBoundHandle. /// - internal sealed class ThreadPoolBoundHandleOverlapped : Overlapped + internal unsafe sealed class ThreadPoolBoundHandleOverlapped : Overlapped { - private static readonly unsafe IOCompletionCallback s_completionCallback = CompletionCallback; + private static readonly IOCompletionCallback s_completionCallback = CompletionCallback; private readonly IOCompletionCallback _userCallback; internal readonly object? _userState; - internal PreAllocatedOverlapped? _preAllocated; - internal unsafe NativeOverlapped* _nativeOverlapped; + internal readonly PreAllocatedOverlapped? _preAllocated; + + internal NativeOverlapped* _nativeOverlapped; internal ThreadPoolBoundHandle? _boundHandle; internal bool _completed; - public unsafe ThreadPoolBoundHandleOverlapped(IOCompletionCallback callback, object? state, object? pinData, PreAllocatedOverlapped? preAllocated) + public ThreadPoolBoundHandleOverlapped(IOCompletionCallback callback, object? state, object? pinData, PreAllocatedOverlapped? preAllocated, bool flowExecutionContext) { _userCallback = callback; _userState = state; _preAllocated = preAllocated; - _nativeOverlapped = Pack(s_completionCallback, pinData); - _nativeOverlapped->OffsetLow = 0; // CLR reuses NativeOverlapped instances and does not reset these + _nativeOverlapped = flowExecutionContext ? + Pack(s_completionCallback, pinData) : + UnsafePack(s_completionCallback, pinData); + _nativeOverlapped->OffsetLow = 0; // CLR reuses NativeOverlapped instances and does not reset these _nativeOverlapped->OffsetHigh = 0; } - private static unsafe void CompletionCallback(uint errorCode, uint numBytes, NativeOverlapped* nativeOverlapped) + private static void CompletionCallback(uint errorCode, uint numBytes, NativeOverlapped* nativeOverlapped) { ThreadPoolBoundHandleOverlapped overlapped = (ThreadPoolBoundHandleOverlapped)Overlapped.Unpack(nativeOverlapped); diff --git a/src/coreclr/System.Private.CoreLib/src/System/Threading/ClrThreadPoolPreAllocatedOverlapped.cs b/src/coreclr/System.Private.CoreLib/src/System/Threading/ClrThreadPoolPreAllocatedOverlapped.cs index d856d1d9231b3f..560f2e449578f1 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Threading/ClrThreadPoolPreAllocatedOverlapped.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Threading/ClrThreadPoolPreAllocatedOverlapped.cs @@ -47,12 +47,56 @@ public sealed class PreAllocatedOverlapped : IDisposable, IDeferredDisposable /// This method was called after the was disposed. /// [CLSCompliant(false)] - public PreAllocatedOverlapped(IOCompletionCallback callback, object? state, object? pinData) + public PreAllocatedOverlapped(IOCompletionCallback callback, object? state, object? pinData) : + this(callback, state, pinData, flowExecutionContext: true) + { + } + + /// + /// Initializes a new instance of the class, specifying + /// a delegate that is invoked when each asynchronous I/O operation is complete, a user-provided + /// object providing context, and managed objects that serve as buffers. + /// + /// + /// An delegate that represents the callback method + /// invoked when each asynchronous I/O operation completes. + /// + /// + /// A user-provided object that distinguishes instance produced from this + /// object from other instances. Can be . + /// + /// + /// An object or array of objects representing the input or output buffer for the operations. Each + /// object represents a buffer, for example an array of bytes. Can be . + /// + /// + /// The new instance can be passed to + /// , to produce + /// a instance that can be passed to the operating system in overlapped + /// I/O operations. A single instance can only be used for + /// a single native I/O operation at a time. However, the state stored in the + /// instance can be reused for subsequent native operations. ExecutionContext is not flowed to the invocation + /// of the callback. + /// + /// The buffers specified in are pinned until is called. + /// + /// + /// + /// is . + /// + /// + /// This method was called after the was disposed. + /// + [CLSCompliant(false)] + public static PreAllocatedOverlapped UnsafeCreate(IOCompletionCallback callback, object? state, object? pinData) => + new PreAllocatedOverlapped(callback, state, pinData, flowExecutionContext: false); + + private PreAllocatedOverlapped(IOCompletionCallback callback, object? state, object? pinData, bool flowExecutionContext) { if (callback == null) throw new ArgumentNullException(nameof(callback)); - _overlapped = new ThreadPoolBoundHandleOverlapped(callback, state, pinData, this); + _overlapped = new ThreadPoolBoundHandleOverlapped(callback, state, pinData, this, flowExecutionContext); } internal bool AddRef() diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.Windows.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.Windows.cs index 7bca8498ebca1a..d79d340c53164a 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.Windows.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.Windows.cs @@ -83,20 +83,9 @@ private enum PinState : byte { None = 0, MultipleBuffer, SendPackets } [MemberNotNull(nameof(_preAllocatedOverlapped))] private void InitializeInternals() { - // PreAllocatedOverlapped captures ExecutionContext, but SocketAsyncEventArgs ensures - // that context is properly flowed if necessary, and thus we don't need the overlapped - // infrastructure capturing and flowing as well. - bool suppressFlow = !ExecutionContext.IsFlowSuppressed(); - try - { - Debug.Assert(OperatingSystem.IsWindows()); - if (suppressFlow) ExecutionContext.SuppressFlow(); - _preAllocatedOverlapped = new PreAllocatedOverlapped(s_completionPortCallback, _strongThisRef, null); - } - finally - { - if (suppressFlow) ExecutionContext.RestoreFlow(); - } + Debug.Assert(OperatingSystem.IsWindows()); + + _preAllocatedOverlapped = PreAllocatedOverlapped.UnsafeCreate(s_completionPortCallback, _strongThisRef, null); if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"new PreAllocatedOverlapped {_preAllocatedOverlapped}"); } 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 index 1a7fcbe9f913eb..f1153a5cc041ce 100644 --- 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 @@ -32,7 +32,7 @@ internal ValueTaskSource(AsyncWindowsFileStreamStrategy strategy) { _strategy = strategy; _source.RunContinuationsAsynchronously = true; - _preallocatedOverlapped = new PreAllocatedOverlapped(s_ioCallback, this, null); + _preallocatedOverlapped = PreAllocatedOverlapped.UnsafeCreate(s_ioCallback, this, null); } internal void Dispose() 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 8b51b2597fed0e..ceb9bcc6e40527 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 @@ -544,7 +544,7 @@ partial void OnBufferAllocated() Debug.Assert(_preallocatedOverlapped == null); if (_useAsyncIO) - _preallocatedOverlapped = new PreAllocatedOverlapped(CompletionSource.s_ioCallback, this, _buffer); + _preallocatedOverlapped = PreAllocatedOverlapped.UnsafeCreate(CompletionSource.s_ioCallback, this, _buffer); } private CompletionSource? CompareExchangeCurrentOverlappedOwner(CompletionSource? newSource, CompletionSource? existingSource) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.PlatformNotSupported.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.PlatformNotSupported.cs index 3e34c04ff6a592..e7da142c9ee2e8 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.PlatformNotSupported.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.PlatformNotSupported.cs @@ -33,6 +33,10 @@ public static ThreadPoolBoundHandle BindHandle(SafeHandle handle) throw new PlatformNotSupportedException(SR.PlatformNotSupported_OverlappedIO); } + [CLSCompliant(false)] + public unsafe NativeOverlapped* UnsafeAllocateNativeOverlapped(IOCompletionCallback callback, object? state, object? pinData) => + AllocateNativeOverlapped(callback, state, pinData); + [CLSCompliant(false)] public unsafe NativeOverlapped* AllocateNativeOverlapped(PreAllocatedOverlapped preAllocated) { diff --git a/src/libraries/System.Threading.Overlapped/ref/System.Threading.Overlapped.cs b/src/libraries/System.Threading.Overlapped/ref/System.Threading.Overlapped.cs index 634e1697d46144..9f9312cc017cc8 100644 --- a/src/libraries/System.Threading.Overlapped/ref/System.Threading.Overlapped.cs +++ b/src/libraries/System.Threading.Overlapped/ref/System.Threading.Overlapped.cs @@ -49,6 +49,8 @@ public sealed partial class PreAllocatedOverlapped : System.IDisposable public PreAllocatedOverlapped(System.Threading.IOCompletionCallback callback, object? state, object? pinData) { } public void Dispose() { } ~PreAllocatedOverlapped() { } + [System.CLSCompliantAttribute(false)] + public static System.Threading.PreAllocatedOverlapped UnsafeCreate(System.Threading.IOCompletionCallback callback, object? state, object? pinData) { throw null; } } public sealed partial class ThreadPoolBoundHandle : System.IDisposable { @@ -64,5 +66,7 @@ public void Dispose() { } public unsafe void FreeNativeOverlapped(System.Threading.NativeOverlapped* overlapped) { } [System.CLSCompliantAttribute(false)] public unsafe static object? GetNativeOverlappedState(System.Threading.NativeOverlapped* overlapped) { throw null; } + [System.CLSCompliantAttribute(false)] + public unsafe System.Threading.NativeOverlapped* UnsafeAllocateNativeOverlapped(System.Threading.IOCompletionCallback callback, object? state, object? pinData) { throw null; } } } diff --git a/src/libraries/System.Threading.Overlapped/tests/ThreadPoolBoundHandle_AllocateNativeOverlappedTests.cs b/src/libraries/System.Threading.Overlapped/tests/ThreadPoolBoundHandle_AllocateNativeOverlappedTests.cs index ca73c0db329e7d..5621e8a4f124e9 100644 --- a/src/libraries/System.Threading.Overlapped/tests/ThreadPoolBoundHandle_AllocateNativeOverlappedTests.cs +++ b/src/libraries/System.Threading.Overlapped/tests/ThreadPoolBoundHandle_AllocateNativeOverlappedTests.cs @@ -13,12 +13,10 @@ public partial class ThreadPoolBoundHandleTests [PlatformSpecific(TestPlatforms.Windows)] // ThreadPoolBoundHandle.BindHandle is not supported on Unix public unsafe void AllocateNativeOverlapped_NullAsCallback_ThrowsArgumentNullException() { - using(ThreadPoolBoundHandle handle = CreateThreadPoolBoundHandle()) + using (ThreadPoolBoundHandle handle = CreateThreadPoolBoundHandle()) { - AssertExtensions.Throws("callback", () => - { - handle.AllocateNativeOverlapped(null, new object(), new byte[256]); - }); + AssertExtensions.Throws("callback", () => handle.AllocateNativeOverlapped(null, new object(), new byte[256])); + AssertExtensions.Throws("callback", () => handle.UnsafeAllocateNativeOverlapped(null, new object(), new byte[256])); } } @@ -26,12 +24,9 @@ public unsafe void AllocateNativeOverlapped_NullAsCallback_ThrowsArgumentNullExc [PlatformSpecific(TestPlatforms.Windows)] // ThreadPoolBoundHandle.BindHandle is not supported on Unix public unsafe void AllocateNativeOverlapped_PreAllocated_ThrowsArgumentNullException() { - using(ThreadPoolBoundHandle handle = CreateThreadPoolBoundHandle()) + using (ThreadPoolBoundHandle handle = CreateThreadPoolBoundHandle()) { - AssertExtensions.Throws("preAllocated", () => - { - handle.AllocateNativeOverlapped((PreAllocatedOverlapped)null); - }); + AssertExtensions.Throws("preAllocated", () => handle.AllocateNativeOverlapped((PreAllocatedOverlapped)null)); } } @@ -39,12 +34,14 @@ public unsafe void AllocateNativeOverlapped_PreAllocated_ThrowsArgumentNullExcep [PlatformSpecific(TestPlatforms.Windows)] // ThreadPoolBoundHandle.BindHandle is not supported on Unix public unsafe void AllocateNativeOverlapped_NullAsContext_DoesNotThrow() { - using(ThreadPoolBoundHandle handle = CreateThreadPoolBoundHandle()) + using (ThreadPoolBoundHandle handle = CreateThreadPoolBoundHandle()) { NativeOverlapped* result = handle.AllocateNativeOverlapped((_, __, ___) => { }, (object)null, new byte[256]); - Assert.True(result != null); + handle.FreeNativeOverlapped(result); + result = handle.UnsafeAllocateNativeOverlapped((_, __, ___) => { }, (object)null, new byte[256]); + Assert.True(result != null); handle.FreeNativeOverlapped(result); } } @@ -53,12 +50,14 @@ public unsafe void AllocateNativeOverlapped_NullAsContext_DoesNotThrow() [PlatformSpecific(TestPlatforms.Windows)] // ThreadPoolBoundHandle.BindHandle is not supported on Unix public unsafe void AllocateNativeOverlapped_NullAsPinData_DoesNotThrow() { - using(ThreadPoolBoundHandle handle = CreateThreadPoolBoundHandle()) + using (ThreadPoolBoundHandle handle = CreateThreadPoolBoundHandle()) { NativeOverlapped* result = handle.AllocateNativeOverlapped((_, __, ___) => { }, new object(), (byte[])null); - Assert.True(result != null); + handle.FreeNativeOverlapped(result); + result = handle.UnsafeAllocateNativeOverlapped((_, __, ___) => { }, new object(), (byte[])null); + Assert.True(result != null); handle.FreeNativeOverlapped(result); } } @@ -67,12 +66,14 @@ public unsafe void AllocateNativeOverlapped_NullAsPinData_DoesNotThrow() [PlatformSpecific(TestPlatforms.Windows)] // ThreadPoolBoundHandle.BindHandle is not supported on Unix public unsafe void AllocateNativeOverlapped_EmptyArrayAsPinData_DoesNotThrow() { - using(ThreadPoolBoundHandle handle = CreateThreadPoolBoundHandle()) + using (ThreadPoolBoundHandle handle = CreateThreadPoolBoundHandle()) { NativeOverlapped* result = handle.AllocateNativeOverlapped((_, __, ___) => { }, new object(), new byte[0]); - Assert.True(result != null); + handle.FreeNativeOverlapped(result); + result = handle.UnsafeAllocateNativeOverlapped((_, __, ___) => { }, new object(), new byte[0]); + Assert.True(result != null); handle.FreeNativeOverlapped(result); } } @@ -81,9 +82,10 @@ public unsafe void AllocateNativeOverlapped_EmptyArrayAsPinData_DoesNotThrow() [PlatformSpecific(TestPlatforms.Windows)] // ThreadPoolBoundHandle.BindHandle is not supported on Unix public unsafe void AllocateNativeOverlapped_NonBlittableTypeAsPinData_Throws() { - using(ThreadPoolBoundHandle handle = CreateThreadPoolBoundHandle()) + using (ThreadPoolBoundHandle handle = CreateThreadPoolBoundHandle()) { AssertExtensions.Throws(null, () => handle.AllocateNativeOverlapped((_, __, ___) => { }, new object(), new NonBlittableType() { s = "foo" })); + AssertExtensions.Throws(null, () => handle.UnsafeAllocateNativeOverlapped((_, __, ___) => { }, new object(), new NonBlittableType() { s = "foo" })); } } @@ -91,12 +93,14 @@ public unsafe void AllocateNativeOverlapped_NonBlittableTypeAsPinData_Throws() [PlatformSpecific(TestPlatforms.Windows)] // ThreadPoolBoundHandle.BindHandle is not supported on Unix public unsafe void AllocateNativeOverlapped_BlittableTypeAsPinData_DoesNotThrow() { - using(ThreadPoolBoundHandle handle = CreateThreadPoolBoundHandle()) + using (ThreadPoolBoundHandle handle = CreateThreadPoolBoundHandle()) { NativeOverlapped* result = handle.AllocateNativeOverlapped((_, __, ___) => { }, new object(), new BlittableType() { i = 42 }); - Assert.True(result != null); + handle.FreeNativeOverlapped(result); + result = handle.UnsafeAllocateNativeOverlapped((_, __, ___) => { }, new object(), new BlittableType() { i = 42 }); + Assert.True(result != null); handle.FreeNativeOverlapped(result); } } @@ -105,17 +109,20 @@ public unsafe void AllocateNativeOverlapped_BlittableTypeAsPinData_DoesNotThrow( [PlatformSpecific(TestPlatforms.Windows)] // ThreadPoolBoundHandle.BindHandle is not supported on Unix public unsafe void AllocateNativeOverlapped_ObjectArrayAsPinData_DoesNotThrow() { - object[] array = new object[] + var array = new object[] { new BlittableType() { i = 1 }, new byte[5], }; - using(ThreadPoolBoundHandle handle = CreateThreadPoolBoundHandle()) + + using (ThreadPoolBoundHandle handle = CreateThreadPoolBoundHandle()) { NativeOverlapped* result = handle.AllocateNativeOverlapped((_, __, ___) => { }, new object(), array); - Assert.True(result != null); + handle.FreeNativeOverlapped(result); + result = handle.UnsafeAllocateNativeOverlapped((_, __, ___) => { }, new object(), array); + Assert.True(result != null); handle.FreeNativeOverlapped(result); } } @@ -124,24 +131,30 @@ public unsafe void AllocateNativeOverlapped_ObjectArrayAsPinData_DoesNotThrow() [PlatformSpecific(TestPlatforms.Windows)] // ThreadPoolBoundHandle.BindHandle is not supported on Unix public unsafe void AllocateNativeOverlapped_ObjectArrayWithNonBlittableTypeAsPinData_Throws() { - object[] array = new object[] + var array = new object[] { new NonBlittableType() { s = "foo" }, new byte[5], }; - using(ThreadPoolBoundHandle handle = CreateThreadPoolBoundHandle()) + + using (ThreadPoolBoundHandle handle = CreateThreadPoolBoundHandle()) { AssertExtensions.Throws(null, () => handle.AllocateNativeOverlapped((_, __, ___) => { }, new object(), array)); + AssertExtensions.Throws(null, () => handle.UnsafeAllocateNativeOverlapped((_, __, ___) => { }, new object(), array)); } } - [Fact] + [Theory] + [InlineData(false)] + [InlineData(true)] [PlatformSpecific(TestPlatforms.Windows)] // ThreadPoolBoundHandle.BindHandle is not supported on Unix - public unsafe void AllocateNativeOverlapped_ReturnedNativeOverlapped_AllFieldsZero() + public unsafe void AllocateNativeOverlapped_ReturnedNativeOverlapped_AllFieldsZero(bool useUnsafe) { - using(ThreadPoolBoundHandle handle = CreateThreadPoolBoundHandle()) + using (ThreadPoolBoundHandle handle = CreateThreadPoolBoundHandle()) { - NativeOverlapped* overlapped = handle.AllocateNativeOverlapped((_, __, ___) => { }, new object(), new byte[256]); + NativeOverlapped* overlapped = useUnsafe ? + handle.UnsafeAllocateNativeOverlapped((_, __, ___) => { }, new object(), new byte[256]) : + handle.AllocateNativeOverlapped((_, __, ___) => { }, new object(), new byte[256]); Assert.Equal(IntPtr.Zero, overlapped->InternalLow); Assert.Equal(IntPtr.Zero, overlapped->InternalHigh); @@ -153,13 +166,17 @@ public unsafe void AllocateNativeOverlapped_ReturnedNativeOverlapped_AllFieldsZe } } - [Fact] + [Theory] + [InlineData(false)] + [InlineData(true)] [PlatformSpecific(TestPlatforms.Windows)] // ThreadPoolBoundHandle.BindHandle is not supported on Unix - public unsafe void AllocateNativeOverlapped_PreAllocated_ReturnedNativeOverlapped_AllFieldsZero() + public unsafe void AllocateNativeOverlapped_PreAllocated_ReturnedNativeOverlapped_AllFieldsZero(bool useUnsafe) { - using(ThreadPoolBoundHandle handle = CreateThreadPoolBoundHandle()) + using (ThreadPoolBoundHandle handle = CreateThreadPoolBoundHandle()) { - using(PreAllocatedOverlapped preAlloc = new PreAllocatedOverlapped((_, __, ___) => { }, new object(), new byte[256])) + using (PreAllocatedOverlapped preAlloc = useUnsafe ? + PreAllocatedOverlapped.UnsafeCreate((_, __, ___) => { }, new object(), new byte[256]) : + new PreAllocatedOverlapped((_, __, ___) => { }, new object(), new byte[256])) { NativeOverlapped* overlapped = handle.AllocateNativeOverlapped(preAlloc); @@ -174,19 +191,25 @@ public unsafe void AllocateNativeOverlapped_PreAllocated_ReturnedNativeOverlappe } } - [Fact] + [Theory] + [InlineData(false)] + [InlineData(true)] [PlatformSpecific(TestPlatforms.Windows)] // ThreadPoolBoundHandle.BindHandle is not supported on Unix - public unsafe void AllocateNativeOverlapped_PossibleReusedReturnedNativeOverlapped_OffsetLowAndOffsetHighSetToZero() - { // The CLR reuses NativeOverlapped underneath, check to make sure that they reset fields back to zero - - using(ThreadPoolBoundHandle handle = CreateThreadPoolBoundHandle()) + public unsafe void AllocateNativeOverlapped_PossibleReusedReturnedNativeOverlapped_OffsetLowAndOffsetHighSetToZero(bool useUnsafe) + { + // The CLR reuses NativeOverlapped underneath, check to make sure that they reset fields back to zero + using (ThreadPoolBoundHandle handle = CreateThreadPoolBoundHandle()) { - NativeOverlapped* overlapped = handle.AllocateNativeOverlapped((_, __, ___) => { }, new object(), new byte[256]); + NativeOverlapped* overlapped = useUnsafe ? + handle.UnsafeAllocateNativeOverlapped((_, __, ___) => { }, new object(), new byte[256]) : + handle.AllocateNativeOverlapped((_, __, ___) => { }, new object(), new byte[256]); overlapped->OffsetHigh = 1; overlapped->OffsetLow = 1; handle.FreeNativeOverlapped(overlapped); - overlapped = handle.AllocateNativeOverlapped((errorCode, numBytes, overlap) => { }, new object(), new byte[256]); + overlapped = useUnsafe ? + handle.UnsafeAllocateNativeOverlapped((errorCode, numBytes, overlap) => { }, new object(), new byte[256]) : + handle.AllocateNativeOverlapped((errorCode, numBytes, overlap) => { }, new object(), new byte[256]); Assert.Equal(IntPtr.Zero, overlapped->InternalLow); Assert.Equal(IntPtr.Zero, overlapped->InternalHigh); @@ -198,14 +221,19 @@ public unsafe void AllocateNativeOverlapped_PossibleReusedReturnedNativeOverlapp } } - [Fact] + [Theory] + [InlineData(false)] + [InlineData(true)] [PlatformSpecific(TestPlatforms.Windows)] // ThreadPoolBoundHandle.BindHandle is not supported on Unix - public unsafe void AllocateNativeOverlapped_PreAllocated_ReusedReturnedNativeOverlapped_OffsetLowAndOffsetHighSetToZero() - { // The CLR reuses NativeOverlapped underneath, check to make sure that they reset fields back to zero - - using(ThreadPoolBoundHandle handle = CreateThreadPoolBoundHandle()) + public unsafe void AllocateNativeOverlapped_PreAllocated_ReusedReturnedNativeOverlapped_OffsetLowAndOffsetHighSetToZero(bool useUnsafe) + { + // The CLR reuses NativeOverlapped underneath, check to make sure that they reset fields back to zero + using (ThreadPoolBoundHandle handle = CreateThreadPoolBoundHandle()) { - PreAllocatedOverlapped preAlloc = new PreAllocatedOverlapped((_, __, ___) => { }, new object(), new byte[256]); + PreAllocatedOverlapped preAlloc = useUnsafe ? + PreAllocatedOverlapped.UnsafeCreate((_, __, ___) => { }, new object(), new byte[256]) : + new PreAllocatedOverlapped((_, __, ___) => { }, new object(), new byte[256]); + NativeOverlapped* overlapped = handle.AllocateNativeOverlapped(preAlloc); overlapped->OffsetHigh = 1; overlapped->OffsetLow = 1; @@ -230,60 +258,59 @@ public unsafe void AllocateNativeOverlapped_WhenDisposed_ThrowsObjectDisposedExc ThreadPoolBoundHandle handle = CreateThreadPoolBoundHandle(); handle.Dispose(); - Assert.Throws(() => - { - handle.AllocateNativeOverlapped((_, __, ___) => { }, new object(), new byte[256]); - }); + Assert.Throws(() => handle.AllocateNativeOverlapped((_, __, ___) => { }, new object(), new byte[256])); + Assert.Throws(() => handle.UnsafeAllocateNativeOverlapped((_, __, ___) => { }, new object(), new byte[256])); } - [Fact] + [Theory] + [InlineData(false)] + [InlineData(true)] [PlatformSpecific(TestPlatforms.Windows)] // ThreadPoolBoundHandle.BindHandle is not supported on Unix - public unsafe void AllocateNativeOverlapped_PreAllocated_WhenDisposed_ThrowsObjectDisposedException() + public unsafe void AllocateNativeOverlapped_PreAllocated_WhenDisposed_ThrowsObjectDisposedException(bool useUnsafe) { - using(ThreadPoolBoundHandle handle = CreateThreadPoolBoundHandle()) + using (ThreadPoolBoundHandle handle = CreateThreadPoolBoundHandle()) { - PreAllocatedOverlapped preAlloc = new PreAllocatedOverlapped(delegate { }, null, null); + PreAllocatedOverlapped preAlloc = useUnsafe ? + PreAllocatedOverlapped.UnsafeCreate(delegate { }, null, null) : + new PreAllocatedOverlapped(delegate { }, null, null); preAlloc.Dispose(); - - Assert.Throws(() => - { - handle.AllocateNativeOverlapped(preAlloc); - }); + Assert.Throws(() => handle.AllocateNativeOverlapped(preAlloc)); } } - [Fact] + [Theory] + [InlineData(false)] + [InlineData(true)] [PlatformSpecific(TestPlatforms.Windows)] // ThreadPoolBoundHandle.BindHandle is not supported on Unix - public unsafe void AllocateNativeOverlapped_PreAllocated_WhenHandleDisposed_ThrowsObjectDisposedException() + public unsafe void AllocateNativeOverlapped_PreAllocated_WhenHandleDisposed_ThrowsObjectDisposedException(bool useUnsafe) { ThreadPoolBoundHandle handle = CreateThreadPoolBoundHandle(); handle.Dispose(); - PreAllocatedOverlapped preAlloc = new PreAllocatedOverlapped(delegate { }, null, null); + PreAllocatedOverlapped preAlloc = useUnsafe ? + PreAllocatedOverlapped.UnsafeCreate(delegate { }, null, null) : + new PreAllocatedOverlapped(delegate { }, null, null); - Assert.Throws(() => - { - handle.AllocateNativeOverlapped(preAlloc); - }); + Assert.Throws(() => handle.AllocateNativeOverlapped(preAlloc)); } - [Fact] + [Theory] + [InlineData(false)] + [InlineData(true)] [PlatformSpecific(TestPlatforms.Windows)] // ThreadPoolBoundHandle.BindHandle is not supported on Unix - public unsafe void AllocateNativeOverlapped_PreAllocated_WhenAlreadyAllocated_ThrowsArgumentException() + public unsafe void AllocateNativeOverlapped_PreAllocated_WhenAlreadyAllocated_ThrowsArgumentException(bool useUnsafe) { - using(ThreadPoolBoundHandle handle = CreateThreadPoolBoundHandle()) + using (ThreadPoolBoundHandle handle = CreateThreadPoolBoundHandle()) { - using(PreAllocatedOverlapped preAlloc = new PreAllocatedOverlapped(delegate { }, null, null)) - { - NativeOverlapped* overlapped = handle.AllocateNativeOverlapped(preAlloc); + using PreAllocatedOverlapped preAlloc = useUnsafe ? + PreAllocatedOverlapped.UnsafeCreate(delegate { }, null, null) : + new PreAllocatedOverlapped(delegate { }, null, null); - AssertExtensions.Throws("preAllocated", () => - { - handle.AllocateNativeOverlapped(preAlloc); - }); + NativeOverlapped* overlapped = handle.AllocateNativeOverlapped(preAlloc); - handle.FreeNativeOverlapped(overlapped); - } + AssertExtensions.Throws("preAllocated", () => handle.AllocateNativeOverlapped(preAlloc)); + + handle.FreeNativeOverlapped(overlapped); } } } diff --git a/src/libraries/System.Threading.Overlapped/tests/ThreadPoolBoundHandle_IntegrationTests.cs b/src/libraries/System.Threading.Overlapped/tests/ThreadPoolBoundHandle_IntegrationTests.cs index 008bf7aa2a5712..f7a3fff90cdf52 100644 --- a/src/libraries/System.Threading.Overlapped/tests/ThreadPoolBoundHandle_IntegrationTests.cs +++ b/src/libraries/System.Threading.Overlapped/tests/ThreadPoolBoundHandle_IntegrationTests.cs @@ -177,10 +177,13 @@ public unsafe void MultipleOperationsOverMultipleHandles() handle2.Dispose(); } - [Fact] + [Theory] + [InlineData(false)] + [InlineData(true)] [PlatformSpecific(TestPlatforms.Windows)] // ThreadPoolBoundHandle.BindHandle is not supported on Unix - public unsafe void FlowsAsyncLocalsToCallback() - { // Makes sure that we flow async locals to callback + public unsafe void FlowsAsyncLocalsToCallback(bool shouldFlow) + { + // Makes sure that we flow async locals to callback const int DATA_SIZE = 2; @@ -201,7 +204,9 @@ public unsafe void FlowsAsyncLocalsToCallback() OnOverlappedOperationCompleted(_, __, ___); }; - NativeOverlapped* overlapped = boundHandle.AllocateNativeOverlapped(callback, context, data); + NativeOverlapped* overlapped = shouldFlow ? + boundHandle.AllocateNativeOverlapped(callback, context, data) : + boundHandle.UnsafeAllocateNativeOverlapped(callback, context, data); fixed (byte* p = data) { @@ -220,7 +225,66 @@ public unsafe void FlowsAsyncLocalsToCallback() boundHandle.Dispose(); handle.Dispose(); - Assert.Equal(10, result); + Assert.Equal( + shouldFlow ? 10 : 0, + result); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + [PlatformSpecific(TestPlatforms.Windows)] // ThreadPoolBoundHandle.BindHandle is not supported on Unix + public unsafe void FlowsAsyncLocalsToCallback_PreAllocatedOverlapped(bool shouldFlow) + { + // Makes sure that we flow async locals to callback + + const int DATA_SIZE = 2; + + SafeHandle handle = HandleFactory.CreateAsyncFileHandleForWrite(Path.Combine(TestDirectory, @"AsyncLocal.tmp")); + ThreadPoolBoundHandle boundHandle = ThreadPoolBoundHandle.BindHandle(handle); + + OverlappedContext context = new OverlappedContext(); + + byte[] data = new byte[DATA_SIZE]; + + AsyncLocal asyncLocal = new AsyncLocal(); + asyncLocal.Value = 10; + + int? result = null; + IOCompletionCallback callback = (_, __, ___) => { + + result = asyncLocal.Value; + OnOverlappedOperationCompleted(_, __, ___); + }; + + using (PreAllocatedOverlapped preAlloc = shouldFlow ? + new PreAllocatedOverlapped(callback, context, data) : + PreAllocatedOverlapped.UnsafeCreate(callback, context, data)) + { + NativeOverlapped* overlapped = boundHandle.AllocateNativeOverlapped(preAlloc); + + fixed (byte* p = data) + { + int retval = DllImport.WriteFile(boundHandle.Handle, p, DATA_SIZE, IntPtr.Zero, overlapped); + + if (retval == 0) + { + Assert.Equal(DllImport.ERROR_IO_PENDING, Marshal.GetLastPInvokeError()); + } + + // Wait for overlapped operation to complete + context.Event.WaitOne(); + } + + boundHandle.FreeNativeOverlapped(overlapped); + } + + boundHandle.Dispose(); + handle.Dispose(); + + Assert.Equal( + shouldFlow ? 10 : 0, + result); } private static unsafe void OnOverlappedOperationCompleted(uint errorCode, uint numBytes, NativeOverlapped* overlapped) diff --git a/src/libraries/System.Threading.Overlapped/tests/ThreadPoolBoundHandle_PreAllocatedOverlappedTests.cs b/src/libraries/System.Threading.Overlapped/tests/ThreadPoolBoundHandle_PreAllocatedOverlappedTests.cs index ee8ccd3394d0b8..8517a5c0a595a5 100644 --- a/src/libraries/System.Threading.Overlapped/tests/ThreadPoolBoundHandle_PreAllocatedOverlappedTests.cs +++ b/src/libraries/System.Threading.Overlapped/tests/ThreadPoolBoundHandle_PreAllocatedOverlappedTests.cs @@ -12,10 +12,8 @@ public partial class ThreadPoolBoundHandleTests [ActiveIssue("https://github.com/mono/mono/issues/15313", TestRuntimes.Mono)] public unsafe void PreAllocatedOverlapped_NullAsCallback_ThrowsArgumentNullException() { - AssertExtensions.Throws("callback", () => - { - new PreAllocatedOverlapped(null, new object(), new byte[256]); - }); + AssertExtensions.Throws("callback", () => new PreAllocatedOverlapped(null, new object(), new byte[256])); + AssertExtensions.Throws("callback", () => PreAllocatedOverlapped.UnsafeCreate(null, new object(), new byte[256])); // Make sure the PreAllocatedOverlapped finalizer does the right thing in the case where the .ctor failed. GC.Collect(); @@ -25,19 +23,22 @@ public unsafe void PreAllocatedOverlapped_NullAsCallback_ThrowsArgumentNullExcep [Fact] public unsafe void PreAllocatedOverlapped_NullAsContext_DoesNotThrow() { - using(new PreAllocatedOverlapped((_, __, ___) => { }, (object)null, new byte[256])) {} + using (new PreAllocatedOverlapped((_, __, ___) => { }, (object)null, new byte[256])) { } + using (PreAllocatedOverlapped.UnsafeCreate((_, __, ___) => { }, (object)null, new byte[256])) { } } [Fact] public unsafe void PreAllocatedOverlapped_NullAsPinData_DoesNotThrow() { - using(new PreAllocatedOverlapped((_, __, ___) => { }, new object(), (byte[])null)) {} + using (new PreAllocatedOverlapped((_, __, ___) => { }, new object(), (byte[])null)) { } + using (PreAllocatedOverlapped.UnsafeCreate((_, __, ___) => { }, new object(), (byte[])null)) { } } [Fact] public unsafe void PreAllocatedOverlapped_EmptyArrayAsPinData_DoesNotThrow() { - using(new PreAllocatedOverlapped((_, __, ___) => { }, new object(), new byte[0])) {} + using (new PreAllocatedOverlapped((_, __, ___) => { }, new object(), new byte[0])) { } + using (PreAllocatedOverlapped.UnsafeCreate((_, __, ___) => { }, new object(), new byte[0])) { } } [Fact] @@ -45,6 +46,7 @@ public unsafe void PreAllocatedOverlapped_EmptyArrayAsPinData_DoesNotThrow() public unsafe void PreAllocatedOverlapped_NonBlittableTypeAsPinData_Throws() { AssertExtensions.Throws(null, () => new PreAllocatedOverlapped((_, __, ___) => { }, new object(), new NonBlittableType() { s = "foo" })); + AssertExtensions.Throws(null, () => PreAllocatedOverlapped.UnsafeCreate((_, __, ___) => { }, new object(), new NonBlittableType() { s = "foo" })); // Make sure the PreAllocatedOverlapped finalizer does the right thing in the case where the .ctor failed. GC.Collect(); @@ -54,30 +56,35 @@ public unsafe void PreAllocatedOverlapped_NonBlittableTypeAsPinData_Throws() [Fact] public unsafe void PreAllocatedOverlapped_BlittableTypeAsPinData_DoesNotThrow() { - using(new PreAllocatedOverlapped((_, __, ___) => { }, new object(), new BlittableType() { i = 42 })) {} + using (new PreAllocatedOverlapped((_, __, ___) => { }, new object(), new BlittableType() { i = 42 })) { } + using (PreAllocatedOverlapped.UnsafeCreate((_, __, ___) => { }, new object(), new BlittableType() { i = 42 })) { } } [Fact] public unsafe void PreAllocatedOverlapped_ObjectArrayAsPinData_DoesNotThrow() { - object[] array = new object[] + var array = new object[] { new BlittableType() { i = 1 }, new byte[5], }; - using(new PreAllocatedOverlapped((_, __, ___) => { }, new object(), array)) {} + + using (new PreAllocatedOverlapped((_, __, ___) => { }, new object(), array)) { } + using (PreAllocatedOverlapped.UnsafeCreate((_, __, ___) => { }, new object(), array)) { } } [Fact] [ActiveIssue("https://github.com/mono/mono/issues/15313", TestRuntimes.Mono)] public unsafe void PreAllocatedOverlapped_ObjectArrayWithNonBlittableTypeAsPinData_Throws() { - object[] array = new object[] + var array = new object[] { new NonBlittableType() { s = "foo" }, new byte[5], }; + AssertExtensions.Throws(null, () => new PreAllocatedOverlapped((_, __, ___) => { }, new object(), array)); + AssertExtensions.Throws(null, () => PreAllocatedOverlapped.UnsafeCreate((_, __, ___) => { }, new object(), array)); // Make sure the PreAllocatedOverlapped finalizer does the right thing in the case where the .ctor failed. GC.Collect(); @@ -87,12 +94,14 @@ public unsafe void PreAllocatedOverlapped_ObjectArrayWithNonBlittableTypeAsPinDa [Fact] public unsafe void PreAllocatedOverlapped_ReturnedNativeOverlapped_InternalLowAndInternalHighSetToZero() { - using(new PreAllocatedOverlapped((_, __, ___) => { }, new object(), new byte[256])) {} + using (new PreAllocatedOverlapped((_, __, ___) => { }, new object(), new byte[256])) { } + using (PreAllocatedOverlapped.UnsafeCreate((_, __, ___) => { }, new object(), new byte[256])) { } } [Fact] public unsafe void PreAllocatedOverlapped_ReturnedNativeOverlapped_OffsetLowAndOffsetHighSetToZero() { - using(new PreAllocatedOverlapped((_, __, ___) => { }, new object(), new byte[256])) {} + using (new PreAllocatedOverlapped((_, __, ___) => { }, new object(), new byte[256])) { } + using (PreAllocatedOverlapped.UnsafeCreate((_, __, ___) => { }, new object(), new byte[256])) { } } } diff --git a/src/mono/System.Private.CoreLib/src/System/Threading/PreAllocatedOverlapped.cs b/src/mono/System.Private.CoreLib/src/System/Threading/PreAllocatedOverlapped.cs index d4a0daeff29c78..a1d410e30bd53c 100644 --- a/src/mono/System.Private.CoreLib/src/System/Threading/PreAllocatedOverlapped.cs +++ b/src/mono/System.Private.CoreLib/src/System/Threading/PreAllocatedOverlapped.cs @@ -5,8 +5,10 @@ namespace System.Threading { public sealed class PreAllocatedOverlapped : System.IDisposable { - [System.CLSCompliantAttribute(false)] - public PreAllocatedOverlapped(System.Threading.IOCompletionCallback callback, object? state, object? pinData) { } + [CLSCompliantAttribute(false)] + public PreAllocatedOverlapped(IOCompletionCallback callback, object? state, object? pinData) { } + [CLSCompliantAttribute(false)] + public static PreAllocatedOverlapped UnsafeCreate(IOCompletionCallback callback, object? state, object? pinData) => new PreAllocatedOverlapped(callback, state, pinData); public void Dispose() { } internal bool IsUserObject(byte[]? buffer) => false; }