// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; using System.Runtime.Versioning; using Microsoft.Win32.SafeHandles; namespace System.Threading { /// <summary> /// An object representing the registration of a <see cref="WaitHandle"/> via <see cref="ThreadPool.RegisterWaitForSingleObject"/>. /// </summary> #if !FEATURE_WASM_MANAGED_THREADS [UnsupportedOSPlatform("browser")] #endif public sealed partial class RegisteredWaitHandle : MarshalByRefObject { private static AutoResetEvent? s_cachedEvent; private static readonly LowLevelLock s_callbackLock = new LowLevelLock(); /// <summary> /// The <see cref="WaitHandle"/> the user passed in via <see cref="Unregister(WaitHandle)"/>. /// </summary> private SafeWaitHandle? UserUnregisterWaitHandle { get; set; } private IntPtr UserUnregisterWaitHandleValue { get; set; } private static IntPtr InvalidHandleValue => new IntPtr(-1); internal bool IsBlocking => UserUnregisterWaitHandleValue == InvalidHandleValue; /// <summary> /// The number of callbacks that are currently queued on the Thread Pool or executing. /// </summary> private int _numRequestedCallbacks; /// <summary> /// Notes if we need to signal the user's unregister event after all callbacks complete. /// </summary> private bool _signalAfterCallbacksComplete; private bool _unregisterCalled; private bool _unregistered; private AutoResetEvent? _callbacksComplete; private AutoResetEvent? _removed; /// <summary> /// The <see cref="PortableThreadPool.WaitThread"/> this <see cref="RegisteredWaitHandle"/> was registered on. /// </summary> internal PortableThreadPool.WaitThread? WaitThread { get; set; } internal RegisteredWaitHandle(WaitHandle waitHandle, _ThreadPoolWaitOrTimerCallback callbackHelper, int millisecondsTimeout, bool repeating) { #if WINDOWS Debug.Assert(!ThreadPool.UseWindowsThreadPool); #endif GC.SuppressFinalize(this); Thread.ThrowIfNoThreadStart(); _waitHandle = waitHandle.SafeWaitHandle; _callbackHelper = callbackHelper; _signedMillisecondsTimeout = millisecondsTimeout; _repeating = repeating; if (!IsInfiniteTimeout) { RestartTimeout(); } } private static AutoResetEvent RentEvent() => Interlocked.Exchange(ref s_cachedEvent, null) ?? new AutoResetEvent(false); private static void ReturnEvent(AutoResetEvent resetEvent) { if (Interlocked.CompareExchange(ref s_cachedEvent, resetEvent, null) != null) { resetEvent.Dispose(); } } internal void RestartTimeout() { Debug.Assert(!IsInfiniteTimeout); TimeoutTimeMs = Environment.TickCount + TimeoutDurationMs; } private bool UnregisterPortableCore(WaitHandle waitObject) { // The registered wait handle must have been registered by this time, otherwise the instance is not handed out to // the caller of the public variants of RegisterWaitForSingleObject Debug.Assert(WaitThread != null); s_callbackLock.Acquire(); bool needToRollBackRefCountOnException = false; try { if (_unregisterCalled) { return false; } UserUnregisterWaitHandle = waitObject?.SafeWaitHandle; UserUnregisterWaitHandle?.DangerousAddRef(ref needToRollBackRefCountOnException); UserUnregisterWaitHandleValue = UserUnregisterWaitHandle?.DangerousGetHandle() ?? IntPtr.Zero; if (_unregistered) { SignalUserWaitHandle(); return true; } if (IsBlocking) { _callbacksComplete = RentEvent(); } else { _removed = RentEvent(); } } catch (Exception) // Rollback state on exception { if (_removed != null) { ReturnEvent(_removed); _removed = null; } else if (_callbacksComplete != null) { ReturnEvent(_callbacksComplete); _callbacksComplete = null; } UserUnregisterWaitHandleValue = IntPtr.Zero; if (needToRollBackRefCountOnException) { UserUnregisterWaitHandle?.DangerousRelease(); } UserUnregisterWaitHandle = null; throw; } finally { _unregisterCalled = true; s_callbackLock.Release(); } WaitThread!.UnregisterWait(this); return true; } /// <summary> /// Signal <see cref="UserUnregisterWaitHandle"/> if it has not been signaled yet and is a valid handle. /// </summary> private void SignalUserWaitHandle() { s_callbackLock.VerifyIsLocked(); SafeWaitHandle? handle = UserUnregisterWaitHandle; IntPtr handleValue = UserUnregisterWaitHandleValue; try { if (handleValue != IntPtr.Zero && handleValue != InvalidHandleValue) { Debug.Assert(handleValue == handle!.DangerousGetHandle()); EventWaitHandle.Set(handle); } } finally { handle?.DangerousRelease(); _callbacksComplete?.Set(); _unregistered = true; } } /// <summary> /// Perform the registered callback if the <see cref="UserUnregisterWaitHandle"/> has not been signaled. /// </summary> /// <param name="timedOut">Whether or not the wait timed out.</param> internal void PerformCallbackPortableCore(bool timedOut) { #if DEBUG s_callbackLock.Acquire(); try { Debug.Assert(_numRequestedCallbacks != 0); } finally { s_callbackLock.Release(); } #endif _ThreadPoolWaitOrTimerCallback.PerformWaitOrTimerCallback(Callback!, timedOut); CompleteCallbackRequest(); } /// <summary> /// Tell this handle that there is a callback queued on the thread pool for this handle. /// </summary> internal void RequestCallback() { s_callbackLock.Acquire(); try { _numRequestedCallbacks++; } finally { s_callbackLock.Release(); } } /// <summary> /// Called when the wait thread removes this handle registration. This will signal the user's event if there are no callbacks pending, /// or note that the user's event must be signaled when the callbacks complete. /// </summary> internal void OnRemoveWait() { s_callbackLock.Acquire(); try { _removed?.Set(); if (_numRequestedCallbacks == 0) { SignalUserWaitHandle(); } else { _signalAfterCallbacksComplete = true; } } finally { s_callbackLock.Release(); } } /// <summary> /// Reduces the number of callbacks requested. If there are no more callbacks and the user's handle is queued to be signaled, signal it. /// </summary> private void CompleteCallbackRequest() { s_callbackLock.Acquire(); try { --_numRequestedCallbacks; if (_numRequestedCallbacks == 0 && _signalAfterCallbacksComplete) { SignalUserWaitHandle(); } } finally { s_callbackLock.Release(); } } /// <summary> /// Wait for all queued callbacks and the full unregistration to complete. /// </summary> internal void WaitForCallbacks() { Debug.Assert(IsBlocking); Debug.Assert(_unregisterCalled); // Should only be called when the wait is unregistered by the user. _callbacksComplete!.WaitOne(); ReturnEvent(_callbacksComplete); _callbacksComplete = null; } internal void WaitForRemoval() { Debug.Assert(!IsBlocking); Debug.Assert(_unregisterCalled); // Should only be called when the wait is unregistered by the user. _removed!.WaitOne(); ReturnEvent(_removed); _removed = null; } } } |