File: src\libraries\System.Private.CoreLib\src\System\Threading\Condition.cs
Web Access
Project: src\src\coreclr\System.Private.CoreLib\System.Private.CoreLib.csproj (System.Private.CoreLib)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
#pragma warning disable 0420 //passing volatile fields by ref
 
using System.Diagnostics;
using System.Diagnostics.Tracing;
 
namespace System.Threading
{
    internal sealed class Condition
    {
        internal sealed class Waiter
        {
            public Waiter? next;
            public Waiter? prev;
            public AutoResetEvent ev = new AutoResetEvent(false);
            public bool signalled;
        }
 
        [ThreadStatic]
        private static Waiter? t_waiterForCurrentThread;
 
        private static Waiter GetWaiterForCurrentThread()
        {
            Waiter waiter = t_waiterForCurrentThread ??= new Waiter();
            waiter.signalled = false;
            return waiter;
        }
 
        private readonly Lock _lock;
        private Waiter? _waitersHead;
        private Waiter? _waitersTail;
 
        internal Lock AssociatedLock => _lock;
 
        private unsafe void AssertIsInList(Waiter waiter)
        {
            Debug.Assert(_waitersHead != null && _waitersTail != null);
            Debug.Assert((_waitersHead == waiter) == (waiter.prev == null));
            Debug.Assert((_waitersTail == waiter) == (waiter.next == null));
 
            for (Waiter? current = _waitersHead; current != null; current = current.next)
                if (current == waiter)
                    return;
            Debug.Fail("Waiter is not in the waiter list");
        }
 
        private unsafe void AssertIsNotInList(Waiter waiter)
        {
            Debug.Assert(waiter.next == null && waiter.prev == null);
            Debug.Assert((_waitersHead == null) == (_waitersTail == null));
 
            for (Waiter? current = _waitersHead; current != null; current = current.next)
                if (current == waiter)
                    Debug.Fail("Waiter is in the waiter list, but should not be");
        }
 
        private unsafe void AddWaiter(Waiter waiter)
        {
            Debug.Assert(_lock.IsHeldByCurrentThread);
            AssertIsNotInList(waiter);
 
            waiter.prev = _waitersTail;
            waiter.prev?.next = waiter;
 
            _waitersTail = waiter;
 
            _waitersHead ??= waiter;
        }
 
        private unsafe void RemoveWaiter(Waiter waiter)
        {
            Debug.Assert(_lock.IsHeldByCurrentThread);
            AssertIsInList(waiter);
 
            if (waiter.next != null)
                waiter.next.prev = waiter.prev;
            else
                _waitersTail = waiter.prev;
 
            if (waiter.prev != null)
                waiter.prev.next = waiter.next;
            else
                _waitersHead = waiter.next;
 
            waiter.next = null;
            waiter.prev = null;
        }
 
        public Condition(Lock @lock)
        {
#pragma warning disable CS9216 // A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement.
            ArgumentNullException.ThrowIfNull(@lock);
#pragma warning restore CS9216
            _lock = @lock;
        }
 
        public unsafe bool Wait(int millisecondsTimeout, object associatedObjectForMonitorWait)
        {
            ArgumentOutOfRangeException.ThrowIfLessThan(millisecondsTimeout, -1);
 
            if (!_lock.IsHeldByCurrentThread)
                throw new SynchronizationLockException();
 
            using ThreadBlockingInfo.Scope threadBlockingScope = new(this, millisecondsTimeout);
 
            Waiter waiter = GetWaiterForCurrentThread();
            AddWaiter(waiter);
 
            uint recursionCount = _lock.ExitAll();
            bool success = false;
            try
            {
                success =
                    waiter.ev.WaitOneNoCheck(
                        millisecondsTimeout,
                        false, // useTrivialWaits
                        associatedObjectForMonitorWait,
                        NativeRuntimeEventSource.WaitHandleWaitSourceMap.MonitorWait);
            }
            finally
            {
                _lock.Reenter(recursionCount);
                Debug.Assert(_lock.IsHeldByCurrentThread);
 
                if (!waiter.signalled)
                {
                    RemoveWaiter(waiter);
                }
                else if (!success)
                {
                    //
                    // The wait timed out, but we were signalled before we could reacquire the lock.
                    // Since WaitOne timed out, it didn't trigger the auto-reset of the AutoResetEvent.
                    // So, we need to manually reset the event.
                    //
                    waiter.ev.Reset();
                }
 
                AssertIsNotInList(waiter);
            }
 
            return waiter.signalled;
        }
 
        public unsafe void SignalAll()
        {
            if (!_lock.IsHeldByCurrentThread)
                throw new SynchronizationLockException();
 
            while (_waitersHead != null)
                SignalOne();
        }
 
        public unsafe void SignalOne()
        {
            if (!_lock.IsHeldByCurrentThread)
                throw new SynchronizationLockException();
 
            Waiter? waiter = _waitersHead;
            if (waiter != null)
            {
                RemoveWaiter(waiter);
                waiter.signalled = true;
                waiter.ev.Set();
            }
        }
    }
}