File: src\runtime\src\libraries\System.Private.CoreLib\src\System\Threading\LowLevelThreadBlocker.cs
Web Access
Project: src\runtime\src\coreclr\nativeaot\System.Private.CoreLib\src\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.

#if TARGET_LINUX
// use OS-provided compare-and-wait API.
#define USE_FUTEX
#elif TARGET_WINDOWS
#define USE_FUTEX
#else
// fallback to monitor (condvar+mutex)
#define USE_MONITOR
#endif

using System.Diagnostics;
using System.Runtime.InteropServices;

namespace System.Threading
{
    /// <summary>
    /// Portable lightweight API to block and wake threads.
    /// The blocker is low-level. It is not alertable by APCs and not interruptible by
    /// Thread.Interrupt.
    /// Functionally, the blocker is similar to a counting semaphore with MaxInt max count.
    /// We count Wakes and Wait consumes them or blocks until wake count is != 0.
    /// Waking policy is as fair as provided by the underlying APIs. That is typically
    /// FIFO or close to FIFO, but rarely documented explicitly.
    /// When OS provides a compare-and-wait API, such as futex, we use that.
    /// Otherwise we fallback to a heavier, but more portable condvar/mutex implementation.
    /// </summary>
    internal unsafe struct LowLevelThreadBlocker : IDisposable
    {
        private int* _pState;

#if USE_MONITOR
        private LowLevelMonitor _monitor;
#endif

        public LowLevelThreadBlocker()
        {
            _pState = (int*)NativeMemory.AlignedAlloc(Internal.PaddingHelpers.CACHE_LINE_SIZE, Internal.PaddingHelpers.CACHE_LINE_SIZE);
            *_pState = 0;

#if USE_MONITOR
            try
            {
                _monitor.Initialize();
            }
            catch
            {
                NativeMemory.AlignedFree(_pState);
                _pState = null;
                throw;
            }
#endif
        }

        public void Dispose()
        {
            if (_pState == null)
            {
                return;
            }

            NativeMemory.AlignedFree(_pState);
            _pState = null;

#if USE_MONITOR
            _monitor.Dispose();
#endif
        }

#if USE_MONITOR
        internal void Wait()
        {
            _monitor.Acquire();

            int originalState = *_pState;
            while (originalState == 0)
            {
                _monitor.Wait();
                originalState = *_pState;
            }

            *_pState = originalState - 1;
            _monitor.Release();
        }

        internal bool TimedWait(int timeoutMs)
        {
            Debug.Assert(timeoutMs >= -1);
            if (timeoutMs == -1)
            {
                Wait();
                return true;
            }

            long deadline = Environment.TickCount64 + timeoutMs;
            _monitor.Acquire();

            int originalState = *_pState;
            while (originalState == 0)
            {
                if (!_monitor.Wait(timeoutMs) ||
                    (timeoutMs = (int)(deadline - Environment.TickCount64)) < 0)
                {
                    _monitor.Release();
                    return false;
                }

                originalState = *_pState;
            }

            *_pState = originalState - 1;
            _monitor.Release();
            return true;
        }

        internal void WakeOne()
        {
            _monitor.Acquire();
            *_pState = *_pState + 1;
            _monitor.Signal_Release();
        }
#else
        internal void Wait()
        {
            while (true)
            {
                int originalState = *_pState;
                while (originalState == 0)
                {
                    LowLevelFutex.WaitOnAddress(_pState, originalState);
                    originalState = *_pState;
                }

                if (Interlocked.CompareExchange(ref *_pState, originalState - 1, originalState) == originalState)
                {
                    return;
                }
            }
        }

        internal bool TimedWait(int timeoutMs)
        {
            Debug.Assert(timeoutMs >= -1);
            if (timeoutMs == -1)
            {
                Wait();
                return true;
            }

            long deadline = Environment.TickCount64 + timeoutMs;
            while (true)
            {
                int originalState = *_pState;
                while (originalState == 0)
                {
                    if (!LowLevelFutex.WaitOnAddressTimeout(_pState, originalState, timeoutMs))
                    {
                        return false;
                    }

                    timeoutMs = (int)(deadline - Environment.TickCount64);
                    if (timeoutMs < 0)
                    {
                        return false;
                    }

                    originalState = *_pState;
                }

                if (Interlocked.CompareExchange(ref *_pState, originalState - 1, originalState) == originalState)
                {
                    return true;
                }
            }
        }

        internal void WakeOne()
        {
            Interlocked.Increment(ref *_pState);
            LowLevelFutex.WakeByAddressSingle(_pState);
        }
#endif
    }
}