Add PreAllocatedOverlapped.UnsafeCreate / UnsafeAllocateNativeOverlapped (#53196)
authorStephen Toub <stoub@microsoft.com>
Tue, 25 May 2021 21:49:10 +0000 (17:49 -0400)
committerGitHub <noreply@github.com>
Tue, 25 May 2021 21:49:10 +0000 (17:49 -0400)
12 files changed:
src/coreclr/System.Private.CoreLib/src/System/Threading/ClrThreadPoolBoundHandle.cs
src/coreclr/System.Private.CoreLib/src/System/Threading/ClrThreadPoolBoundHandleOverlapped.cs
src/coreclr/System.Private.CoreLib/src/System/Threading/ClrThreadPoolPreAllocatedOverlapped.cs
src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.Windows.cs
src/libraries/System.Private.CoreLib/src/System/IO/Strategies/AsyncWindowsFileStreamStrategy.ValueTaskSource.cs
src/libraries/System.Private.CoreLib/src/System/IO/Strategies/Net5CompatFileStreamStrategy.Windows.cs
src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolBoundHandle.PlatformNotSupported.cs
src/libraries/System.Threading.Overlapped/ref/System.Threading.Overlapped.cs
src/libraries/System.Threading.Overlapped/tests/ThreadPoolBoundHandle_AllocateNativeOverlappedTests.cs
src/libraries/System.Threading.Overlapped/tests/ThreadPoolBoundHandle_IntegrationTests.cs
src/libraries/System.Threading.Overlapped/tests/ThreadPoolBoundHandle_PreAllocatedOverlappedTests.cs
src/mono/System.Private.CoreLib/src/System/Threading/PreAllocatedOverlapped.cs

index eff292e..b188c8f 100644 (file)
@@ -124,14 +124,65 @@ namespace System.Threading
         ///     This method was called after the <see cref="ThreadPoolBoundHandle"/> was disposed.
         /// </exception>
         [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);
+
+        /// <summary>
+        ///     Returns an unmanaged pointer to a <see cref="NativeOverlapped"/> 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.
+        /// </summary>
+        /// <param name="callback">
+        ///     An <see cref="IOCompletionCallback"/> delegate that represents the callback method
+        ///     invoked when the asynchronous I/O operation completes.
+        /// </param>
+        /// <param name="state">
+        ///     A user-provided object that distinguishes this <see cref="NativeOverlapped"/> from other
+        ///     <see cref="NativeOverlapped"/> instances. Can be <see langword="null"/>.
+        /// </param>
+        /// <param name="pinData">
+        ///     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 <see langword="null"/>.
+        /// </param>
+        /// <returns>
+        ///     An unmanaged pointer to a <see cref="NativeOverlapped"/> structure.
+        /// </returns>
+        /// <remarks>
+        ///     <para>
+        ///         The unmanaged pointer returned by this method can be passed to the operating system in
+        ///         overlapped I/O operations. The <see cref="NativeOverlapped"/> structure is fixed in
+        ///         physical memory until <see cref="FreeNativeOverlapped(NativeOverlapped*)"/> is called.
+        ///     </para>
+        ///     <para>
+        ///         The buffer or buffers specified in <paramref name="pinData"/> must be the same as those passed
+        ///         to the unmanaged operating system function that performs the asynchronous I/O.
+        ///     </para>
+        ///     <para>
+        ///         <see cref="ExecutionContext"/> is not flowed to the invocation of the callback.
+        ///     </para>
+        ///     <note>
+        ///         The buffers specified in <paramref name="pinData"/> are pinned for the duration of
+        ///         the I/O operation.
+        ///     </note>
+        /// </remarks>
+        /// <exception cref="ArgumentNullException">
+        ///     <paramref name="callback"/> is <see langword="null"/>.
+        /// </exception>
+        /// <exception cref="ObjectDisposedException">
+        ///     This method was called after the <see cref="ThreadPoolBoundHandle"/> was disposed.
+        /// </exception>
+        [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;
         }
index bc2d082..3fe953d 100644 (file)
@@ -6,29 +6,32 @@ namespace System.Threading
     /// <summary>
     /// Overlapped subclass adding data needed by ThreadPoolBoundHandle.
     /// </summary>
-    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);
 
index d856d1d..560f2e4 100644 (file)
@@ -47,12 +47,56 @@ namespace System.Threading
         ///     This method was called after the <see cref="ThreadPoolBoundHandle"/> was disposed.
         /// </exception>
         [CLSCompliant(false)]
-        public PreAllocatedOverlapped(IOCompletionCallback callback, object? state, object? pinData)
+        public PreAllocatedOverlapped(IOCompletionCallback callback, object? state, object? pinData) :
+            this(callback, state, pinData, flowExecutionContext: true)
+        {
+        }
+
+        /// <summary>
+        ///     Initializes a new instance of the <see cref="PreAllocatedOverlapped"/> 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.
+        /// </summary>
+        /// <param name="callback">
+        ///     An <see cref="IOCompletionCallback"/> delegate that represents the callback method
+        ///     invoked when each asynchronous I/O operation completes.
+        /// </param>
+        /// <param name="state">
+        ///     A user-provided object that distinguishes <see cref="NativeOverlapped"/> instance produced from this
+        ///     object from other <see cref="NativeOverlapped"/> instances. Can be <see langword="null"/>.
+        /// </param>
+        /// <param name="pinData">
+        ///     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 <see langword="null"/>.
+        /// </param>
+        /// <remarks>
+        ///     The new <see cref="PreAllocatedOverlapped"/> instance can be passed to
+        ///     <see cref="ThreadPoolBoundHandle.AllocateNativeOverlapped(PreAllocatedOverlapped)"/>, to produce
+        ///     a <see cref="NativeOverlapped"/> instance that can be passed to the operating system in overlapped
+        ///     I/O operations.  A single <see cref="PreAllocatedOverlapped"/> instance can only be used for
+        ///     a single native I/O operation at a time.  However, the state stored in the <see cref="PreAllocatedOverlapped"/>
+        ///     instance can be reused for subsequent native operations. ExecutionContext is not flowed to the invocation
+        ///     of the callback.
+        ///     <note>
+        ///         The buffers specified in <paramref name="pinData"/> are pinned until <see cref="Dispose"/> is called.
+        ///     </note>
+        /// </remarks>
+        /// <exception cref="ArgumentNullException">
+        ///     <paramref name="callback"/> is <see langword="null"/>.
+        /// </exception>
+        /// <exception cref="ObjectDisposedException">
+        ///     This method was called after the <see cref="ThreadPoolBoundHandle"/> was disposed.
+        /// </exception>
+        [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()
index 7bca849..d79d340 100644 (file)
@@ -83,20 +83,9 @@ namespace System.Net.Sockets
         [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}");
         }
index 1a7fcbe..f1153a5 100644 (file)
@@ -32,7 +32,7 @@ namespace System.IO.Strategies
             {
                 _strategy = strategy;
                 _source.RunContinuationsAsynchronously = true;
-                _preallocatedOverlapped = new PreAllocatedOverlapped(s_ioCallback, this, null);
+                _preallocatedOverlapped = PreAllocatedOverlapped.UnsafeCreate(s_ioCallback, this, null);
             }
 
             internal void Dispose()
index 8b51b25..ceb9bcc 100644 (file)
@@ -544,7 +544,7 @@ namespace System.IO.Strategies
             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)
index 3e34c04..e7da142 100644 (file)
@@ -34,6 +34,10 @@ namespace System.Threading
         }
 
         [CLSCompliant(false)]
+        public unsafe NativeOverlapped* UnsafeAllocateNativeOverlapped(IOCompletionCallback callback, object? state, object? pinData) =>
+            AllocateNativeOverlapped(callback, state, pinData);
+
+        [CLSCompliant(false)]
         public unsafe NativeOverlapped* AllocateNativeOverlapped(PreAllocatedOverlapped preAllocated)
         {
             if (preAllocated == null)
index 634e169..9f9312c 100644 (file)
@@ -49,6 +49,8 @@ namespace System.Threading
         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 @@ namespace System.Threading
         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; }
     }
 }
index ca73c0d..5621e8a 100644 (file)
@@ -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<ArgumentNullException>("callback", () =>
-            {
-                handle.AllocateNativeOverlapped(null, new object(), new byte[256]);
-            });
+            AssertExtensions.Throws<ArgumentNullException>("callback", () => handle.AllocateNativeOverlapped(null, new object(), new byte[256]));
+            AssertExtensions.Throws<ArgumentNullException>("callback", () => handle.UnsafeAllocateNativeOverlapped(null, new object(), new byte[256]));
         }
     }
 
@@ -26,12 +24,9 @@ public partial class ThreadPoolBoundHandleTests
     [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<ArgumentNullException>("preAllocated", () =>
-            {
-                handle.AllocateNativeOverlapped((PreAllocatedOverlapped)null);
-            });
+            AssertExtensions.Throws<ArgumentNullException>("preAllocated", () => handle.AllocateNativeOverlapped((PreAllocatedOverlapped)null));
         }
     }
 
@@ -39,12 +34,14 @@ public partial class ThreadPoolBoundHandleTests
     [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 partial class ThreadPoolBoundHandleTests
     [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 partial class ThreadPoolBoundHandleTests
     [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 partial class ThreadPoolBoundHandleTests
     [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<ArgumentException>(null, () => handle.AllocateNativeOverlapped((_, __, ___) => { }, new object(), new NonBlittableType() { s = "foo" }));
+            AssertExtensions.Throws<ArgumentException>(null, () => handle.UnsafeAllocateNativeOverlapped((_, __, ___) => { }, new object(), new NonBlittableType() { s = "foo" }));
         }
     }
 
@@ -91,12 +93,14 @@ public partial class ThreadPoolBoundHandleTests
     [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 partial class ThreadPoolBoundHandleTests
     [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 partial class ThreadPoolBoundHandleTests
     [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<ArgumentException>(null, () => handle.AllocateNativeOverlapped((_, __, ___) => { }, new object(), array));
+            AssertExtensions.Throws<ArgumentException>(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 partial class ThreadPoolBoundHandleTests
         }
     }
 
-    [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 partial class ThreadPoolBoundHandleTests
         }
     }
 
-    [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 partial class ThreadPoolBoundHandleTests
         }
     }
 
-    [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 partial class ThreadPoolBoundHandleTests
         ThreadPoolBoundHandle handle = CreateThreadPoolBoundHandle();
         handle.Dispose();
 
-        Assert.Throws<ObjectDisposedException>(() =>
-        {
-            handle.AllocateNativeOverlapped((_, __, ___) => { }, new object(), new byte[256]);
-        });
+        Assert.Throws<ObjectDisposedException>(() => handle.AllocateNativeOverlapped((_, __, ___) => { }, new object(), new byte[256]));
+        Assert.Throws<ObjectDisposedException>(() => 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<ObjectDisposedException>(() =>
-            {
-                handle.AllocateNativeOverlapped(preAlloc);
-            });
+            Assert.Throws<ObjectDisposedException>(() => 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<ObjectDisposedException>(() =>
-        {
-            handle.AllocateNativeOverlapped(preAlloc);
-        });
+        Assert.Throws<ObjectDisposedException>(() => 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<ArgumentException>("preAllocated", () =>
-                {
-                    handle.AllocateNativeOverlapped(preAlloc);
-                });
+            NativeOverlapped* overlapped = handle.AllocateNativeOverlapped(preAlloc);
 
-                handle.FreeNativeOverlapped(overlapped);
-            }
+            AssertExtensions.Throws<ArgumentException>("preAllocated", () => handle.AllocateNativeOverlapped(preAlloc));
+
+            handle.FreeNativeOverlapped(overlapped);
         }
     }
 }
index 008bf7a..f7a3fff 100644 (file)
@@ -177,10 +177,13 @@ public partial class ThreadPoolBoundHandleTests
         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 partial class ThreadPoolBoundHandleTests
             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 partial class ThreadPoolBoundHandleTests
         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<int> asyncLocal = new AsyncLocal<int>();
+        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)
index ee8ccd3..8517a5c 100644 (file)
@@ -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<ArgumentNullException>("callback", () =>
-        {
-            new PreAllocatedOverlapped(null, new object(), new byte[256]);
-        });
+        AssertExtensions.Throws<ArgumentNullException>("callback", () => new PreAllocatedOverlapped(null, new object(), new byte[256]));
+        AssertExtensions.Throws<ArgumentNullException>("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 partial class ThreadPoolBoundHandleTests
     [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 partial class ThreadPoolBoundHandleTests
     public unsafe void PreAllocatedOverlapped_NonBlittableTypeAsPinData_Throws()
     {
         AssertExtensions.Throws<ArgumentException>(null, () => new PreAllocatedOverlapped((_, __, ___) => { }, new object(), new NonBlittableType() { s = "foo" }));
+        AssertExtensions.Throws<ArgumentException>(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 partial class ThreadPoolBoundHandleTests
     [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<ArgumentException>(null, () => new PreAllocatedOverlapped((_, __, ___) => { }, new object(), array));
+        AssertExtensions.Throws<ArgumentException>(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 partial class ThreadPoolBoundHandleTests
     [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])) { }
     }
 }
index d4a0dae..a1d410e 100644 (file)
@@ -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;
     }