From 8398e5e6c53b564d628888965b4cda8625be059b Mon Sep 17 00:00:00 2001 From: Egor Bogatov Date: Tue, 3 Sep 2019 23:22:04 +0300 Subject: [PATCH] [netcore] Use CoreRT's Overlapped (mono/mono#16106) Made it Nullability friendly Interop.MemFree -> Marshal.FreeHGlobal Interop.MemAlloc -> Marshal.AllocHGlobal Fixes https://github.com/mono/mono/issues/15308 Fixes https://github.com/mono/mono/issues/15310 Also, copy ThreadPoolBoundHandle.PlatformNotSupported.cs Commit migrated from https://github.com/mono/mono/commit/625af20cefcc3944fa10f341329ff40fc9e27694 --- src/mono/netcore/CoreFX.issues.rsp | 18 +- .../netcore/System.Private.CoreLib/resources/SR.cs | 1 + .../src/System.Threading/Overlapped.cs | 342 +++++++++++++++++++-- .../src/System.Threading/ThreadPoolBoundHandle.cs | 74 ++++- 4 files changed, 375 insertions(+), 60 deletions(-) diff --git a/src/mono/netcore/CoreFX.issues.rsp b/src/mono/netcore/CoreFX.issues.rsp index 1b3d2b8..b797846 100644 --- a/src/mono/netcore/CoreFX.issues.rsp +++ b/src/mono/netcore/CoreFX.issues.rsp @@ -676,9 +676,6 @@ ## System.Data.SqlClient.Tests #################################################################### -# Test does not run when included --nomethod System.Data.SqlClient.Tests.AADAccessTokenTest.InvalidCombinationOfAccessToken - # Hangs and then fails... All async tests display the same failure # https://github.com/mono/mono/issues/15181 -nomethod System.Data.SqlClient.Tests.DiagnosticTest.ExecuteNonQueryAsyncTest @@ -765,21 +762,10 @@ # Overflow Exception # https://github.com/mono/mono/issues/15197 --nomethod OverlappedTests.PropertyTest2 - -# NRE in Overlapped.Pack -# https://github.com/mono/mono/issues/15308 --nomethod OverlappedTests.PackNegTest --nomethod OverlappedTests.PackNegTest1 - -# NRE at Overlapped.Unpack (similar to pack) -# https://github.com/mono/mono/issues/15310 --nomethod OverlappedTests.UnPackTest - -# NRE may be similar to above # https://github.com/mono/mono/issues/15311 +# These tests should be ignored on x64 in dotnet/corefx +-nomethod OverlappedTests.PropertyTest2 -nomethod OverlappedTests.PropertyTest3 --nomethod OverlappedTests.PropertyTest1 # NRE # https://github.com/mono/mono/issues/15312 diff --git a/src/mono/netcore/System.Private.CoreLib/resources/SR.cs b/src/mono/netcore/System.Private.CoreLib/resources/SR.cs index 8c7afd9..0dd5f5a 100644 --- a/src/mono/netcore/System.Private.CoreLib/resources/SR.cs +++ b/src/mono/netcore/System.Private.CoreLib/resources/SR.cs @@ -1220,4 +1220,5 @@ partial class SR public const string Arg_MustBeRuntimeAssembly = "Object must be of type RuntimeAssembly."; public const string InvalidOperation_SpanOverlappedOperation = "This operation is invalid on overlapping buffers."; public const string SetterHasNoParams = "Setter must have parameters."; + public const string NotSupported_Overlapped = "This API is not supported on this platform."; } diff --git a/src/mono/netcore/System.Private.CoreLib/src/System.Threading/Overlapped.cs b/src/mono/netcore/System.Private.CoreLib/src/System.Threading/Overlapped.cs index 11c9570..3212071 100644 --- a/src/mono/netcore/System.Private.CoreLib/src/System.Threading/Overlapped.cs +++ b/src/mono/netcore/System.Private.CoreLib/src/System.Threading/Overlapped.cs @@ -2,33 +2,319 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Diagnostics; +using System.Runtime.InteropServices; + namespace System.Threading { - public class Overlapped - { - public Overlapped() { } - [System.ObsoleteAttribute("This constructor is not 64-bit compatible. Use the constructor that takes an IntPtr for the event handle. https://go.microsoft.com/fwlink/?linkid=14202")] - public Overlapped(int offsetLo, int offsetHi, int hEvent, System.IAsyncResult ar) { } - public Overlapped(int offsetLo, int offsetHi, System.IntPtr hEvent, System.IAsyncResult ar) { } - public System.IAsyncResult AsyncResult { get { throw null; } set { } } - [System.ObsoleteAttribute("This property is not 64-bit compatible. Use EventHandleIntPtr instead. https://go.microsoft.com/fwlink/?linkid=14202")] - public int EventHandle { get { throw null; } set { } } - public System.IntPtr EventHandleIntPtr { get { throw null; } set { } } - public int OffsetHigh { get { throw null; } set { } } - public int OffsetLow { get { throw null; } set { } } - [System.CLSCompliantAttribute(false)] - public static unsafe void Free(System.Threading.NativeOverlapped* nativeOverlappedPtr) { } - [System.CLSCompliantAttribute(false)] - [System.ObsoleteAttribute("This method is not safe. Use Pack (iocb, userData) instead. https://go.microsoft.com/fwlink/?linkid=14202")] - public unsafe System.Threading.NativeOverlapped* Pack(System.Threading.IOCompletionCallback iocb) { throw null; } - [System.CLSCompliantAttribute(false)] - public unsafe System.Threading.NativeOverlapped* Pack(System.Threading.IOCompletionCallback iocb, object userData) { throw null; } - [System.CLSCompliantAttribute(false)] - public static unsafe System.Threading.Overlapped Unpack(System.Threading.NativeOverlapped* nativeOverlappedPtr) { throw null; } - [System.CLSCompliantAttribute(false)] - [System.ObsoleteAttribute("This method is not safe. Use UnsafePack (iocb, userData) instead. https://go.microsoft.com/fwlink/?linkid=14202")] - public unsafe System.Threading.NativeOverlapped* UnsafePack(System.Threading.IOCompletionCallback iocb) { throw null; } - [System.CLSCompliantAttribute(false)] - public unsafe System.Threading.NativeOverlapped* UnsafePack(System.Threading.IOCompletionCallback iocb, object userData) { throw null; } - } -} + #region class _IOCompletionCallback + + internal unsafe class _IOCompletionCallback + { + private IOCompletionCallback _ioCompletionCallback; + private ExecutionContext _executionContext; + private uint _errorCode; // Error code + private uint _numBytes; // No. of bytes transferred + private NativeOverlapped* _pNativeOverlapped; + + internal _IOCompletionCallback(IOCompletionCallback ioCompletionCallback, ExecutionContext executionContext) + { + _ioCompletionCallback = ioCompletionCallback; + _executionContext = executionContext; + } + + // Context callback: same sig for SendOrPostCallback and ContextCallback + internal static ContextCallback s_ccb = new ContextCallback(IOCompletionCallback_Context); + private static void IOCompletionCallback_Context(object? state) + { + _IOCompletionCallback helper = (_IOCompletionCallback)state!; + Debug.Assert(helper != null, "_IOCompletionCallback cannot be null"); + helper._ioCompletionCallback(helper._errorCode, helper._numBytes, helper._pNativeOverlapped); + } + + //TODO: call from ThreadPool + internal static unsafe void PerformIOCompletionCallback(uint errorCode, uint numBytes, NativeOverlapped* pNativeOverlapped) + { + do + { + OverlappedData overlapped = OverlappedData.GetOverlappedFromNative(pNativeOverlapped); + + if (overlapped._callback is IOCompletionCallback iocb) + { + // We got here because of UnsafePack (or) Pack with EC flow suppressed + iocb(errorCode, numBytes, pNativeOverlapped); + } + else + { + // We got here because of Pack + var helper = (_IOCompletionCallback)overlapped._callback; + helper._errorCode = errorCode; + helper._numBytes = numBytes; + helper._pNativeOverlapped = pNativeOverlapped; + ExecutionContext.Run(helper._executionContext, s_ccb, helper); + } + + //Quickly check the VM again, to see if a packet has arrived. + //OverlappedData.CheckVMForIOPacket(out pOVERLAP, out errorCode, out numBytes); + pNativeOverlapped = null; + } while (pNativeOverlapped != null); + } + } + + #endregion class _IOCompletionCallback + + #region class OverlappedData + + internal unsafe sealed class OverlappedData + { + internal IAsyncResult _asyncResult; + internal object _callback; // IOCompletionCallback or _IOCompletionCallback + internal Overlapped _overlapped; + private object _userObject; + private NativeOverlapped * _pNativeOverlapped; + private IntPtr _eventHandle; + private int _offsetLow; + private int _offsetHigh; + private GCHandle[] _pinnedData; + + internal ref IAsyncResult AsyncResult => ref _asyncResult; + + internal ref int OffsetLow => ref (_pNativeOverlapped != null) ? ref _pNativeOverlapped->OffsetLow : ref _offsetLow; + internal ref int OffsetHigh => ref (_pNativeOverlapped != null) ? ref _pNativeOverlapped->OffsetHigh : ref _offsetHigh; + internal ref IntPtr EventHandle => ref (_pNativeOverlapped != null) ? ref _pNativeOverlapped->EventHandle : ref _eventHandle; + + internal unsafe NativeOverlapped* Pack(IOCompletionCallback iocb, object userData) + { + if (_pNativeOverlapped != null) + { + throw new InvalidOperationException(SR.InvalidOperation_Overlapped_Pack); + } + + if (iocb != null) + { + ExecutionContext ec = ExecutionContext.Capture(); + _callback = (ec != null && !ec.IsDefault) ? new _IOCompletionCallback(iocb, ec) : (object)iocb; + } + else + { + _callback = null; + } + _userObject = userData; + return AllocateNativeOverlapped(); + } + + internal unsafe NativeOverlapped* UnsafePack(IOCompletionCallback iocb, object userData) + { + if (_pNativeOverlapped != null) + { + throw new InvalidOperationException(SR.InvalidOperation_Overlapped_Pack); + } + _userObject = userData; + _callback = iocb; + return AllocateNativeOverlapped(); + } + + private unsafe NativeOverlapped* AllocateNativeOverlapped() + { + Debug.Assert(_pinnedData == null); + + bool success = false; + try + { + if (_userObject != null) + { + if (_userObject.GetType() == typeof(object[])) + { + object[] objArray = (object[])_userObject; + + _pinnedData = new GCHandle[objArray.Length]; + for (int i = 0; i < objArray.Length; i++) + { + _pinnedData[i] = GCHandle.Alloc(objArray[i], GCHandleType.Pinned); + } + } + else + { + _pinnedData = new GCHandle[1]; + _pinnedData[0] = GCHandle.Alloc(_userObject, GCHandleType.Pinned); + } + } + + //CORERT: NativeOverlapped* pNativeOverlapped = (NativeOverlapped*)Interop.MemAlloc((UIntPtr)(sizeof(NativeOverlapped) + sizeof(GCHandle))); + NativeOverlapped* pNativeOverlapped = (NativeOverlapped*) Marshal.AllocHGlobal((IntPtr) (sizeof(NativeOverlapped) + sizeof(GCHandle))); + + *(GCHandle*)(pNativeOverlapped + 1) = default(GCHandle); + _pNativeOverlapped = pNativeOverlapped; + + _pNativeOverlapped->InternalLow = default; + _pNativeOverlapped->InternalHigh = default; + _pNativeOverlapped->OffsetLow = _offsetLow; + _pNativeOverlapped->OffsetHigh = _offsetHigh; + _pNativeOverlapped->EventHandle = _eventHandle; + + *(GCHandle*)(_pNativeOverlapped + 1) = GCHandle.Alloc(this); + + success = true; + return _pNativeOverlapped; + } + finally + { + if (!success) + FreeNativeOverlapped(); + } + } + + internal static unsafe void FreeNativeOverlapped(NativeOverlapped* nativeOverlappedPtr) + { + OverlappedData overlappedData = OverlappedData.GetOverlappedFromNative(nativeOverlappedPtr); + overlappedData.FreeNativeOverlapped(); + } + + private void FreeNativeOverlapped() + { + if (_pinnedData != null) + { + for (int i = 0; i < _pinnedData.Length; i++) + { + if (_pinnedData[i].IsAllocated) + { + _pinnedData[i].Free(); + } + } + _pinnedData = null; + } + + if (_pNativeOverlapped != null) + { + GCHandle handle = *(GCHandle*)(_pNativeOverlapped + 1); + if (handle.IsAllocated) + handle.Free(); + + Marshal.FreeHGlobal((IntPtr)_pNativeOverlapped); + //CORERT: Interop.MemFree((IntPtr)_pNativeOverlapped); + _pNativeOverlapped = null; + } + } + + internal static unsafe OverlappedData GetOverlappedFromNative(NativeOverlapped* pNativeOverlapped) + { + GCHandle handle = *(GCHandle*)(pNativeOverlapped + 1); + return (OverlappedData)handle.Target; + } + } + + #endregion class OverlappedData + + #region class Overlapped + + public class Overlapped + { + private OverlappedData _overlappedData; + + public Overlapped() + { + _overlappedData = new OverlappedData(); + _overlappedData._overlapped = this; + } + + public Overlapped(int offsetLo, int offsetHi, IntPtr hEvent, IAsyncResult ar) : this() + { + _overlappedData.OffsetLow = offsetLo; + _overlappedData.OffsetHigh = offsetHi; + _overlappedData.EventHandle = hEvent; + _overlappedData.AsyncResult = ar; + } + + [Obsolete("This constructor is not 64-bit compatible. Use the constructor that takes an IntPtr for the event handle. http://go.microsoft.com/fwlink/?linkid=14202")] + public Overlapped(int offsetLo, int offsetHi, int hEvent, IAsyncResult ar) : this(offsetLo, offsetHi, new IntPtr(hEvent), ar) + { + } + + public IAsyncResult AsyncResult + { + get { return _overlappedData.AsyncResult; } + set { _overlappedData.AsyncResult = value; } + } + + public int OffsetLow + { + get { return _overlappedData.OffsetLow; } + set { _overlappedData.OffsetLow = value; } + } + + public int OffsetHigh + { + get { return _overlappedData.OffsetHigh; } + set { _overlappedData.OffsetHigh = value; } + } + + [Obsolete("This property is not 64-bit compatible. Use EventHandleIntPtr instead. http://go.microsoft.com/fwlink/?linkid=14202")] + public int EventHandle + { + get { return EventHandleIntPtr.ToInt32(); } + set { EventHandleIntPtr = new IntPtr(value); } + } + + public IntPtr EventHandleIntPtr + { + get { return _overlappedData.EventHandle; } + set { _overlappedData.EventHandle = value; } + } + + /*==================================================================== + * Packs a managed overlapped class into native Overlapped struct. + * Roots the iocb and stores it in the ReservedCOR field of native Overlapped + * Pins the native Overlapped struct and returns the pinned index. + ====================================================================*/ + [Obsolete("This method is not safe. Use Pack (iocb, userData) instead. http://go.microsoft.com/fwlink/?linkid=14202")] + [CLSCompliant(false)] + public unsafe NativeOverlapped* Pack(IOCompletionCallback iocb) + { + return Pack(iocb, null); + } + + [CLSCompliant(false)] + public unsafe NativeOverlapped* Pack(IOCompletionCallback iocb, object userData) + { + return _overlappedData.Pack(iocb, userData); + } + + [Obsolete("This method is not safe. Use UnsafePack (iocb, userData) instead. http://go.microsoft.com/fwlink/?linkid=14202")] + [CLSCompliant(false)] + public unsafe NativeOverlapped* UnsafePack(IOCompletionCallback iocb) + { + return UnsafePack(iocb, null); + } + + [CLSCompliant(false)] + public unsafe NativeOverlapped* UnsafePack(IOCompletionCallback iocb, object userData) + { + return _overlappedData.UnsafePack(iocb, userData); + } + + /*==================================================================== + * Unpacks an unmanaged native Overlapped struct. + * Unpins the native Overlapped struct + ====================================================================*/ + [CLSCompliant(false)] + public static unsafe Overlapped Unpack(NativeOverlapped* nativeOverlappedPtr) + { + if (nativeOverlappedPtr == null) + throw new ArgumentNullException(nameof(nativeOverlappedPtr)); + + return OverlappedData.GetOverlappedFromNative(nativeOverlappedPtr)._overlapped; + } + + [CLSCompliant(false)] + public static unsafe void Free(NativeOverlapped* nativeOverlappedPtr) + { + if (nativeOverlappedPtr == null) + throw new ArgumentNullException(nameof(nativeOverlappedPtr)); + + OverlappedData.GetOverlappedFromNative(nativeOverlappedPtr)._overlapped._overlappedData = null; + OverlappedData.FreeNativeOverlapped(nativeOverlappedPtr); + } + } + + #endregion class Overlapped +} \ No newline at end of file diff --git a/src/mono/netcore/System.Private.CoreLib/src/System.Threading/ThreadPoolBoundHandle.cs b/src/mono/netcore/System.Private.CoreLib/src/System.Threading/ThreadPoolBoundHandle.cs index becb0f7..940ce28 100644 --- a/src/mono/netcore/System.Private.CoreLib/src/System.Threading/ThreadPoolBoundHandle.cs +++ b/src/mono/netcore/System.Private.CoreLib/src/System.Threading/ThreadPoolBoundHandle.cs @@ -2,21 +2,63 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Runtime.InteropServices; + namespace System.Threading { - public sealed partial class ThreadPoolBoundHandle : System.IDisposable - { - internal ThreadPoolBoundHandle() { } - public System.Runtime.InteropServices.SafeHandle Handle { get { throw null; } } - [System.CLSCompliantAttribute(false)] - public unsafe System.Threading.NativeOverlapped* AllocateNativeOverlapped(System.Threading.IOCompletionCallback callback, object state, object pinData) { throw null; } - [System.CLSCompliantAttribute(false)] - public unsafe System.Threading.NativeOverlapped* AllocateNativeOverlapped(System.Threading.PreAllocatedOverlapped preAllocated) { throw null; } - public static System.Threading.ThreadPoolBoundHandle BindHandle(System.Runtime.InteropServices.SafeHandle handle) { throw null; } - public void Dispose() { } - [System.CLSCompliantAttribute(false)] - public unsafe void FreeNativeOverlapped(System.Threading.NativeOverlapped* overlapped) { } - [System.CLSCompliantAttribute(false)] - public static unsafe object GetNativeOverlappedState(System.Threading.NativeOverlapped* overlapped) { throw null; } - } -} + public sealed class ThreadPoolBoundHandle : IDisposable + { + public SafeHandle Handle => null; + + private ThreadPoolBoundHandle() { } + + public static ThreadPoolBoundHandle BindHandle(SafeHandle handle) + { + if (handle == null) + throw new ArgumentNullException(nameof(handle)); + + if (handle.IsClosed || handle.IsInvalid) + throw new ArgumentException(SR.Argument_InvalidHandle, nameof(handle)); + + throw new PlatformNotSupportedException(SR.NotSupported_Overlapped); + } + + [CLSCompliant(false)] + public unsafe NativeOverlapped* AllocateNativeOverlapped(IOCompletionCallback callback, object state, object pinData) + { + if (callback == null) + throw new ArgumentNullException(nameof(callback)); + + throw new PlatformNotSupportedException(SR.NotSupported_Overlapped); + } + + [CLSCompliant(false)] + public unsafe NativeOverlapped* AllocateNativeOverlapped(PreAllocatedOverlapped preAllocated) + { + if (preAllocated == null) + throw new ArgumentNullException(nameof(preAllocated)); + + throw new PlatformNotSupportedException(SR.NotSupported_Overlapped); + } + + [CLSCompliant(false)] + public unsafe void FreeNativeOverlapped(NativeOverlapped* overlapped) + { + if (overlapped == null) + throw new ArgumentNullException(nameof(overlapped)); + + throw new PlatformNotSupportedException(SR.NotSupported_Overlapped); + } + + [CLSCompliant(false)] + public static unsafe object GetNativeOverlappedState(NativeOverlapped* overlapped) + { + if (overlapped == null) + throw new ArgumentNullException(nameof(overlapped)); + + throw new PlatformNotSupportedException(SR.NotSupported_Overlapped); + } + + public void Dispose() { } + } +} \ No newline at end of file -- 2.7.4