diff options
10 files changed, 519 insertions, 615 deletions
diff --git a/src/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/System.Private.CoreLib/System.Private.CoreLib.csproj index cd84eb2e31..595ed87a50 100644 --- a/src/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -260,7 +260,7 @@ <Compile Include="$(BclSourcesRoot)\System\Threading\Thread.cs" /> <Compile Include="$(BclSourcesRoot)\System\Threading\ThreadPool.CoreCLR.cs" /> <Compile Include="$(BclSourcesRoot)\System\Threading\Timer.CoreCLR.cs" /> - <Compile Include="$(BclSourcesRoot)\System\Threading\WaitHandle.cs" /> + <Compile Include="$(BclSourcesRoot)\System\Threading\WaitHandle.CoreCLR.cs" /> <Compile Include="$(BclSourcesRoot)\System\Type.CoreCLR.cs" /> <Compile Include="$(BclSourcesRoot)\System\TypedReference.cs" /> <Compile Include="$(BclSourcesRoot)\System\TypeLoadException.cs" /> diff --git a/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems b/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems index 6d9eea8c6c..97c12067f1 100644 --- a/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems +++ b/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems @@ -840,6 +840,7 @@ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\Timeout.cs" /> <Compile Include="$(MSBuildThisFileDirectory)System\Threading\TimeoutHelper.cs" /> <Compile Include="$(MSBuildThisFileDirectory)System\Threading\Timer.cs" /> + <Compile Include="$(MSBuildThisFileDirectory)System\Threading\WaitHandle.cs" /> <Compile Include="$(MSBuildThisFileDirectory)System\Threading\WaitHandleCannotBeOpenedException.cs" /> <Compile Include="$(MSBuildThisFileDirectory)System\ThreadStaticAttribute.cs" /> <Compile Include="$(MSBuildThisFileDirectory)System\ThrowHelper.cs" /> diff --git a/src/System.Private.CoreLib/shared/System/Threading/EventWaitHandle.Windows.cs b/src/System.Private.CoreLib/shared/System/Threading/EventWaitHandle.Windows.cs index 0d41e2a88d..62b3e4f9fc 100644 --- a/src/System.Private.CoreLib/shared/System/Threading/EventWaitHandle.Windows.cs +++ b/src/System.Private.CoreLib/shared/System/Threading/EventWaitHandle.Windows.cs @@ -76,7 +76,7 @@ namespace System.Threading public bool Reset() { - bool res = Interop.Kernel32.ResetEvent(_waitHandle); + bool res = Interop.Kernel32.ResetEvent(SafeWaitHandle); if (!res) throw Win32Marshal.GetExceptionForLastWin32Error(); return res; @@ -84,7 +84,7 @@ namespace System.Threading public bool Set() { - bool res = Interop.Kernel32.SetEvent(_waitHandle); + bool res = Interop.Kernel32.SetEvent(SafeWaitHandle); if (!res) throw Win32Marshal.GetExceptionForLastWin32Error(); return res; diff --git a/src/System.Private.CoreLib/shared/System/Threading/Mutex.Windows.cs b/src/System.Private.CoreLib/shared/System/Threading/Mutex.Windows.cs index c40ab94dc3..26eb4fe5ab 100644 --- a/src/System.Private.CoreLib/shared/System/Threading/Mutex.Windows.cs +++ b/src/System.Private.CoreLib/shared/System/Threading/Mutex.Windows.cs @@ -90,7 +90,7 @@ namespace System.Threading // in a Mutex's ACL is MUTEX_ALL_ACCESS (0x1F0001). public void ReleaseMutex() { - if (!Interop.Kernel32.ReleaseMutex(_waitHandle)) + if (!Interop.Kernel32.ReleaseMutex(SafeWaitHandle)) { throw new ApplicationException(SR.Arg_SynchronizationLockException); } diff --git a/src/System.Private.CoreLib/shared/System/Threading/WaitHandle.cs b/src/System.Private.CoreLib/shared/System/Threading/WaitHandle.cs new file mode 100644 index 0000000000..d90db240a2 --- /dev/null +++ b/src/System.Private.CoreLib/shared/System/Threading/WaitHandle.cs @@ -0,0 +1,437 @@ +// Licensed to the .NET Foundation under one or more agreements. +// 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 Microsoft.Win32.SafeHandles; + +namespace System.Threading +{ + public abstract partial class WaitHandle : MarshalByRefObject, IDisposable + { + internal const int MaxWaitHandles = 64; + + protected static readonly IntPtr InvalidHandle = new IntPtr(-1); + + // IMPORTANT: + // - Do not add or rearrange fields as the EE depends on this layout. + + private SafeWaitHandle _waitHandle; + + [ThreadStatic] + private static SafeWaitHandle[] t_safeWaitHandlesForRent; + + private protected enum OpenExistingResult + { + Success, + NameNotFound, + PathNotFound, + NameInvalid + } + + // The wait result values below match Win32 wait result codes (WAIT_OBJECT_0, + // WAIT_ABANDONED, WAIT_TIMEOUT). + + // Successful wait on first object. When waiting for multiple objects the + // return value is (WaitSuccess + waitIndex). + internal const int WaitSuccess = 0; + + // The specified object is a mutex object that was not released by the + // thread that owned the mutex object before the owning thread terminated. + // When waiting for multiple objects the return value is (WaitAbandoned + + // waitIndex). + internal const int WaitAbandoned = 0x80; + + internal const int WaitTimeout = 0x102; + + protected WaitHandle() + { + } + + [Obsolete("Use the SafeWaitHandle property instead.")] + public virtual IntPtr Handle + { + get + { + return _waitHandle == null ? InvalidHandle : _waitHandle.DangerousGetHandle(); + } + set + { + if (value == InvalidHandle) + { + // This line leaks a handle. However, it's currently + // not perfectly clear what the right behavior is here + // anyways. This preserves Everett behavior. We should + // ideally do these things: + // *) Expose a settable SafeHandle property on WaitHandle. + // *) Expose a settable OwnsHandle property on SafeHandle. + if (_waitHandle != null) + { + _waitHandle.SetHandleAsInvalid(); + _waitHandle = null; + } + } + else + { + _waitHandle = new SafeWaitHandle(value, true); + } + } + } + + public SafeWaitHandle SafeWaitHandle + { + get + { + if (_waitHandle == null) + { + _waitHandle = new SafeWaitHandle(InvalidHandle, false); + } + return _waitHandle; + } + set + { + _waitHandle = value; + } + } + + internal static int ToTimeoutMilliseconds(TimeSpan timeout) + { + var timeoutMilliseconds = (long)timeout.TotalMilliseconds; + if (timeoutMilliseconds < -1) + { + throw new ArgumentOutOfRangeException(nameof(timeout), SR.ArgumentOutOfRange_NeedNonNegOrNegative1); + } + if (timeoutMilliseconds > int.MaxValue) + { + throw new ArgumentOutOfRangeException(nameof(timeout), SR.ArgumentOutOfRange_LessEqualToIntegerMaxVal); + } + return (int)timeoutMilliseconds; + } + + public virtual void Close() => Dispose(); + + protected virtual void Dispose(bool explicitDisposing) + { + _waitHandle?.Close(); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + public virtual bool WaitOne(int millisecondsTimeout) + { + if (millisecondsTimeout < -1) + { + throw new ArgumentOutOfRangeException(nameof(millisecondsTimeout), SR.ArgumentOutOfRange_NeedNonNegOrNegative1); + } + + return WaitOneNoCheck(millisecondsTimeout); + } + + private bool WaitOneNoCheck(int millisecondsTimeout) + { + Debug.Assert(millisecondsTimeout >= -1); + + // The field value is modifiable via the public <see cref="WaitHandle.SafeWaitHandle"/> property, save it locally + // to ensure that one instance is used in all places in this method + SafeWaitHandle waitHandle = _waitHandle; + if (waitHandle == null) + { + // Throw ObjectDisposedException for backward compatibility even though it is not be representative of the issue + throw new ObjectDisposedException(null, SR.ObjectDisposed_Generic); + } + + bool success = false; + try + { + int waitResult; + + waitHandle.DangerousAddRef(ref success); + + SynchronizationContext context = SynchronizationContext.Current; + if (context != null && context.IsWaitNotificationRequired()) + { + waitResult = context.Wait(new[] { waitHandle.DangerousGetHandle() }, false, millisecondsTimeout); + } + else + { + waitResult = WaitOneCore(waitHandle.DangerousGetHandle(), millisecondsTimeout); + } + + if (waitResult == WaitAbandoned) + { + throw new AbandonedMutexException(); + } + + return waitResult != WaitTimeout; + } + finally + { + if (success) + waitHandle.DangerousRelease(); + } + } + + // Returns an array for storing SafeWaitHandles in WaitMultiple calls. The array + // is reused for subsequent calls to reduce GC pressure. + private static SafeWaitHandle[] RentSafeWaitHandleArray(int capacity) + { + SafeWaitHandle[] safeWaitHandles = t_safeWaitHandlesForRent; + + t_safeWaitHandlesForRent = null; + + // t_safeWaitHandlesForRent can be null when it was not initialized yet or + // if a re-entrant wait is performed and the array is already rented. In + // that case we just allocate a new one and reuse it as necessary. + if (safeWaitHandles == null || safeWaitHandles.Length < capacity) + { + // Always allocate at least 4 slots to prevent unnecessary reallocations + safeWaitHandles = new SafeWaitHandle[Math.Max(capacity, 4)]; + } + + return safeWaitHandles; + } + + private static void ReturnSafeWaitHandleArray(SafeWaitHandle[] safeWaitHandles) + => t_safeWaitHandlesForRent = safeWaitHandles; + + /// <summary> + /// Obtains all of the corresponding safe wait handles and adds a ref to each. Since the <see cref="SafeWaitHandle"/> + /// property is publically modifiable, this makes sure that we add and release refs one the same set of safe wait + /// handles to keep them alive during a multi-wait operation. + /// </summary> + private static void ObtainSafeWaitHandles( + ReadOnlySpan<WaitHandle> waitHandles, + Span<SafeWaitHandle> safeWaitHandles, + Span<IntPtr> unsafeWaitHandles) + { + Debug.Assert(waitHandles != null); + Debug.Assert(waitHandles.Length > 0); + Debug.Assert(waitHandles.Length <= MaxWaitHandles); + + bool lastSuccess = true; + SafeWaitHandle lastSafeWaitHandle = null; + try + { + for (int i = 0; i < waitHandles.Length; ++i) + { + WaitHandle waitHandle = waitHandles[i]; + if (waitHandle == null) + { + throw new ArgumentNullException("waitHandles[" + i + ']', SR.ArgumentNull_ArrayElement); + } + + SafeWaitHandle safeWaitHandle = waitHandle._waitHandle; + if (safeWaitHandle == null) + { + // Throw ObjectDisposedException for backward compatibility even though it is not be representative of the issue + throw new ObjectDisposedException(null, SR.ObjectDisposed_Generic); + } + + lastSafeWaitHandle = safeWaitHandle; + lastSuccess = false; + safeWaitHandle.DangerousAddRef(ref lastSuccess); + safeWaitHandles[i] = safeWaitHandle; + unsafeWaitHandles[i] = safeWaitHandle.DangerousGetHandle(); + } + } + catch + { + for (int i = 0; i < waitHandles.Length; ++i) + { + SafeWaitHandle safeWaitHandle = safeWaitHandles[i]; + if (safeWaitHandle == null) + { + break; + } + safeWaitHandle.DangerousRelease(); + safeWaitHandles[i] = null; + if (safeWaitHandle == lastSafeWaitHandle) + { + lastSafeWaitHandle = null; + lastSuccess = true; + } + } + + if (!lastSuccess) + { + lastSafeWaitHandle.DangerousRelease(); + } + + throw; + } + } + + private static int WaitMultiple(ReadOnlySpan<WaitHandle> waitHandles, bool waitAll, int millisecondsTimeout) + { + if (waitHandles == null) + { + throw new ArgumentNullException(nameof(waitHandles), SR.ArgumentNull_Waithandles); + } + if (waitHandles.Length == 0) + { + // + // Some history: in CLR 1.0 and 1.1, we threw ArgumentException in this case, which was correct. + // Somehow, in 2.0, this became ArgumentNullException. This was not fixed until Silverlight 2, + // which went back to ArgumentException. + // + // Now we're in a bit of a bind. Backward-compatibility requires us to keep throwing ArgumentException + // in CoreCLR, and ArgumentNullException in the desktop CLR. This is ugly, but so is breaking + // user code. + // + throw new ArgumentException(SR.Argument_EmptyWaithandleArray, nameof(waitHandles)); + } + if (waitHandles.Length > MaxWaitHandles) + { + throw new NotSupportedException(SR.NotSupported_MaxWaitHandles); + } + if (millisecondsTimeout < -1) + { + throw new ArgumentOutOfRangeException(nameof(millisecondsTimeout), SR.ArgumentOutOfRange_NeedNonNegOrNegative1); + } + + SynchronizationContext context = SynchronizationContext.Current; + bool useWaitContext = context != null && context.IsWaitNotificationRequired(); + SafeWaitHandle[] safeWaitHandles = RentSafeWaitHandleArray(waitHandles.Length); + + try + { + int waitResult; + + if (useWaitContext) + { + IntPtr[] unsafeWaitHandles = new IntPtr[waitHandles.Length]; + ObtainSafeWaitHandles(waitHandles, safeWaitHandles, unsafeWaitHandles); + waitResult = context.Wait(unsafeWaitHandles, waitAll, millisecondsTimeout); + } + else + { + Span<IntPtr> unsafeWaitHandles = stackalloc IntPtr[waitHandles.Length]; + ObtainSafeWaitHandles(waitHandles, safeWaitHandles, unsafeWaitHandles); + waitResult = WaitMultipleIgnoringSyncContext(unsafeWaitHandles, waitAll, millisecondsTimeout); + } + + if (waitResult >= WaitAbandoned && waitResult < WaitAbandoned + waitHandles.Length) + { + if (waitAll) + { + // In the case of WaitAll the OS will only provide the information that mutex was abandoned. + // It won't tell us which one. So we can't set the Index or provide access to the Mutex + throw new AbandonedMutexException(); + } + + waitResult -= WaitAbandoned; + throw new AbandonedMutexException(waitResult, waitHandles[waitResult]); + } + + return waitResult; + } + finally + { + for (int i = 0; i < waitHandles.Length; ++i) + { + if (safeWaitHandles[i] != null) + { + safeWaitHandles[i].DangerousRelease(); + safeWaitHandles[i] = null; + } + } + + ReturnSafeWaitHandleArray(safeWaitHandles); + } + } + + private static bool SignalAndWait(WaitHandle toSignal, WaitHandle toWaitOn, int millisecondsTimeout) + { + if (toSignal == null) + { + throw new ArgumentNullException(nameof(toSignal)); + } + if (toWaitOn == null) + { + throw new ArgumentNullException(nameof(toWaitOn)); + } + if (millisecondsTimeout < -1) + { + throw new ArgumentOutOfRangeException(nameof(millisecondsTimeout), SR.ArgumentOutOfRange_NeedNonNegOrNegative1); + } + + // The field value is modifiable via the public <see cref="WaitHandle.SafeWaitHandle"/> property, save it locally + // to ensure that one instance is used in all places in this method + SafeWaitHandle safeWaitHandleToSignal = toSignal._waitHandle; + SafeWaitHandle safeWaitHandleToWaitOn = toWaitOn._waitHandle; + if (safeWaitHandleToSignal == null || safeWaitHandleToWaitOn == null) + { + // Throw ObjectDisposedException for backward compatibility even though it is not be representative of the issue + throw new ObjectDisposedException(null, SR.ObjectDisposed_Generic); + } + + bool successSignal = false, successWait = false; + try + { + safeWaitHandleToSignal.DangerousAddRef(ref successSignal); + safeWaitHandleToWaitOn.DangerousAddRef(ref successWait); + + int ret = SignalAndWaitCore( + safeWaitHandleToSignal.DangerousGetHandle(), + safeWaitHandleToWaitOn.DangerousGetHandle(), + millisecondsTimeout); + + if (ret == WaitAbandoned) + { + throw new AbandonedMutexException(); + } + + return ret != WaitTimeout; + } + finally + { + if (successWait) + { + safeWaitHandleToWaitOn.DangerousRelease(); + } + if (successSignal) + { + safeWaitHandleToSignal.DangerousRelease(); + } + } + } + + public virtual bool WaitOne(TimeSpan timeout) => WaitOneNoCheck(ToTimeoutMilliseconds(timeout)); + public virtual bool WaitOne() => WaitOneNoCheck(-1); + public virtual bool WaitOne(int millisecondsTimeout, bool exitContext) => WaitOne(millisecondsTimeout); + public virtual bool WaitOne(TimeSpan timeout, bool exitContext) => WaitOneNoCheck(ToTimeoutMilliseconds(timeout)); + + public static bool WaitAll(WaitHandle[] waitHandles, int millisecondsTimeout) => + WaitMultiple(waitHandles, true, millisecondsTimeout) != WaitTimeout; + public static bool WaitAll(WaitHandle[] waitHandles, TimeSpan timeout) => + WaitMultiple(waitHandles, true, ToTimeoutMilliseconds(timeout)) != WaitTimeout; + public static bool WaitAll(WaitHandle[] waitHandles) => + WaitMultiple(waitHandles, true, -1) != WaitTimeout; + public static bool WaitAll(WaitHandle[] waitHandles, int millisecondsTimeout, bool exitContext) => + WaitMultiple(waitHandles, true, millisecondsTimeout) != WaitTimeout; + public static bool WaitAll(WaitHandle[] waitHandles, TimeSpan timeout, bool exitContext) => + WaitMultiple(waitHandles, true, ToTimeoutMilliseconds(timeout)) != WaitTimeout; + + public static int WaitAny(WaitHandle[] waitHandles, int millisecondsTimeout) => + WaitMultiple(waitHandles, false, millisecondsTimeout); + public static int WaitAny(WaitHandle[] waitHandles, TimeSpan timeout) => + WaitMultiple(waitHandles, false, ToTimeoutMilliseconds(timeout)); + public static int WaitAny(WaitHandle[] waitHandles) => + WaitMultiple(waitHandles, false, -1); + public static int WaitAny(WaitHandle[] waitHandles, int millisecondsTimeout, bool exitContext) => + WaitMultiple(waitHandles, false, millisecondsTimeout); + public static int WaitAny(WaitHandle[] waitHandles, TimeSpan timeout, bool exitContext) => + WaitMultiple(waitHandles, false, ToTimeoutMilliseconds(timeout)); + + public static bool SignalAndWait(WaitHandle toSignal, WaitHandle toWaitOn) => + SignalAndWait(toSignal, toWaitOn, -1); + public static bool SignalAndWait(WaitHandle toSignal, WaitHandle toWaitOn, TimeSpan timeout, bool exitContext) => + SignalAndWait(toSignal, toWaitOn, ToTimeoutMilliseconds(timeout)); + public static bool SignalAndWait(WaitHandle toSignal, WaitHandle toWaitOn, int millisecondsTimeout, bool exitContext) => + SignalAndWait(toSignal, toWaitOn, millisecondsTimeout); + } +} diff --git a/src/System.Private.CoreLib/src/System/Threading/WaitHandle.CoreCLR.cs b/src/System.Private.CoreLib/src/System/Threading/WaitHandle.CoreCLR.cs new file mode 100644 index 0000000000..97b5c574e3 --- /dev/null +++ b/src/System.Private.CoreLib/src/System/Threading/WaitHandle.CoreCLR.cs @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// 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.CompilerServices; +using System.Runtime.InteropServices; + +namespace System.Threading +{ + public abstract partial class WaitHandle + { + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern int WaitOneCore(IntPtr waitHandle, int millisecondsTimeout); + + internal static unsafe int WaitMultipleIgnoringSyncContext(Span<IntPtr> waitHandles, bool waitAll, int millisecondsTimeout) + { + fixed (IntPtr *pWaitHandles = &MemoryMarshal.GetReference(waitHandles)) + { + return WaitMultipleIgnoringSyncContext(pWaitHandles, waitHandles.Length, waitAll, millisecondsTimeout); + } + } + + [MethodImpl(MethodImplOptions.InternalCall)] + private static unsafe extern int WaitMultipleIgnoringSyncContext(IntPtr *waitHandles, int numHandles, bool waitAll, int millisecondsTimeout); + + private static int SignalAndWaitCore(IntPtr waitHandleToSignal, IntPtr waitHandleToWaitOn, int millisecondsTimeout) + { + int ret = SignalAndWaitNative(waitHandleToSignal, waitHandleToWaitOn, millisecondsTimeout); + + if (ret == Interop.Errors.ERROR_TOO_MANY_POSTS) + { + throw new InvalidOperationException(SR.Threading_WaitHandleTooManyPosts); + } + + return ret; + } + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern int SignalAndWaitNative(IntPtr waitHandleToSignal, IntPtr waitHandleToWaitOn, int millisecondsTimeout); + } +} diff --git a/src/System.Private.CoreLib/src/System/Threading/WaitHandle.cs b/src/System.Private.CoreLib/src/System/Threading/WaitHandle.cs deleted file mode 100644 index a5a5643df0..0000000000 --- a/src/System.Private.CoreLib/src/System/Threading/WaitHandle.cs +++ /dev/null @@ -1,471 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -// -/*============================================================================= -** -** -** -** Purpose: Class to represent all synchronization objects in the runtime (that allow multiple wait) -** -** -=============================================================================*/ - -using System; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using Microsoft.Win32.SafeHandles; -using System.Diagnostics.CodeAnalysis; - -namespace System.Threading -{ - public abstract class WaitHandle : MarshalByRefObject, IDisposable - { - public const int WaitTimeout = 0x102; - - private const int MAX_WAITHANDLES = 64; - - // IMPORTANT: - // - Do not add or rearrange fields as the EE depends on this layout. - - internal SafeWaitHandle _waitHandle; - - protected static readonly IntPtr InvalidHandle = new IntPtr(-1); - private const int WAIT_OBJECT_0 = 0; - private const int WAIT_ABANDONED = 0x80; - private const int WAIT_FAILED = 0x7FFFFFFF; - private const int ERROR_TOO_MANY_POSTS = 0x12A; - - internal enum OpenExistingResult - { - Success, - NameNotFound, - PathNotFound, - NameInvalid - } - - protected WaitHandle() - { - } - - [Obsolete("Use the SafeWaitHandle property instead.")] - public virtual IntPtr Handle - { - get { return _waitHandle == null ? InvalidHandle : _waitHandle.DangerousGetHandle(); } - set - { - if (value == InvalidHandle) - { - // This line leaks a handle. However, it's currently - // not perfectly clear what the right behavior is here - // anyways. This preserves Everett behavior. We should - // ideally do these things: - // *) Expose a settable SafeHandle property on WaitHandle. - // *) Expose a settable OwnsHandle property on SafeHandle. - if (_waitHandle != null) - { - _waitHandle.SetHandleAsInvalid(); - _waitHandle = null; - } - } - else - { - _waitHandle = new SafeWaitHandle(value, true); - } - } - } - - public SafeWaitHandle SafeWaitHandle - { - get - { - if (_waitHandle == null) - { - _waitHandle = new SafeWaitHandle(InvalidHandle, false); - } - return _waitHandle; - } - - set - { - _waitHandle = value; - } - } - - public virtual bool WaitOne(int millisecondsTimeout, bool exitContext) - { - if (millisecondsTimeout < -1) - { - throw new ArgumentOutOfRangeException(nameof(millisecondsTimeout), SR.ArgumentOutOfRange_NeedNonNegOrNegative1); - } - return WaitOne((long)millisecondsTimeout, exitContext); - } - - public virtual bool WaitOne(TimeSpan timeout, bool exitContext) - { - long tm = (long)timeout.TotalMilliseconds; - if (-1 > tm || (long)int.MaxValue < tm) - { - throw new ArgumentOutOfRangeException(nameof(timeout), SR.ArgumentOutOfRange_NeedNonNegOrNegative1); - } - return WaitOne(tm, exitContext); - } - - public virtual bool WaitOne() - { - //Infinite Timeout - return WaitOne(-1, false); - } - - public virtual bool WaitOne(int millisecondsTimeout) - { - return WaitOne(millisecondsTimeout, false); - } - - public virtual bool WaitOne(TimeSpan timeout) - { - return WaitOne(timeout, false); - } - - [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "Reviewed for thread-safety.")] - private bool WaitOne(long timeout, bool exitContext) - { - return InternalWaitOne(_waitHandle, timeout, exitContext); - } - - internal static bool InternalWaitOne(SafeHandle waitableSafeHandle, long millisecondsTimeout, bool exitContext) - { - if (waitableSafeHandle == null) - { - throw new ObjectDisposedException(null, SR.ObjectDisposed_Generic); - } - int ret = WaitOneNative(waitableSafeHandle, (uint)millisecondsTimeout, exitContext); - - if (ret == WAIT_ABANDONED) - { - ThrowAbandonedMutexException(); - } - return (ret != WaitTimeout); - } - - internal bool WaitOneWithoutFAS() - { - // version of waitone without fast application switch (FAS) support - // This is required to support the Wait which FAS needs (otherwise recursive dependency comes in) - if (_waitHandle == null) - { - throw new ObjectDisposedException(null, SR.ObjectDisposed_Generic); - } - - long timeout = -1; - int ret = WaitOneNative(_waitHandle, (uint)timeout, false); - if (ret == WAIT_ABANDONED) - { - ThrowAbandonedMutexException(); - } - return (ret != WaitTimeout); - } - - [MethodImplAttribute(MethodImplOptions.InternalCall)] - private static extern int WaitOneNative(SafeHandle waitableSafeHandle, uint millisecondsTimeout, bool exitContext); - - /*======================================================================== - ** Waits for signal from all the objects. - ** timeout indicates how long to wait before the method returns. - ** This method will return either when all the object have been pulsed - ** or timeout milliseconds have elapsed. - ** If exitContext is true then the synchronization domain for the context - ** (if in a synchronized context) is exited before the wait and reacquired - ========================================================================*/ - - [MethodImplAttribute(MethodImplOptions.InternalCall)] - private static extern int WaitMultiple(WaitHandle[] waitHandles, int millisecondsTimeout, bool exitContext, bool WaitAll); - - [MethodImplAttribute(MethodImplOptions.InternalCall)] - internal static extern int WaitMultipleIgnoringSyncContext(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout); - - public static bool WaitAll(WaitHandle[] waitHandles, int millisecondsTimeout, bool exitContext) - { - if (waitHandles == null) - { - throw new ArgumentNullException(nameof(waitHandles), SR.ArgumentNull_Waithandles); - } - if (waitHandles.Length == 0) - { - // - // Some history: in CLR 1.0 and 1.1, we threw ArgumentException in this case, which was correct. - // Somehow, in 2.0, this became ArgumentNullException. This was not fixed until Silverlight 2, - // which went back to ArgumentException. - // - // Now we're in a bit of a bind. Backward-compatibility requires us to keep throwing ArgumentException - // in CoreCLR, and ArgumentNullException in the desktop CLR. This is ugly, but so is breaking - // user code. - // - throw new ArgumentException(SR.Argument_EmptyWaithandleArray); - } - if (waitHandles.Length > MAX_WAITHANDLES) - { - throw new NotSupportedException(SR.NotSupported_MaxWaitHandles); - } - if (-1 > millisecondsTimeout) - { - throw new ArgumentOutOfRangeException(nameof(millisecondsTimeout), SR.ArgumentOutOfRange_NeedNonNegOrNegative1); - } - WaitHandle[] internalWaitHandles = new WaitHandle[waitHandles.Length]; - for (int i = 0; i < waitHandles.Length; i++) - { - WaitHandle waitHandle = waitHandles[i]; - - if (waitHandle == null) - throw new ArgumentNullException("waitHandles[" + i + "]", SR.ArgumentNull_ArrayElement); - - internalWaitHandles[i] = waitHandle; - } -#if DEBUG - // make sure we do not use waitHandles any more. - waitHandles = null; -#endif - - int ret = WaitMultiple(internalWaitHandles, millisecondsTimeout, exitContext, true /* waitall*/ ); - - if ((WAIT_ABANDONED <= ret) && (WAIT_ABANDONED + internalWaitHandles.Length > ret)) - { - //In the case of WaitAll the OS will only provide the - // information that mutex was abandoned. - // It won't tell us which one. So we can't set the Index or provide access to the Mutex - ThrowAbandonedMutexException(); - } - - GC.KeepAlive(internalWaitHandles); - return (ret != WaitTimeout); - } - - public static bool WaitAll( - WaitHandle[] waitHandles, - TimeSpan timeout, - bool exitContext) - { - long tm = (long)timeout.TotalMilliseconds; - if (-1 > tm || (long)int.MaxValue < tm) - { - throw new ArgumentOutOfRangeException(nameof(timeout), SR.ArgumentOutOfRange_NeedNonNegOrNegative1); - } - return WaitAll(waitHandles, (int)tm, exitContext); - } - - - /*======================================================================== - ** Shorthand for WaitAll with timeout = Timeout.Infinite and exitContext = true - ========================================================================*/ - public static bool WaitAll(WaitHandle[] waitHandles) - { - return WaitAll(waitHandles, Timeout.Infinite, true); - } - - public static bool WaitAll(WaitHandle[] waitHandles, int millisecondsTimeout) - { - return WaitAll(waitHandles, millisecondsTimeout, true); - } - - public static bool WaitAll(WaitHandle[] waitHandles, TimeSpan timeout) - { - return WaitAll(waitHandles, timeout, true); - } - - - /*======================================================================== - ** Waits for notification from any of the objects. - ** timeout indicates how long to wait before the method returns. - ** This method will return either when either one of the object have been - ** signaled or timeout milliseconds have elapsed. - ** If exitContext is true then the synchronization domain for the context - ** (if in a synchronized context) is exited before the wait and reacquired - ========================================================================*/ - - public static int WaitAny(WaitHandle[] waitHandles, int millisecondsTimeout, bool exitContext) - { - if (waitHandles == null) - { - throw new ArgumentNullException(nameof(waitHandles), SR.ArgumentNull_Waithandles); - } - if (waitHandles.Length == 0) - { - throw new ArgumentException(SR.Argument_EmptyWaithandleArray); - } - if (MAX_WAITHANDLES < waitHandles.Length) - { - throw new NotSupportedException(SR.NotSupported_MaxWaitHandles); - } - if (-1 > millisecondsTimeout) - { - throw new ArgumentOutOfRangeException(nameof(millisecondsTimeout), SR.ArgumentOutOfRange_NeedNonNegOrNegative1); - } - WaitHandle[] internalWaitHandles = new WaitHandle[waitHandles.Length]; - for (int i = 0; i < waitHandles.Length; i++) - { - WaitHandle waitHandle = waitHandles[i]; - - if (waitHandle == null) - throw new ArgumentNullException("waitHandles[" + i + "]", SR.ArgumentNull_ArrayElement); - - internalWaitHandles[i] = waitHandle; - } -#if DEBUG - // make sure we do not use waitHandles any more. - waitHandles = null; -#endif - int ret = WaitMultiple(internalWaitHandles, millisecondsTimeout, exitContext, false /* waitany*/ ); - - if ((WAIT_ABANDONED <= ret) && (WAIT_ABANDONED + internalWaitHandles.Length > ret)) - { - int mutexIndex = ret - WAIT_ABANDONED; - if (0 <= mutexIndex && mutexIndex < internalWaitHandles.Length) - { - ThrowAbandonedMutexException(mutexIndex, internalWaitHandles[mutexIndex]); - } - else - { - ThrowAbandonedMutexException(); - } - } - - GC.KeepAlive(internalWaitHandles); - return ret; - } - - public static int WaitAny( - WaitHandle[] waitHandles, - TimeSpan timeout, - bool exitContext) - { - long tm = (long)timeout.TotalMilliseconds; - if (-1 > tm || (long)int.MaxValue < tm) - { - throw new ArgumentOutOfRangeException(nameof(timeout), SR.ArgumentOutOfRange_NeedNonNegOrNegative1); - } - return WaitAny(waitHandles, (int)tm, exitContext); - } - public static int WaitAny(WaitHandle[] waitHandles, TimeSpan timeout) - { - return WaitAny(waitHandles, timeout, true); - } - - - /*======================================================================== - ** Shorthand for WaitAny with timeout = Timeout.Infinite and exitContext = true - ========================================================================*/ - public static int WaitAny(WaitHandle[] waitHandles) - { - return WaitAny(waitHandles, Timeout.Infinite, true); - } - - public static int WaitAny(WaitHandle[] waitHandles, int millisecondsTimeout) - { - return WaitAny(waitHandles, millisecondsTimeout, true); - } - - /*================================================= - == - == SignalAndWait - == - ==================================================*/ - [MethodImplAttribute(MethodImplOptions.InternalCall)] - private static extern int SignalAndWaitOne(SafeWaitHandle waitHandleToSignal, SafeWaitHandle waitHandleToWaitOn, int millisecondsTimeout, - bool exitContext); - - public static bool SignalAndWait( - WaitHandle toSignal, - WaitHandle toWaitOn) - { - return SignalAndWait(toSignal, toWaitOn, -1, false); - } - - public static bool SignalAndWait( - WaitHandle toSignal, - WaitHandle toWaitOn, - TimeSpan timeout, - bool exitContext) - { - long tm = (long)timeout.TotalMilliseconds; - if (-1 > tm || (long)int.MaxValue < tm) - { - throw new ArgumentOutOfRangeException(nameof(timeout), SR.ArgumentOutOfRange_NeedNonNegOrNegative1); - } - return SignalAndWait(toSignal, toWaitOn, (int)tm, exitContext); - } - - [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "Reviewed for thread-safety.")] - public static bool SignalAndWait( - WaitHandle toSignal, - WaitHandle toWaitOn, - int millisecondsTimeout, - bool exitContext) - { - if (null == toSignal) - { - throw new ArgumentNullException(nameof(toSignal)); - } - if (null == toWaitOn) - { - throw new ArgumentNullException(nameof(toWaitOn)); - } - if (-1 > millisecondsTimeout) - { - throw new ArgumentOutOfRangeException(nameof(millisecondsTimeout), SR.ArgumentOutOfRange_NeedNonNegOrNegative1); - } - - //NOTE: This API is not supporting Pause/Resume as it's not exposed in CoreCLR (not in WP or SL) - int ret = SignalAndWaitOne(toSignal._waitHandle, toWaitOn._waitHandle, millisecondsTimeout, exitContext); - - if (WAIT_ABANDONED == ret) - { - ThrowAbandonedMutexException(); - } - - if (ERROR_TOO_MANY_POSTS == ret) - { - throw new InvalidOperationException(SR.Threading_WaitHandleTooManyPosts); - } - - //Object was signaled - if (WAIT_OBJECT_0 == ret) - { - return true; - } - - //Timeout - return false; - } - - private static void ThrowAbandonedMutexException() - { - throw new AbandonedMutexException(); - } - - private static void ThrowAbandonedMutexException(int location, WaitHandle handle) - { - throw new AbandonedMutexException(location, handle); - } - - public virtual void Close() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool explicitDisposing) - { - if (_waitHandle != null) - { - _waitHandle.Close(); - } - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - } -} diff --git a/src/vm/comwaithandle.cpp b/src/vm/comwaithandle.cpp index abb1daf16b..d6c7da37aa 100644 --- a/src/vm/comwaithandle.cpp +++ b/src/vm/comwaithandle.cpp @@ -78,37 +78,6 @@ private: } }; -void AcquireSafeHandleFromWaitHandle(WAITHANDLEREF wh) -{ - CONTRACTL { - THROWS; - GC_TRIGGERS; - MODE_COOPERATIVE; - PRECONDITION(wh != NULL); - } CONTRACTL_END; - - SAFEHANDLEREF sh = wh->GetSafeHandle(); - if (sh == NULL) - COMPlusThrow(kObjectDisposedException); - sh->AddRef(); -} - -void ReleaseSafeHandleFromWaitHandle(WAITHANDLEREF wh) -{ - CONTRACTL { - THROWS; - GC_TRIGGERS; - MODE_COOPERATIVE; - PRECONDITION(wh != NULL); - } CONTRACTL_END; - - SAFEHANDLEREF sh = wh->GetSafeHandle(); - _ASSERTE(sh); - sh->Release(); -} - -typedef ObjArrayHolder<WAITHANDLEREF, AcquireSafeHandleFromWaitHandle, ReleaseSafeHandleFromWaitHandle> WaitHandleArrayHolder; - INT64 AdditionalWait(INT64 sPauseTime, INT64 sTime, INT64 expDuration) { LIMITED_METHOD_CONTRACT; @@ -157,91 +126,58 @@ INT64 AdditionalWait(INT64 sPauseTime, INT64 sTime, INT64 expDuration) return additional; } -FCIMPL3(INT32, WaitHandleNative::CorWaitOneNative, SafeHandle* safeWaitHandleUNSAFE, INT32 timeout, CLR_BOOL exitContext) +FCIMPL2(INT32, WaitHandleNative::CorWaitOneNative, HANDLE handle, INT32 timeout) { FCALL_CONTRACT; INT32 retVal = 0; - SAFEHANDLEREF sh(safeWaitHandleUNSAFE); - HELPER_METHOD_FRAME_BEGIN_RET_1(sh); + HELPER_METHOD_FRAME_BEGIN_RET_0(); - _ASSERTE(sh != NULL); + _ASSERTE(handle != 0); + _ASSERTE(handle != INVALID_HANDLE_VALUE); Thread* pThread = GET_THREAD(); DWORD res = (DWORD) -1; - SafeHandleHolder shh(&sh); - // Note that SafeHandle is a GC object, and RequestCallback and - // DoAppropriateWait work on an array of handles. Don't pass the address - // of the handle field - that's a GC hole. Instead, pass this temp - // array. - HANDLE handles[1]; - handles[0] = sh->GetHandle(); + // Support for pause/resume (FXFREEZE) + while(true) { - // Support for pause/resume (FXFREEZE) - while(true) - { - INT64 sPauseTime = g_PauseTime; - INT64 sTime = CLRGetTickCount64(); - res = pThread->DoAppropriateWait(1,handles,TRUE,timeout, WaitMode_Alertable /*alertable*/); - if(res != WAIT_TIMEOUT) - break; - timeout = (INT32)AdditionalWait(sPauseTime, sTime, timeout); - if(timeout == 0) - break; - } + INT64 sPauseTime = g_PauseTime; + INT64 sTime = CLRGetTickCount64(); + res = pThread->DoAppropriateWait(1, &handle, TRUE, timeout, (WaitMode)(WaitMode_Alertable | WaitMode_IgnoreSyncCtx)); + if(res != WAIT_TIMEOUT) + break; + timeout = (INT32)AdditionalWait(sPauseTime, sTime, timeout); + if(timeout == 0) + break; } retVal = res; - HELPER_METHOD_FRAME_END(); return retVal; } FCIMPLEND -FCIMPL4(INT32, WaitHandleNative::CorWaitMultipleNative, Object* waitObjectsUNSAFE, INT32 timeout, CLR_BOOL exitContext, CLR_BOOL waitForAll) +FCIMPL4(INT32, WaitHandleNative::CorWaitMultipleNative, HANDLE *handleArray, INT32 numHandles, CLR_BOOL waitForAll, INT32 timeout) { FCALL_CONTRACT; - INT32 retVal = 0; - OBJECTREF waitObjects = (OBJECTREF) waitObjectsUNSAFE; - HELPER_METHOD_FRAME_BEGIN_RET_1(waitObjects); - - _ASSERTE(waitObjects); - - Thread* pThread = GET_THREAD(); + INT32 ret = 0; + HELPER_METHOD_FRAME_BEGIN_RET_0(); - PTRARRAYREF pWaitObjects = (PTRARRAYREF)waitObjects; // array of objects on which to wait - int numWaiters = pWaitObjects->GetNumComponents(); + Thread * pThread = GET_THREAD(); #ifdef FEATURE_COMINTEROP_APARTMENT_SUPPORT // There are some issues with wait-all from an STA thread // - https://github.com/dotnet/coreclr/issues/17787#issuecomment-385117537 - if (waitForAll && numWaiters > 1 && pThread->GetApartment() == Thread::AS_InSTA) + if (waitForAll && numHandles > 1 && pThread->GetApartment() == Thread::AS_InSTA) { COMPlusThrow(kNotSupportedException, W("NotSupported_WaitAllSTAThread")); } #endif // FEATURE_COMINTEROP_APARTMENT_SUPPORT - WaitHandleArrayHolder arrayHolder; - arrayHolder.Initialize(numWaiters, (PTRARRAYREF*) &waitObjects); - - pWaitObjects = (PTRARRAYREF)waitObjects; // array of objects on which to wait - HANDLE* internalHandles = (HANDLE*) _alloca(numWaiters*sizeof(HANDLE)); - for (int i=0;i<numWaiters;i++) - { - WAITHANDLEREF waitObject = (WAITHANDLEREF) pWaitObjects->m_Array[i]; - _ASSERTE(waitObject != NULL); - - //If the size of the array is 1 and handle is INVALID_HANDLE_VALUE then WaitForMultipleObjectsEx will - // return ERROR_INVALID_HANDLE but DoAppropriateWait will convert to WAIT_OBJECT_0. i.e Success, - // this behavior seems wrong but someone explicitly coded that condition so it must have been for a reason. - internalHandles[i] = waitObject->GetWaitHandle(); - - } - DWORD res = (DWORD) -1; { // Support for pause/resume (FXFREEZE) @@ -249,7 +185,7 @@ FCIMPL4(INT32, WaitHandleNative::CorWaitMultipleNative, Object* waitObjectsUNSAF { INT64 sPauseTime = g_PauseTime; INT64 sTime = CLRGetTickCount64(); - res = pThread->DoAppropriateWait(numWaiters, internalHandles, waitForAll, timeout, WaitMode_Alertable /*alertable*/); + res = pThread->DoAppropriateWait(numHandles, handleArray, waitForAll, timeout, (WaitMode)(WaitMode_Alertable | WaitMode_IgnoreSyncCtx)); if(res != WAIT_TIMEOUT) break; timeout = (INT32)AdditionalWait(sPauseTime, sTime, timeout); @@ -258,30 +194,23 @@ FCIMPL4(INT32, WaitHandleNative::CorWaitMultipleNative, Object* waitObjectsUNSAF } } - - retVal = res; - + ret = res; + HELPER_METHOD_FRAME_END(); - return retVal; + return ret; } FCIMPLEND -FCIMPL4(INT32, WaitHandleNative::CorSignalAndWaitOneNative, SafeHandle* safeWaitHandleSignalUNSAFE,SafeHandle* safeWaitHandleWaitUNSAFE, INT32 timeout, CLR_BOOL exitContext) +FCIMPL3(INT32, WaitHandleNative::CorSignalAndWaitOneNative, HANDLE waitHandleSignalUNSAFE, HANDLE waitHandleWaitUNSAFE, INT32 timeout) { FCALL_CONTRACT; INT32 retVal = 0; - SAFEHANDLEREF shSignal(safeWaitHandleSignalUNSAFE); - SAFEHANDLEREF shWait(safeWaitHandleWaitUNSAFE); - HELPER_METHOD_FRAME_BEGIN_RET_2(shSignal,shWait); - - if(shSignal == NULL || shWait == NULL) - COMPlusThrow(kObjectDisposedException); - - _ASSERTE(safeWaitHandleSignalUNSAFE != NULL); - _ASSERTE( safeWaitHandleWaitUNSAFE != NULL); + HELPER_METHOD_FRAME_BEGIN_RET_0(); + _ASSERTE(waitHandleSignalUNSAFE != 0); + _ASSERTE(waitHandleWaitUNSAFE != 0); Thread* pThread = GET_THREAD(); @@ -293,47 +222,16 @@ FCIMPL4(INT32, WaitHandleNative::CorSignalAndWaitOneNative, SafeHandle* safeWait DWORD res = (DWORD) -1; - SafeHandleHolder shhSignal(&shSignal); - SafeHandleHolder shhWait(&shWait); - // Don't pass the address of the handle field - // - that's a GC hole. Instead, pass this temp array. HANDLE handles[2]; - handles[0] = shSignal->GetHandle(); - handles[1] = shWait->GetHandle(); + handles[0] = waitHandleSignalUNSAFE; + handles[1] = waitHandleWaitUNSAFE; { - res = pThread->DoSignalAndWait(handles,timeout,TRUE /*alertable*/); + res = pThread->DoSignalAndWait(handles, timeout, TRUE /*alertable*/); } - retVal = res; HELPER_METHOD_FRAME_END(); return retVal; } FCIMPLEND - -FCIMPL3(DWORD, WaitHandleNative::WaitHelper, PTRArray *handleArrayUNSAFE, CLR_BOOL waitAll, DWORD millis) -{ - FCALL_CONTRACT; - - DWORD ret = 0; - - PTRARRAYREF handleArrayObj = (PTRARRAYREF) handleArrayUNSAFE; - HELPER_METHOD_FRAME_BEGIN_RET_1(handleArrayObj); - - CQuickArray<HANDLE> qbHandles; - int cHandles = handleArrayObj->GetNumComponents(); - - // Since DoAppropriateWait could cause a GC, we need to copy the handles to an unmanaged block - // of memory to ensure they aren't relocated during the call to DoAppropriateWait. - qbHandles.AllocThrows(cHandles); - memcpy(qbHandles.Ptr(), handleArrayObj->GetDataPtr(), cHandles * sizeof(HANDLE)); - - Thread * pThread = GetThread(); - ret = pThread->DoAppropriateWait(cHandles, qbHandles.Ptr(), waitAll, millis, - (WaitMode)(WaitMode_Alertable | WaitMode_IgnoreSyncCtx)); - - HELPER_METHOD_FRAME_END(); - return ret; -} -FCIMPLEND diff --git a/src/vm/comwaithandle.h b/src/vm/comwaithandle.h index 816af13e9b..218446881a 100644 --- a/src/vm/comwaithandle.h +++ b/src/vm/comwaithandle.h @@ -19,9 +19,8 @@ class WaitHandleNative { public: - static FCDECL3(INT32, CorWaitOneNative, SafeHandle* safeWaitHandleUNSAFE, INT32 timeout, CLR_BOOL exitContext); - static FCDECL4(INT32, CorWaitMultipleNative, Object* waitObjectsUNSAFE, INT32 timeout, CLR_BOOL exitContext, CLR_BOOL waitForAll); - static FCDECL4(INT32, CorSignalAndWaitOneNative, SafeHandle* safeWaitHandleSignalUNSAFE, SafeHandle* safeWaitHandleWaitUNSAFE, INT32 timeout, CLR_BOOL exitContext); - static FCDECL3(DWORD, WaitHelper, PTRArray *handleArrayUNSAFE, CLR_BOOL waitAll, DWORD millis); + static FCDECL2(INT32, CorWaitOneNative, HANDLE handle, INT32 timeout); + static FCDECL4(INT32, CorWaitMultipleNative, HANDLE *handleArray, INT32 numHandles, CLR_BOOL waitForAll, INT32 timeout); + static FCDECL3(INT32, CorSignalAndWaitOneNative, HANDLE waitHandleSignalUNSAFE, HANDLE waitHandleWaitUNSAFE, INT32 timeout); }; #endif diff --git a/src/vm/ecalllist.h b/src/vm/ecalllist.h index cc07d0e570..30492d1512 100644 --- a/src/vm/ecalllist.h +++ b/src/vm/ecalllist.h @@ -689,10 +689,9 @@ FCFuncStart(gRegisteredWaitHandleFuncs) FCFuncEnd() FCFuncStart(gWaitHandleFuncs) - FCFuncElement("WaitOneNative", WaitHandleNative::CorWaitOneNative) - FCFuncElement("WaitMultiple", WaitHandleNative::CorWaitMultipleNative) - FCFuncElement("SignalAndWaitOne", WaitHandleNative::CorSignalAndWaitOneNative) - FCFuncElement("WaitMultipleIgnoringSyncContext", WaitHandleNative::WaitHelper) + FCFuncElement("WaitOneCore", WaitHandleNative::CorWaitOneNative) + FCFuncElement("WaitMultipleIgnoringSyncContext", WaitHandleNative::CorWaitMultipleNative) + FCFuncElement("SignalAndWaitNative", WaitHandleNative::CorSignalAndWaitOneNative) FCFuncEnd() #ifdef FEATURE_COMINTEROP |