diff --git a/src/coreclr/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.Unix.cs b/src/coreclr/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.Unix.cs index 677cfb1718325..8fe80e7286858 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.Unix.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.Unix.cs @@ -11,7 +11,7 @@ namespace System.Threading /// /// A LIFO semaphore implemented using the PAL's semaphore with uninterruptible waits. /// - internal sealed partial class LowLevelLifoSemaphore : LowLevelLifoSemaphoreBase, IDisposable + internal sealed partial class LowLevelLifoSemaphore : IDisposable { private Semaphore? _semaphore; @@ -34,7 +34,7 @@ public bool WaitCore(int timeoutMs) [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "WaitHandle_CorWaitOnePrioritizedNative")] private static partial int WaitNative(SafeWaitHandle handle, int timeoutMs); - protected override void ReleaseCore(int count) + private void ReleaseCore(int count) { Debug.Assert(_semaphore != null); Debug.Assert(count > 0); diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.Unix.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.Unix.cs index 5046b6230104f..a0873fc273ff3 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.Unix.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.Unix.cs @@ -9,7 +9,7 @@ namespace System.Threading /// A LIFO semaphore. /// Waits on this semaphore are uninterruptible. /// - internal sealed partial class LowLevelLifoSemaphore : LowLevelLifoSemaphoreBase, IDisposable + internal sealed partial class LowLevelLifoSemaphore : IDisposable { private WaitSubsystem.WaitableObject _semaphore; @@ -27,7 +27,7 @@ private bool WaitCore(int timeoutMs) return WaitSubsystem.Wait(_semaphore, timeoutMs, false, true) == WaitHandle.WaitSuccess; } - protected override void ReleaseCore(int count) + private void ReleaseCore(int count) { WaitSubsystem.ReleaseSemaphore(_semaphore, count); } diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index aabc63e9e2e50..ff26f3bb27c8f 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -2685,7 +2685,6 @@ - @@ -2697,7 +2696,6 @@ - diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.Windows.cs index c9d5582e72759..cc64808ab9ca7 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.Windows.cs @@ -50,7 +50,7 @@ public bool WaitCore(int timeoutMs) return success; } - protected override void ReleaseCore(int count) + private void ReleaseCore(int count) { Debug.Assert(count > 0); diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.cs index 6975cfa6356f3..7f7bddf24737b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.cs @@ -11,13 +11,29 @@ namespace System.Threading /// A LIFO semaphore. /// Waits on this semaphore are uninterruptible. /// - internal sealed partial class LowLevelLifoSemaphore : LowLevelLifoSemaphoreBase, IDisposable + internal sealed partial class LowLevelLifoSemaphore : IDisposable { + private CacheLineSeparatedCounts _separated; + + private readonly int _maximumSignalCount; + private readonly int _spinCount; + private readonly Action _onWait; + private const int SpinSleep0Threshold = 10; public LowLevelLifoSemaphore(int initialSignalCount, int maximumSignalCount, int spinCount, Action onWait) - : base(initialSignalCount, maximumSignalCount, spinCount, onWait) { + Debug.Assert(initialSignalCount >= 0); + Debug.Assert(initialSignalCount <= maximumSignalCount); + Debug.Assert(maximumSignalCount > 0); + Debug.Assert(spinCount >= 0); + + _separated = default; + _separated._counts.SignalCount = (uint)initialSignalCount; + _maximumSignalCount = maximumSignalCount; + _spinCount = spinCount; + _onWait = onWait; + Create(maximumSignalCount); } @@ -185,5 +201,178 @@ private bool WaitForSignal(int timeoutMs) } } } + + public void Release(int releaseCount) + { + Debug.Assert(releaseCount > 0); + Debug.Assert(releaseCount <= _maximumSignalCount); + + int countOfWaitersToWake; + Counts counts = _separated._counts; + while (true) + { + Counts newCounts = counts; + + // Increase the signal count. The addition doesn't overflow because of the limit on the max signal count in constructor. + newCounts.AddSignalCount((uint)releaseCount); + + // Determine how many waiters to wake, taking into account how many spinners and waiters there are and how many waiters + // have previously been signaled to wake but have not yet woken + countOfWaitersToWake = + (int)Math.Min(newCounts.SignalCount, (uint)counts.WaiterCount + counts.SpinnerCount) - + counts.SpinnerCount - + counts.CountOfWaitersSignaledToWake; + if (countOfWaitersToWake > 0) + { + // Ideally, limiting to a maximum of releaseCount would not be necessary and could be an assert instead, but since + // WaitForSignal() does not have enough information to tell whether a woken thread was signaled, and due to the cap + // below, it's possible for countOfWaitersSignaledToWake to be less than the number of threads that have actually + // been signaled to wake. + if (countOfWaitersToWake > releaseCount) + { + countOfWaitersToWake = releaseCount; + } + + // Cap countOfWaitersSignaledToWake to its max value. It's ok to ignore some woken threads in this count, it just + // means some more threads will be woken next time. Typically, it won't reach the max anyway. + newCounts.AddUpToMaxCountOfWaitersSignaledToWake((uint)countOfWaitersToWake); + } + + Counts countsBeforeUpdate = _separated._counts.InterlockedCompareExchange(newCounts, counts); + if (countsBeforeUpdate == counts) + { + Debug.Assert(releaseCount <= _maximumSignalCount - counts.SignalCount); + if (countOfWaitersToWake > 0) + ReleaseCore(countOfWaitersToWake); + return; + } + + counts = countsBeforeUpdate; + } + } + + private struct Counts : IEquatable + { + private const byte SignalCountShift = 0; + private const byte WaiterCountShift = 32; + private const byte SpinnerCountShift = 48; + private const byte CountOfWaitersSignaledToWakeShift = 56; + + private ulong _data; + + private Counts(ulong data) => _data = data; + + private uint GetUInt32Value(byte shift) => (uint)(_data >> shift); + private void SetUInt32Value(uint value, byte shift) => + _data = (_data & ~((ulong)uint.MaxValue << shift)) | ((ulong)value << shift); + private ushort GetUInt16Value(byte shift) => (ushort)(_data >> shift); + private void SetUInt16Value(ushort value, byte shift) => + _data = (_data & ~((ulong)ushort.MaxValue << shift)) | ((ulong)value << shift); + private byte GetByteValue(byte shift) => (byte)(_data >> shift); + private void SetByteValue(byte value, byte shift) => + _data = (_data & ~((ulong)byte.MaxValue << shift)) | ((ulong)value << shift); + + public uint SignalCount + { + get => GetUInt32Value(SignalCountShift); + set => SetUInt32Value(value, SignalCountShift); + } + + public void AddSignalCount(uint value) + { + Debug.Assert(value <= uint.MaxValue - SignalCount); + _data += (ulong)value << SignalCountShift; + } + + public void IncrementSignalCount() => AddSignalCount(1); + + public void DecrementSignalCount() + { + Debug.Assert(SignalCount != 0); + _data -= (ulong)1 << SignalCountShift; + } + + public ushort WaiterCount + { + get => GetUInt16Value(WaiterCountShift); + set => SetUInt16Value(value, WaiterCountShift); + } + + public void IncrementWaiterCount() + { + Debug.Assert(WaiterCount < ushort.MaxValue); + _data += (ulong)1 << WaiterCountShift; + } + + public void DecrementWaiterCount() + { + Debug.Assert(WaiterCount != 0); + _data -= (ulong)1 << WaiterCountShift; + } + + public void InterlockedDecrementWaiterCount() + { + var countsAfterUpdate = new Counts(Interlocked.Add(ref _data, unchecked((ulong)-1) << WaiterCountShift)); + Debug.Assert(countsAfterUpdate.WaiterCount != ushort.MaxValue); // underflow check + } + + public byte SpinnerCount + { + get => GetByteValue(SpinnerCountShift); + set => SetByteValue(value, SpinnerCountShift); + } + + public void IncrementSpinnerCount() + { + Debug.Assert(SpinnerCount < byte.MaxValue); + _data += (ulong)1 << SpinnerCountShift; + } + + public void DecrementSpinnerCount() + { + Debug.Assert(SpinnerCount != 0); + _data -= (ulong)1 << SpinnerCountShift; + } + + public byte CountOfWaitersSignaledToWake + { + get => GetByteValue(CountOfWaitersSignaledToWakeShift); + set => SetByteValue(value, CountOfWaitersSignaledToWakeShift); + } + + public void AddUpToMaxCountOfWaitersSignaledToWake(uint value) + { + uint availableCount = (uint)(byte.MaxValue - CountOfWaitersSignaledToWake); + if (value > availableCount) + { + value = availableCount; + } + _data += (ulong)value << CountOfWaitersSignaledToWakeShift; + } + + public void DecrementCountOfWaitersSignaledToWake() + { + Debug.Assert(CountOfWaitersSignaledToWake != 0); + _data -= (ulong)1 << CountOfWaitersSignaledToWakeShift; + } + + public Counts InterlockedCompareExchange(Counts newCounts, Counts oldCounts) => + new Counts(Interlocked.CompareExchange(ref _data, newCounts._data, oldCounts._data)); + + public static bool operator ==(Counts lhs, Counts rhs) => lhs.Equals(rhs); + public static bool operator !=(Counts lhs, Counts rhs) => !lhs.Equals(rhs); + + public override bool Equals([NotNullWhen(true)] object? obj) => obj is Counts other && Equals(other); + public bool Equals(Counts other) => _data == other._data; + public override int GetHashCode() => (int)_data + (int)(_data >> 32); + } + + [StructLayout(LayoutKind.Sequential)] + private struct CacheLineSeparatedCounts + { + private readonly Internal.PaddingFor32 _pad1; + public Counts _counts; + private readonly Internal.PaddingFor32 _pad2; + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphoreBase.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphoreBase.cs deleted file mode 100644 index cb4df549b080d..0000000000000 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphoreBase.cs +++ /dev/null @@ -1,211 +0,0 @@ -// 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.Diagnostics.CodeAnalysis; -using System.Runtime.InteropServices; - -namespace System.Threading -{ - /// - /// A LIFO semaphore. - /// Waits on this semaphore are uninterruptible. - /// - internal abstract class LowLevelLifoSemaphoreBase - { - protected CacheLineSeparatedCounts _separated; - - protected readonly int _maximumSignalCount; - protected readonly int _spinCount; - protected readonly Action _onWait; - - public LowLevelLifoSemaphoreBase(int initialSignalCount, int maximumSignalCount, int spinCount, Action onWait) - { - Debug.Assert(initialSignalCount >= 0); - Debug.Assert(initialSignalCount <= maximumSignalCount); - Debug.Assert(maximumSignalCount > 0); - Debug.Assert(spinCount >= 0); - - _separated = default; - _separated._counts.SignalCount = (uint)initialSignalCount; - _maximumSignalCount = maximumSignalCount; - _spinCount = spinCount; - _onWait = onWait; - } - - protected abstract void ReleaseCore(int count); - - public void Release(int releaseCount) - { - Debug.Assert(releaseCount > 0); - Debug.Assert(releaseCount <= _maximumSignalCount); - - int countOfWaitersToWake; - Counts counts = _separated._counts; - while (true) - { - Counts newCounts = counts; - - // Increase the signal count. The addition doesn't overflow because of the limit on the max signal count in constructor. - newCounts.AddSignalCount((uint)releaseCount); - - // Determine how many waiters to wake, taking into account how many spinners and waiters there are and how many waiters - // have previously been signaled to wake but have not yet woken - countOfWaitersToWake = - (int)Math.Min(newCounts.SignalCount, (uint)counts.WaiterCount + counts.SpinnerCount) - - counts.SpinnerCount - - counts.CountOfWaitersSignaledToWake; - if (countOfWaitersToWake > 0) - { - // Ideally, limiting to a maximum of releaseCount would not be necessary and could be an assert instead, but since - // WaitForSignal() does not have enough information to tell whether a woken thread was signaled, and due to the cap - // below, it's possible for countOfWaitersSignaledToWake to be less than the number of threads that have actually - // been signaled to wake. - if (countOfWaitersToWake > releaseCount) - { - countOfWaitersToWake = releaseCount; - } - - // Cap countOfWaitersSignaledToWake to its max value. It's ok to ignore some woken threads in this count, it just - // means some more threads will be woken next time. Typically, it won't reach the max anyway. - newCounts.AddUpToMaxCountOfWaitersSignaledToWake((uint)countOfWaitersToWake); - } - - Counts countsBeforeUpdate = _separated._counts.InterlockedCompareExchange(newCounts, counts); - if (countsBeforeUpdate == counts) - { - Debug.Assert(releaseCount <= _maximumSignalCount - counts.SignalCount); - if (countOfWaitersToWake > 0) - ReleaseCore(countOfWaitersToWake); - return; - } - - counts = countsBeforeUpdate; - } - } - - protected struct Counts : IEquatable - { - private const byte SignalCountShift = 0; - private const byte WaiterCountShift = 32; - private const byte SpinnerCountShift = 48; - private const byte CountOfWaitersSignaledToWakeShift = 56; - - private ulong _data; - - private Counts(ulong data) => _data = data; - - private uint GetUInt32Value(byte shift) => (uint)(_data >> shift); - private void SetUInt32Value(uint value, byte shift) => - _data = (_data & ~((ulong)uint.MaxValue << shift)) | ((ulong)value << shift); - private ushort GetUInt16Value(byte shift) => (ushort)(_data >> shift); - private void SetUInt16Value(ushort value, byte shift) => - _data = (_data & ~((ulong)ushort.MaxValue << shift)) | ((ulong)value << shift); - private byte GetByteValue(byte shift) => (byte)(_data >> shift); - private void SetByteValue(byte value, byte shift) => - _data = (_data & ~((ulong)byte.MaxValue << shift)) | ((ulong)value << shift); - - public uint SignalCount - { - get => GetUInt32Value(SignalCountShift); - set => SetUInt32Value(value, SignalCountShift); - } - - public void AddSignalCount(uint value) - { - Debug.Assert(value <= uint.MaxValue - SignalCount); - _data += (ulong)value << SignalCountShift; - } - - public void IncrementSignalCount() => AddSignalCount(1); - - public void DecrementSignalCount() - { - Debug.Assert(SignalCount != 0); - _data -= (ulong)1 << SignalCountShift; - } - - public ushort WaiterCount - { - get => GetUInt16Value(WaiterCountShift); - set => SetUInt16Value(value, WaiterCountShift); - } - - public void IncrementWaiterCount() - { - Debug.Assert(WaiterCount < ushort.MaxValue); - _data += (ulong)1 << WaiterCountShift; - } - - public void DecrementWaiterCount() - { - Debug.Assert(WaiterCount != 0); - _data -= (ulong)1 << WaiterCountShift; - } - - public void InterlockedDecrementWaiterCount() - { - var countsAfterUpdate = new Counts(Interlocked.Add(ref _data, unchecked((ulong)-1) << WaiterCountShift)); - Debug.Assert(countsAfterUpdate.WaiterCount != ushort.MaxValue); // underflow check - } - - public byte SpinnerCount - { - get => GetByteValue(SpinnerCountShift); - set => SetByteValue(value, SpinnerCountShift); - } - - public void IncrementSpinnerCount() - { - Debug.Assert(SpinnerCount < byte.MaxValue); - _data += (ulong)1 << SpinnerCountShift; - } - - public void DecrementSpinnerCount() - { - Debug.Assert(SpinnerCount != 0); - _data -= (ulong)1 << SpinnerCountShift; - } - - public byte CountOfWaitersSignaledToWake - { - get => GetByteValue(CountOfWaitersSignaledToWakeShift); - set => SetByteValue(value, CountOfWaitersSignaledToWakeShift); - } - - public void AddUpToMaxCountOfWaitersSignaledToWake(uint value) - { - uint availableCount = (uint)(byte.MaxValue - CountOfWaitersSignaledToWake); - if (value > availableCount) - { - value = availableCount; - } - _data += (ulong)value << CountOfWaitersSignaledToWakeShift; - } - - public void DecrementCountOfWaitersSignaledToWake() - { - Debug.Assert(CountOfWaitersSignaledToWake != 0); - _data -= (ulong)1 << CountOfWaitersSignaledToWakeShift; - } - - public Counts InterlockedCompareExchange(Counts newCounts, Counts oldCounts) => - new Counts(Interlocked.CompareExchange(ref _data, newCounts._data, oldCounts._data)); - - public static bool operator ==(Counts lhs, Counts rhs) => lhs.Equals(rhs); - public static bool operator !=(Counts lhs, Counts rhs) => !lhs.Equals(rhs); - - public override bool Equals([NotNullWhen(true)] object? obj) => obj is Counts other && Equals(other); - public bool Equals(Counts other) => _data == other._data; - public override int GetHashCode() => (int)_data + (int)(_data >> 32); - } - - [StructLayout(LayoutKind.Sequential)] - protected struct CacheLineSeparatedCounts - { - private readonly Internal.PaddingFor32 _pad1; - public Counts _counts; - private readonly Internal.PaddingFor32 _pad2; - } - } -} diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.NonBrowser.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.NonBrowser.cs deleted file mode 100644 index c3b278019f6d9..0000000000000 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.NonBrowser.cs +++ /dev/null @@ -1,124 +0,0 @@ -// 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.Tracing; - -namespace System.Threading -{ - internal sealed partial class PortableThreadPool - { - private int _numThreadsBeingKeptAlive; - - /// - /// The worker thread infastructure for the CLR thread pool. - /// - private static partial class WorkerThread - { - private static readonly short ThreadsToKeepAlive = DetermineThreadsToKeepAlive(); - - private static short DetermineThreadsToKeepAlive() - { - const short DefaultThreadsToKeepAlive = 0; - - // The number of worker threads to keep alive after they are created. Set to -1 to keep all created worker - // threads alive. When the ThreadTimeoutMs config value is also set, for worker threads the timeout applies to - // worker threads that are in excess of the number configured for ThreadsToKeepAlive. - short threadsToKeepAlive = - AppContextConfigHelper.GetInt16Config( - "System.Threading.ThreadPool.ThreadsToKeepAlive", - "DOTNET_ThreadPool_ThreadsToKeepAlive", - DefaultThreadsToKeepAlive); - return threadsToKeepAlive >= -1 ? threadsToKeepAlive : DefaultThreadsToKeepAlive; - } - - /// - /// Semaphore for controlling how many threads are currently working. - /// - private static readonly LowLevelLifoSemaphore s_semaphore = - new LowLevelLifoSemaphore( - 0, - MaxPossibleThreadCount, - AppContextConfigHelper.GetInt32Config( - "System.Threading.ThreadPool.UnfairSemaphoreSpinLimit", - SemaphoreSpinCountDefault, - false), - onWait: () => - { - if (NativeRuntimeEventSource.Log.IsEnabled()) - { - NativeRuntimeEventSource.Log.ThreadPoolWorkerThreadWait( - (uint)ThreadPoolInstance._separated.counts.VolatileRead().NumExistingThreads); - } - }); - - private static readonly ThreadStart s_workerThreadStart = WorkerThreadStart; - - private static void WorkerThreadStart() - { - Thread.CurrentThread.SetThreadPoolWorkerThreadName(); - - PortableThreadPool threadPoolInstance = ThreadPoolInstance; - - if (NativeRuntimeEventSource.Log.IsEnabled()) - { - NativeRuntimeEventSource.Log.ThreadPoolWorkerThreadStart( - (uint)threadPoolInstance._separated.counts.VolatileRead().NumExistingThreads); - } - - LowLevelLock threadAdjustmentLock = threadPoolInstance._threadAdjustmentLock; - LowLevelLifoSemaphore semaphore = s_semaphore; - - // Determine the idle timeout to use for this thread. Some threads may always be kept alive based on config. - int timeoutMs = ThreadPoolThreadTimeoutMs; - if (ThreadsToKeepAlive != 0) - { - if (ThreadsToKeepAlive < 0) - { - timeoutMs = Timeout.Infinite; - } - else - { - int count = threadPoolInstance._numThreadsBeingKeptAlive; - while (count < ThreadsToKeepAlive) - { - int countBeforeUpdate = - Interlocked.CompareExchange(ref threadPoolInstance._numThreadsBeingKeptAlive, count + 1, count); - if (countBeforeUpdate == count) - { - timeoutMs = Timeout.Infinite; - break; - } - - count = countBeforeUpdate; - } - } - } - - while (true) - { - bool spinWait = true; - while (semaphore.Wait(timeoutMs, spinWait)) - { - WorkerDoWork(threadPoolInstance, ref spinWait); - } - - if (ShouldExitWorker(threadPoolInstance, threadAdjustmentLock)) - { - break; - } - } - } - - private static void CreateWorkerThread() - { - // Thread pool threads must start in the default execution context without transferring the context, so - // using UnsafeStart() instead of Start() - Thread workerThread = new Thread(s_workerThreadStart); - workerThread.IsThreadPoolThread = true; - workerThread.IsBackground = true; - // thread name will be set in thread proc - workerThread.UnsafeStart(); - } - } - } -} diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs index e776d05ff0ee4..40c14ae102c41 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs @@ -13,6 +13,8 @@ internal sealed partial class PortableThreadPool /// private static partial class WorkerThread { + private static readonly short ThreadsToKeepAlive = DetermineThreadsToKeepAlive(); + private const int SemaphoreSpinCountDefaultBaseline = 70; #if !TARGET_ARM64 && !TARGET_ARM && !TARGET_LOONGARCH64 private const int SemaphoreSpinCountDefault = SemaphoreSpinCountDefaultBaseline; @@ -29,6 +31,110 @@ private static partial class WorkerThread // preexisting threads from running out of memory when using new stack space in low-memory situations. public const int EstimatedAdditionalStackUsagePerThreadBytes = 64 << 10; // 64 KB + private static short DetermineThreadsToKeepAlive() + { + const short DefaultThreadsToKeepAlive = 0; + + // The number of worker threads to keep alive after they are created. Set to -1 to keep all created worker + // threads alive. When the ThreadTimeoutMs config value is also set, for worker threads the timeout applies to + // worker threads that are in excess of the number configured for ThreadsToKeepAlive. + short threadsToKeepAlive = + AppContextConfigHelper.GetInt16Config( + "System.Threading.ThreadPool.ThreadsToKeepAlive", + "DOTNET_ThreadPool_ThreadsToKeepAlive", + DefaultThreadsToKeepAlive); + return threadsToKeepAlive >= -1 ? threadsToKeepAlive : DefaultThreadsToKeepAlive; + } + + /// + /// Semaphore for controlling how many threads are currently working. + /// + private static readonly LowLevelLifoSemaphore s_semaphore = + new LowLevelLifoSemaphore( + 0, + MaxPossibleThreadCount, + AppContextConfigHelper.GetInt32Config( + "System.Threading.ThreadPool.UnfairSemaphoreSpinLimit", + SemaphoreSpinCountDefault, + false), + onWait: () => + { + if (NativeRuntimeEventSource.Log.IsEnabled()) + { + NativeRuntimeEventSource.Log.ThreadPoolWorkerThreadWait( + (uint)ThreadPoolInstance._separated.counts.VolatileRead().NumExistingThreads); + } + }); + + private static readonly ThreadStart s_workerThreadStart = WorkerThreadStart; + + private static void CreateWorkerThread() + { + // Thread pool threads must start in the default execution context without transferring the context, so + // using UnsafeStart() instead of Start() + Thread workerThread = new Thread(s_workerThreadStart); + workerThread.IsThreadPoolThread = true; + workerThread.IsBackground = true; + // thread name will be set in thread proc + workerThread.UnsafeStart(); + } + + private static void WorkerThreadStart() + { + Thread.CurrentThread.SetThreadPoolWorkerThreadName(); + + PortableThreadPool threadPoolInstance = ThreadPoolInstance; + + if (NativeRuntimeEventSource.Log.IsEnabled()) + { + NativeRuntimeEventSource.Log.ThreadPoolWorkerThreadStart( + (uint)threadPoolInstance._separated.counts.VolatileRead().NumExistingThreads); + } + + LowLevelLock threadAdjustmentLock = threadPoolInstance._threadAdjustmentLock; + LowLevelLifoSemaphore semaphore = s_semaphore; + + // Determine the idle timeout to use for this thread. Some threads may always be kept alive based on config. + int timeoutMs = ThreadPoolThreadTimeoutMs; + if (ThreadsToKeepAlive != 0) + { + if (ThreadsToKeepAlive < 0) + { + timeoutMs = Timeout.Infinite; + } + else + { + int count = threadPoolInstance._numThreadsBeingKeptAlive; + while (count < ThreadsToKeepAlive) + { + int countBeforeUpdate = + Interlocked.CompareExchange(ref threadPoolInstance._numThreadsBeingKeptAlive, count + 1, count); + if (countBeforeUpdate == count) + { + timeoutMs = Timeout.Infinite; + break; + } + + count = countBeforeUpdate; + } + } + } + + while (true) + { + bool spinWait = true; + while (semaphore.Wait(timeoutMs, spinWait)) + { + WorkerDoWork(threadPoolInstance, ref spinWait); + } + + if (ShouldExitWorker(threadPoolInstance, threadAdjustmentLock)) + { + break; + } + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void WorkerDoWork(PortableThreadPool threadPoolInstance, ref bool spinWait) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs index 51142edb03ce5..2523213110bae 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs @@ -72,6 +72,7 @@ private static int DetermineThreadPoolThreadTimeoutMs() private short _maxThreads; private short _legacy_minIOCompletionThreads; private short _legacy_maxIOCompletionThreads; + private int _numThreadsBeingKeptAlive; [StructLayout(LayoutKind.Explicit, Size = Internal.PaddingHelpers.CACHE_LINE_SIZE * 6)] private struct CacheLineSeparated diff --git a/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj index c273e6ba4172f..95f7f110cda50 100644 --- a/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -274,13 +274,10 @@ - + - - - diff --git a/src/mono/System.Private.CoreLib/src/System/Threading/LowLevelLifoAsyncWaitSemaphore.Browser.Threads.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Threading/LowLevelLifoAsyncWaitSemaphore.Browser.Threads.Mono.cs deleted file mode 100644 index 0bb2d90bba2d4..0000000000000 --- a/src/mono/System.Private.CoreLib/src/System/Threading/LowLevelLifoAsyncWaitSemaphore.Browser.Threads.Mono.cs +++ /dev/null @@ -1,223 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace System.Threading; - -// -// This class provides a way for browser threads to asynchronously wait for a semaphore -// from JS, without using the threadpool. It is used to implement threadpool workers. -// -internal sealed partial class LowLevelLifoAsyncWaitSemaphore : LowLevelLifoSemaphoreBase, IDisposable -{ - private IntPtr lifo_semaphore; - - [MethodImplAttribute(MethodImplOptions.InternalCall)] - private static extern IntPtr InitInternal(); - - public LowLevelLifoAsyncWaitSemaphore(int initialSignalCount, int maximumSignalCount, int spinCount, Action onWait) - : base (initialSignalCount, maximumSignalCount, spinCount, onWait) - { - CreateAsyncWait(maximumSignalCount); - } - -#pragma warning disable IDE0060 - private void CreateAsyncWait(int maximumSignalCount) -#pragma warning restore IDE0060 - { - lifo_semaphore = InitInternal(); - } - - [MethodImplAttribute(MethodImplOptions.InternalCall)] - private static extern void DeleteInternal(IntPtr semaphore); - - public void Dispose() - { - DeleteInternal(lifo_semaphore); - lifo_semaphore = IntPtr.Zero; - } - - [MethodImplAttribute(MethodImplOptions.InternalCall)] - private static extern void ReleaseInternal(IntPtr semaphore, int count); - - protected override void ReleaseCore(int count) - { - ReleaseInternal(lifo_semaphore, count); - } - - private sealed record WaitEntry (LowLevelLifoAsyncWaitSemaphore Semaphore, Action OnSuccess, Action OnTimeout, object? State) - { - public int TimeoutMs {get; internal set;} - public int StartWaitTicks {get; internal set; } - } - - public void PrepareAsyncWait(int timeoutMs, Action onSuccess, Action onTimeout, object? state) - { - Debug.Assert(timeoutMs >= -1); - - // Try to acquire the semaphore or - // a) register as a waiter and timeoutMs > 0 - // b) bail out if timeoutMs == 0 and return false - Counts counts = _separated._counts; - while (true) - { - Debug.Assert(counts.SignalCount <= _maximumSignalCount); - Counts newCounts = counts; - if (counts.SignalCount != 0) - { - newCounts.DecrementSignalCount(); - } - else if (timeoutMs != 0) - { - // Maximum number of spinners reached, register as a waiter instead - newCounts.IncrementWaiterCount(); - } - - Counts countsBeforeUpdate = _separated._counts.InterlockedCompareExchange(newCounts, counts); - if (countsBeforeUpdate == counts) - { - if (counts.SignalCount != 0) - { - onSuccess (this, state); - return; - } - if (newCounts.WaiterCount != counts.WaiterCount) - { - PrepareAsyncWaitForSignal(timeoutMs, onSuccess, onTimeout, state); - return; - } - if (timeoutMs == 0) - { - onTimeout (this, state); - return; - } - break; - } - - counts = countsBeforeUpdate; - } - - Debug.Fail("unreachable"); - } - - private void PrepareAsyncWaitForSignal(int timeoutMs, Action onSuccess, Action onTimeout, object? state) - { - Debug.Assert(timeoutMs > 0 || timeoutMs == -1); - - _onWait(); - - WaitEntry we = new WaitEntry(this, onSuccess, onTimeout, state) - { - TimeoutMs = timeoutMs, - StartWaitTicks = timeoutMs != -1 ? Environment.TickCount : 0, - }; - PrepareAsyncWaitCore(we); - // on success calls InternalAsyncWaitSuccess, on timeout calls InternalAsyncWaitTimeout - } - - private static void InternalAsyncWaitTimeout(LowLevelLifoAsyncWaitSemaphore self, WaitEntry internalWaitEntry) - { - WaitEntry we = internalWaitEntry!; - // Unregister the waiter. The wait subsystem used above guarantees that a thread that wakes due to a timeout does - // not observe a signal to the object being waited upon. - self._separated._counts.InterlockedDecrementWaiterCount(); - we.OnTimeout(self, we.State); - } - - private static void InternalAsyncWaitSuccess(LowLevelLifoAsyncWaitSemaphore self, WaitEntry internalWaitEntry) - { - WaitEntry we = internalWaitEntry!; - int endWaitTicks = we.TimeoutMs != -1 ? Environment.TickCount : 0; - // Unregister the waiter if this thread will not be waiting anymore, and try to acquire the semaphore - Counts counts = self._separated._counts; - while (true) - { - Debug.Assert(counts.WaiterCount != 0); - Counts newCounts = counts; - if (counts.SignalCount != 0) - { - newCounts.DecrementSignalCount(); - newCounts.DecrementWaiterCount(); - } - - // This waiter has woken up and this needs to be reflected in the count of waiters signaled to wake - if (counts.CountOfWaitersSignaledToWake != 0) - { - newCounts.DecrementCountOfWaitersSignaledToWake(); - } - - Counts countsBeforeUpdate = self._separated._counts.InterlockedCompareExchange(newCounts, counts); - if (countsBeforeUpdate == counts) - { - if (counts.SignalCount != 0) - { - we.OnSuccess(self, we.State); - return; - } - break; - } - - counts = countsBeforeUpdate; - } - // if we get here, we need to keep waiting because the SignalCount above was 0 after we did - // the CompareExchange - someone took the signal before us. - - if (we.TimeoutMs != -1) { - int waitMs = endWaitTicks - we.StartWaitTicks; - if (waitMs >= 0 && waitMs < we.TimeoutMs) - we.TimeoutMs -= waitMs; - else - we.TimeoutMs = 0; - we.StartWaitTicks = endWaitTicks; - } - PrepareAsyncWaitCore (we); - // on success calls InternalAsyncWaitSuccess, on timeout calls InternalAsyncWaitTimeout - } - - private static void PrepareAsyncWaitCore(WaitEntry internalWaitEntry) - { - int timeoutMs = internalWaitEntry.TimeoutMs; - LowLevelLifoAsyncWaitSemaphore semaphore = internalWaitEntry.Semaphore; - if (timeoutMs == 0) { - internalWaitEntry.OnTimeout (semaphore, internalWaitEntry.State); - return; - } - GCHandle gchandle = GCHandle.Alloc (internalWaitEntry); - unsafe { - delegate* unmanaged successCallback = &SuccessCallback; - delegate* unmanaged timeoutCallback = &TimeoutCallback; - PrepareAsyncWaitInternal (semaphore.lifo_semaphore, timeoutMs, successCallback, timeoutCallback, GCHandle.ToIntPtr(gchandle)); - } - } - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern unsafe void PrepareAsyncWaitInternal(IntPtr semaphore, - int timeoutMs, - /*delegate* unmanaged successCallback*/ void* successCallback, - /*delegate* unmanaged timeoutCallback*/ void* timeoutCallback, - IntPtr userData); - - [UnmanagedCallersOnly] - private static void SuccessCallback(IntPtr lifoSemaphore, IntPtr userData) - { - GCHandle gchandle = GCHandle.FromIntPtr(userData); - WaitEntry internalWaitEntry = (WaitEntry)gchandle.Target!; - gchandle.Free(); - InternalAsyncWaitSuccess(internalWaitEntry.Semaphore, internalWaitEntry); - } - - [UnmanagedCallersOnly] - private static void TimeoutCallback(IntPtr lifoSemaphore, IntPtr userData) - { - GCHandle gchandle = GCHandle.FromIntPtr(userData); - WaitEntry internalWaitEntry = (WaitEntry)gchandle.Target!; - gchandle.Free(); - InternalAsyncWaitTimeout(internalWaitEntry.Semaphore, internalWaitEntry); - } - -} diff --git a/src/mono/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.Unix.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.Unix.Mono.cs index 82365c61b8bb6..477ee0f08c9c5 100644 --- a/src/mono/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.Unix.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.Unix.Mono.cs @@ -5,7 +5,7 @@ namespace System.Threading { - internal sealed unsafe partial class LowLevelLifoSemaphore : LowLevelLifoSemaphoreBase, IDisposable + internal sealed unsafe partial class LowLevelLifoSemaphore : IDisposable { private IntPtr lifo_semaphore; @@ -39,7 +39,7 @@ private bool WaitCore(int timeoutMs) [MethodImplAttribute(MethodImplOptions.InternalCall)] private static extern void ReleaseInternal(IntPtr semaphore, int count); - protected override void ReleaseCore(int count) + private void ReleaseCore(int count) { ReleaseInternal(lifo_semaphore, count); } diff --git a/src/mono/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.Browser.Threads.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.Browser.Threads.Mono.cs deleted file mode 100644 index 82645c987d913..0000000000000 --- a/src/mono/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.Browser.Threads.Mono.cs +++ /dev/null @@ -1,114 +0,0 @@ -// 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.Diagnostics.CodeAnalysis; -using System.Diagnostics.Tracing; -using System.Runtime.CompilerServices; - -namespace System.Threading -{ - internal sealed partial class PortableThreadPool - { - /// - /// The worker thread infastructure for the CLR thread pool. - /// - private static partial class WorkerThread - { - /// - /// Semaphore for controlling how many threads are currently working. - /// - private static readonly LowLevelLifoAsyncWaitSemaphore s_semaphore = - new LowLevelLifoAsyncWaitSemaphore( - 0, - MaxPossibleThreadCount, - AppContextConfigHelper.GetInt32Config( - "System.Threading.ThreadPool.UnfairSemaphoreSpinLimit", - SemaphoreSpinCountDefault, - false), - onWait: () => - { - if (NativeRuntimeEventSource.Log.IsEnabled()) - { - NativeRuntimeEventSource.Log.ThreadPoolWorkerThreadWait( - (uint)ThreadPoolInstance._separated.counts.VolatileRead().NumExistingThreads); - } - }); - - private static readonly ThreadStart s_workerThreadStart = WorkerThreadStart; - - private sealed record SemaphoreWaitState(PortableThreadPool ThreadPoolInstance, LowLevelLock ThreadAdjustmentLock) - { - public bool SpinWait = true; - - public void ResetIteration() { - SpinWait = true; - } - } - - private static void WorkerThreadStart() - { - Thread.CurrentThread.SetThreadPoolWorkerThreadName(); - - PortableThreadPool threadPoolInstance = ThreadPoolInstance; - - if (NativeRuntimeEventSource.Log.IsEnabled()) - { - NativeRuntimeEventSource.Log.ThreadPoolWorkerThreadStart( - (uint)threadPoolInstance._separated.counts.VolatileRead().NumExistingThreads); - } - - LowLevelLock threadAdjustmentLock = threadPoolInstance._threadAdjustmentLock; - SemaphoreWaitState state = new(threadPoolInstance, threadAdjustmentLock) { SpinWait = true }; - // set up the callbacks for semaphore waits, tell - // emscripten to keep the thread alive, and return to - // the JS event loop. - WaitForWorkLoop(s_semaphore, state); - // return from thread start with keepalive - the thread will stay alive in the JS event loop - } - - private static readonly Action s_WorkLoopSemaphoreSuccess = new(WorkLoopSemaphoreSuccess); - private static readonly Action s_WorkLoopSemaphoreTimedOut = new(WorkLoopSemaphoreTimedOut); - - private static void WaitForWorkLoop(LowLevelLifoAsyncWaitSemaphore semaphore, SemaphoreWaitState state) - { - semaphore.PrepareAsyncWait(ThreadPoolThreadTimeoutMs, s_WorkLoopSemaphoreSuccess, s_WorkLoopSemaphoreTimedOut, state); - } - - private static void WorkLoopSemaphoreSuccess(LowLevelLifoAsyncWaitSemaphore semaphore, object? stateObject) - { - SemaphoreWaitState state = (SemaphoreWaitState)stateObject!; - WorkerDoWork(state.ThreadPoolInstance, ref state.SpinWait); - // Go around the loop one more time, keeping existing mutated state - WaitForWorkLoop(semaphore, state); - } - - private static void WorkLoopSemaphoreTimedOut(LowLevelLifoAsyncWaitSemaphore semaphore, object? stateObject) - { - SemaphoreWaitState state = (SemaphoreWaitState)stateObject!; - if (ShouldExitWorker(state.ThreadPoolInstance, state.ThreadAdjustmentLock)) { - // we're done, kill the thread. - return; - } else { - // more work showed up while we were shutting down, go around one more time - state.ResetIteration(); - WaitForWorkLoop(semaphore, state); - } - } - - private static void CreateWorkerThread() - { - // Thread pool threads must start in the default execution context without transferring the context, so - // using captureContext: false. - Thread workerThread = new Thread(s_workerThreadStart); - workerThread.IsThreadPoolThread = true; - workerThread.IsBackground = true; - // thread name will be set in thread proc - - // This thread will return to the JS event loop - tell the runtime not to cleanup - // after the start function returns, if the Emscripten keepalive is non-zero. - WebWorkerEventLoop.StartExitable(workerThread, captureContext: false); - } - } - } -} diff --git a/src/mono/System.Private.CoreLib/src/System/Threading/WebWorkerEventLoop.Browser.Threads.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Threading/WebWorkerEventLoop.Browser.Threads.Mono.cs deleted file mode 100644 index 367b1df00ee64..0000000000000 --- a/src/mono/System.Private.CoreLib/src/System/Threading/WebWorkerEventLoop.Browser.Threads.Mono.cs +++ /dev/null @@ -1,33 +0,0 @@ -// 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.CodeAnalysis; -using System.Diagnostics.Tracing; -using System.Runtime.CompilerServices; - -namespace System.Threading; - -/// -/// Keep a pthread alive in its WebWorker after its pthread start function returns. -/// -internal static class WebWorkerEventLoop -{ - /// - /// Start a thread that may be kept alive on its webworker after the start function returns, - /// if the emscripten keepalive count is positive. Once the thread returns to the JS event - /// loop it will be able to settle JS promises as well as run any queued managed async - /// callbacks. - /// - internal static void StartExitable(Thread thread, bool captureContext) - { - // don't support captureContext == true, for now, since it's - // not needed by PortableThreadPool.WorkerThread - if (captureContext) - throw new InvalidOperationException(); - // for now, threadpool threads are exitable, and nothing else is. - if (!thread.IsThreadPoolThread) - throw new InvalidOperationException(); - thread.HasExternalEventLoop = true; - thread.UnsafeStart(); - } -} diff --git a/src/mono/mono/metadata/icall-decl.h b/src/mono/mono/metadata/icall-decl.h index 6fd12507ec490..240ff111af0a4 100644 --- a/src/mono/mono/metadata/icall-decl.h +++ b/src/mono/mono/metadata/icall-decl.h @@ -189,13 +189,6 @@ ICALL_EXPORT void ves_icall_System_Threading_LowLevelLifoSemaphore_DeleteInt ICALL_EXPORT gint32 ves_icall_System_Threading_LowLevelLifoSemaphore_TimedWaitInternal (gpointer sem_ptr, gint32 timeout_ms); ICALL_EXPORT void ves_icall_System_Threading_LowLevelLifoSemaphore_ReleaseInternal (gpointer sem_ptr, gint32 count); -/* include these declarations if we're in the threaded wasm runtime, or if we're building a wasm-targeting cross compiler and we need to support --print-icall-table */ -#if (defined(HOST_BROWSER) && !defined(DISABLE_THREADS)) || (defined(TARGET_WASM) && defined(ENABLE_ICALL_SYMBOL_MAP)) -ICALL_EXPORT gpointer ves_icall_System_Threading_LowLevelLifoAsyncWaitSemaphore_InitInternal (void); -ICALL_EXPORT void ves_icall_System_Threading_LowLevelLifoAsyncWaitSemaphore_PrepareAsyncWaitInternal (gpointer sem_ptr, gint32 timeout_ms, gpointer success_cb, gpointer timeout_cb, intptr_t user_data); - -#endif - #ifdef TARGET_AMD64 ICALL_EXPORT void ves_icall_System_Runtime_Intrinsics_X86_X86Base___cpuidex (int abcd[4], int function_id, int subfunction_id); #endif diff --git a/src/mono/mono/metadata/icall-def.h b/src/mono/mono/metadata/icall-def.h index c741ccaa44732..b8f552dc72dcf 100644 --- a/src/mono/mono/metadata/icall-def.h +++ b/src/mono/mono/metadata/icall-def.h @@ -574,16 +574,6 @@ NOHANDLES(ICALL(ILOCK_21, "Increment(long&)", ves_icall_System_Threading_Interlo NOHANDLES(ICALL(ILOCK_22, "MemoryBarrierProcessWide", ves_icall_System_Threading_Interlocked_MemoryBarrierProcessWide)) NOHANDLES(ICALL(ILOCK_23, "Read(long&)", ves_icall_System_Threading_Interlocked_Read_Long)) -/* include these icalls if we're in the threaded wasm runtime, or if we're building a wasm-targeting cross compiler and we need to support --print-icall-table */ -#if (defined(HOST_BROWSER) && !defined(DISABLE_THREADS)) || (defined(TARGET_WASM) && defined(ENABLE_ICALL_SYMBOL_MAP)) -ICALL_TYPE(LIFOASYNCSEM, "System.Threading.LowLevelLifoAsyncWaitSemaphore", LIFOASYNCSEM_1) -NOHANDLES(ICALL(LIFOASYNCSEM_1, "DeleteInternal", ves_icall_System_Threading_LowLevelLifoSemaphore_DeleteInternal)) -NOHANDLES(ICALL(LIFOASYNCSEM_2, "InitInternal", ves_icall_System_Threading_LowLevelLifoAsyncWaitSemaphore_InitInternal)) -NOHANDLES(ICALL(LIFOASYNCSEM_3, "PrepareAsyncWaitInternal", ves_icall_System_Threading_LowLevelLifoAsyncWaitSemaphore_PrepareAsyncWaitInternal)) -NOHANDLES(ICALL(LIFOASYNCSEM_4, "ReleaseInternal", ves_icall_System_Threading_LowLevelLifoSemaphore_ReleaseInternal)) -#endif - - ICALL_TYPE(LIFOSEM, "System.Threading.LowLevelLifoSemaphore", LIFOSEM_1) NOHANDLES(ICALL(LIFOSEM_1, "DeleteInternal", ves_icall_System_Threading_LowLevelLifoSemaphore_DeleteInternal)) NOHANDLES(ICALL(LIFOSEM_2, "InitInternal", ves_icall_System_Threading_LowLevelLifoSemaphore_InitInternal)) diff --git a/src/mono/mono/metadata/threads.c b/src/mono/mono/metadata/threads.c index 74f1db84f0518..88c4628284b83 100644 --- a/src/mono/mono/metadata/threads.c +++ b/src/mono/mono/metadata/threads.c @@ -1242,8 +1242,7 @@ start_wrapper_internal (StartInfo *start_info, gsize *stack_ptr) if (G_UNLIKELY (external_eventloop)) { /* if the thread wants to stay alive in an external eventloop, don't clean up after it */ - if (mono_thread_platform_external_eventloop_keepalive_check ()) - return 0; // MONO_ENTER_GC_SAFE_UNBALANCED is done in start_wrapper + return 0; // MONO_ENTER_GC_SAFE_UNBALANCED is done in start_wrapper } /* Do any cleanup needed for apartment state. This @@ -1278,11 +1277,9 @@ start_wrapper (gpointer data) if (G_UNLIKELY (external_eventloop)) { /* if the thread wants to stay alive, don't clean up after it */ - if (mono_thread_platform_external_eventloop_keepalive_check ()) { - /* while we wait in the external eventloop, we're GC safe */ - MONO_ENTER_GC_SAFE_UNBALANCED; - return 0; - } + /* while we wait in the external eventloop, we're GC safe */ + MONO_ENTER_GC_SAFE_UNBALANCED; + return 0; } mono_thread_info_exit (res); @@ -4940,78 +4937,20 @@ ves_icall_System_Threading_LowLevelLifoSemaphore_InitInternal (void) void ves_icall_System_Threading_LowLevelLifoSemaphore_DeleteInternal (gpointer sem_ptr) { - LifoSemaphoreBase *sem = (LifoSemaphoreBase *)sem_ptr; - switch (sem->kind) { - case LIFO_SEMAPHORE_NORMAL: - mono_lifo_semaphore_delete ((LifoSemaphore*)sem); - break; -#if defined(HOST_BROWSER) && !defined(DISABLE_THREADS) - case LIFO_SEMAPHORE_ASYNCWAIT: - mono_lifo_semaphore_asyncwait_delete ((LifoSemaphoreAsyncWait*)sem); - break; -#endif - default: - g_assert_not_reached(); - } + LifoSemaphore *sem = (LifoSemaphore *)sem_ptr; + mono_lifo_semaphore_delete (sem); } gint32 ves_icall_System_Threading_LowLevelLifoSemaphore_TimedWaitInternal (gpointer sem_ptr, gint32 timeout_ms) { LifoSemaphore *sem = (LifoSemaphore *)sem_ptr; - g_assert (sem->base.kind == LIFO_SEMAPHORE_NORMAL); return mono_lifo_semaphore_timed_wait (sem, timeout_ms); } void ves_icall_System_Threading_LowLevelLifoSemaphore_ReleaseInternal (gpointer sem_ptr, gint32 count) { - LifoSemaphoreBase *sem = (LifoSemaphoreBase *)sem_ptr; - switch (sem->kind) { - case LIFO_SEMAPHORE_NORMAL: - mono_lifo_semaphore_release ((LifoSemaphore*)sem, count); - break; -#if defined(HOST_BROWSER) && !defined(DISABLE_THREADS) - case LIFO_SEMAPHORE_ASYNCWAIT: - mono_lifo_semaphore_asyncwait_release ((LifoSemaphoreAsyncWait*)sem, count); - break; -#endif - default: - g_assert_not_reached(); - } -} - -#if defined(HOST_BROWSER) && !defined(DISABLE_THREADS) -gpointer -ves_icall_System_Threading_LowLevelLifoAsyncWaitSemaphore_InitInternal (void) -{ - return (gpointer)mono_lifo_semaphore_asyncwait_init (); -} - -void -ves_icall_System_Threading_LowLevelLifoAsyncWaitSemaphore_PrepareAsyncWaitInternal (gpointer sem_ptr, gint32 timeout_ms, gpointer success_cb, gpointer timedout_cb, intptr_t user_data) -{ - LifoSemaphoreAsyncWait *sem = (LifoSemaphoreAsyncWait *)sem_ptr; - g_assert (sem->base.kind == LIFO_SEMAPHORE_ASYNCWAIT); - mono_lifo_semaphore_asyncwait_prepare_wait (sem, timeout_ms, (LifoSemaphoreAsyncWaitCallbackFn)success_cb, (LifoSemaphoreAsyncWaitCallbackFn)timedout_cb, user_data); -} - -#endif /* HOST_BROWSER && !DISABLE_THREADS */ - -/* for the AOT cross compiler with --print-icall-table these don't need to be callable, they just - * need to be defined */ -#if defined(TARGET_WASM) && defined(ENABLE_ICALL_SYMBOL_MAP) -gpointer -ves_icall_System_Threading_LowLevelLifoAsyncWaitSemaphore_InitInternal (void) -{ - g_assert_not_reached (); -} - -void -ves_icall_System_Threading_LowLevelLifoAsyncWaitSemaphore_PrepareAsyncWaitInternal (gpointer sem_ptr, gint32 timeout_ms, gpointer success_cb, gpointer timedout_cb, intptr_t user_data) -{ - g_assert_not_reached (); + LifoSemaphore *sem = (LifoSemaphore *)sem_ptr; + mono_lifo_semaphore_release (sem, count); } - -#endif /* defined(TARGET_WASM) && defined(ENABLE_ICALL_SYMBOL_MAP) */ - diff --git a/src/mono/mono/utils/lifo-semaphore.c b/src/mono/mono/utils/lifo-semaphore.c index dce67c48e8b37..1f3f6c4410b9f 100644 --- a/src/mono/mono/utils/lifo-semaphore.c +++ b/src/mono/mono/utils/lifo-semaphore.c @@ -11,11 +11,10 @@ LifoSemaphore * mono_lifo_semaphore_init (void) { LifoSemaphore *semaphore = g_new0 (LifoSemaphore, 1); - semaphore->base.kind = LIFO_SEMAPHORE_NORMAL; if (semaphore == NULL) return NULL; - mono_coop_mutex_init (&semaphore->base.mutex); + mono_coop_mutex_init (&semaphore->mutex); return semaphore; } @@ -24,7 +23,7 @@ void mono_lifo_semaphore_delete (LifoSemaphore *semaphore) { g_assert (semaphore->head == NULL); - mono_coop_mutex_destroy (&semaphore->base.mutex); + mono_coop_mutex_destroy (&semaphore->mutex); g_free (semaphore); } @@ -34,12 +33,12 @@ mono_lifo_semaphore_timed_wait (LifoSemaphore *semaphore, int32_t timeout_ms) LifoSemaphoreWaitEntry wait_entry = {0}; mono_coop_cond_init (&wait_entry.condition); - mono_coop_mutex_lock (&semaphore->base.mutex); + mono_coop_mutex_lock (&semaphore->mutex); - if (semaphore->base.pending_signals > 0) { - --semaphore->base.pending_signals; + if (semaphore->pending_signals > 0) { + --semaphore->pending_signals; mono_coop_cond_destroy (&wait_entry.condition); - mono_coop_mutex_unlock (&semaphore->base.mutex); + mono_coop_mutex_unlock (&semaphore->mutex); return 1; } @@ -53,7 +52,7 @@ mono_lifo_semaphore_timed_wait (LifoSemaphore *semaphore, int32_t timeout_ms) // Wait for a signal or timeout int wait_error = 0; do { - wait_error = mono_coop_cond_timedwait (&wait_entry.condition, &semaphore->base.mutex, timeout_ms); + wait_error = mono_coop_cond_timedwait (&wait_entry.condition, &semaphore->mutex, timeout_ms); } while (wait_error == 0 && !wait_entry.signaled); if (wait_error == -1) { @@ -66,7 +65,7 @@ mono_lifo_semaphore_timed_wait (LifoSemaphore *semaphore, int32_t timeout_ms) } mono_coop_cond_destroy (&wait_entry.condition); - mono_coop_mutex_unlock (&semaphore->base.mutex); + mono_coop_mutex_unlock (&semaphore->mutex); return wait_entry.signaled; } @@ -74,7 +73,7 @@ mono_lifo_semaphore_timed_wait (LifoSemaphore *semaphore, int32_t timeout_ms) void mono_lifo_semaphore_release (LifoSemaphore *semaphore, uint32_t count) { - mono_coop_mutex_lock (&semaphore->base.mutex); + mono_coop_mutex_lock (&semaphore->mutex); while (count > 0) { LifoSemaphoreWaitEntry *wait_entry = semaphore->head; @@ -88,243 +87,10 @@ mono_lifo_semaphore_release (LifoSemaphore *semaphore, uint32_t count) mono_coop_cond_signal (&wait_entry->condition); --count; } else { - semaphore->base.pending_signals += count; + semaphore->pending_signals += count; count = 0; } } - mono_coop_mutex_unlock (&semaphore->base.mutex); + mono_coop_mutex_unlock (&semaphore->mutex); } - -#if defined(HOST_BROWSER) && !defined(DISABLE_THREADS) - -LifoSemaphoreAsyncWait * -mono_lifo_semaphore_asyncwait_init (void) -{ - LifoSemaphoreAsyncWait *sem = g_new0 (LifoSemaphoreAsyncWait, 1); - if (sem == NULL) - return NULL; - sem->base.kind = LIFO_SEMAPHORE_ASYNCWAIT; - - mono_coop_mutex_init (&sem->base.mutex); - - return sem; -} - -void -mono_lifo_semaphore_asyncwait_delete (LifoSemaphoreAsyncWait *sem) -{ - /* FIXME: this is probably hard to guarantee - in-flight signaled semaphores still have wait entries */ - g_assert (sem->head == NULL); - mono_coop_mutex_destroy (&sem->base.mutex); - g_free (sem); -} - -enum { - LIFO_JS_WAITING = 0, - LIFO_JS_SIGNALED = 1, - LIFO_JS_SIGNALED_TIMEOUT_IGNORED = 2, - -}; - -static void -lifo_js_wait_entry_on_timeout (void *wait_entry_as_user_data); -static void -lifo_js_wait_entry_on_success (void *wait_entry_as_user_data); - - -static void -lifo_js_wait_entry_push (LifoSemaphoreAsyncWaitWaitEntry **head, - LifoSemaphoreAsyncWaitWaitEntry *entry) -{ - LifoSemaphoreAsyncWaitWaitEntry *next = *head; - *head = entry; - entry->next = next; - if (next) - next->previous = entry; -} - -static void -lifo_js_wait_entry_unlink (LifoSemaphoreAsyncWaitWaitEntry **head, - LifoSemaphoreAsyncWaitWaitEntry *entry) -{ - if (*head == entry) { - *head = entry->next; - } - if (entry->previous) { - entry->previous->next = entry->next; - } - if (entry->next) { - entry->next->previous = entry->previous; - } -} - -/* LOCKING: assumes semaphore is locked */ -static LifoSemaphoreAsyncWaitWaitEntry * -lifo_js_find_waiter (LifoSemaphoreAsyncWaitWaitEntry *entry) -{ - while (entry) { - if (entry->state == LIFO_JS_WAITING) - return entry; - entry = entry->next; - } - return NULL; -} - -static gboolean -lifo_js_wait_entry_no_thread (LifoSemaphoreAsyncWaitWaitEntry *entry, - pthread_t cur) -{ - while (entry) { - if (pthread_equal (entry->thread, cur)) - return FALSE; - entry = entry->next; - } - return TRUE; -} - -void -mono_lifo_semaphore_asyncwait_prepare_wait (LifoSemaphoreAsyncWait *sem, - int32_t timeout_ms, - LifoSemaphoreAsyncWaitCallbackFn success_cb, - LifoSemaphoreAsyncWaitCallbackFn timeout_cb, - intptr_t user_data) -{ - mono_coop_mutex_lock (&sem->base.mutex); - if (sem->base.pending_signals > 0) { - sem->base.pending_signals--; - mono_coop_mutex_unlock (&sem->base.mutex); - success_cb (sem, user_data); // FIXME: queue microtask - return; - } - - pthread_t cur = pthread_self (); - - /* Don't allow the current thread to wait multiple times. - * No particular reason for it, except that it makes reasoning a bit easier. - * This can probably be relaxed if there's a need. - */ - g_assert (lifo_js_wait_entry_no_thread(sem->head, cur)); - - LifoSemaphoreAsyncWaitWaitEntry *wait_entry = g_new0 (LifoSemaphoreAsyncWaitWaitEntry, 1); - wait_entry->success_cb = success_cb; - wait_entry->timeout_cb = timeout_cb; - wait_entry->sem = sem; - wait_entry->user_data = user_data; - wait_entry->thread = pthread_self(); - wait_entry->state = LIFO_JS_WAITING; - wait_entry->refcount = 1; // timeout owns the wait entry - wait_entry->js_timeout_id = emscripten_set_timeout (lifo_js_wait_entry_on_timeout, (double)timeout_ms, wait_entry); - lifo_js_wait_entry_push (&sem->head, wait_entry); - mono_coop_mutex_unlock (&sem->base.mutex); - return; -} - -void -mono_lifo_semaphore_asyncwait_release (LifoSemaphoreAsyncWait *sem, - uint32_t count) -{ - mono_coop_mutex_lock (&sem->base.mutex); - - while (count > 0) { - LifoSemaphoreAsyncWaitWaitEntry *wait_entry = lifo_js_find_waiter (sem->head); - if (wait_entry != NULL) { - /* found one. set its status and queue some work to run on the signaled thread */ - pthread_t target = wait_entry->thread; - wait_entry->state = LIFO_JS_SIGNALED; - wait_entry->refcount++; - // we're under the mutex - if we got here the timeout hasn't fired yet - g_assert (wait_entry->refcount == 2); - --count; - /* if we're on the same thread, don't run the callback while holding the lock */ - emscripten_dispatch_to_thread_async (target, EM_FUNC_SIG_VI, lifo_js_wait_entry_on_success, NULL, wait_entry); - } else { - sem->base.pending_signals += count; - count = 0; - } - } - - mono_coop_mutex_unlock (&sem->base.mutex); -} - -static void -lifo_js_wait_entry_on_timeout (void *wait_entry_as_user_data) -{ - LifoSemaphoreAsyncWaitWaitEntry *wait_entry = (LifoSemaphoreAsyncWaitWaitEntry *)wait_entry_as_user_data; - g_assert (pthread_equal (wait_entry->thread, pthread_self())); - g_assert (wait_entry->sem != NULL); - LifoSemaphoreAsyncWait *sem = wait_entry->sem; - gboolean call_timeout_cb = FALSE; - LifoSemaphoreAsyncWaitCallbackFn timeout_cb = NULL; - intptr_t user_data = 0; - MONO_ENTER_GC_UNSAFE; - mono_coop_mutex_lock (&sem->base.mutex); - switch (wait_entry->state) { - case LIFO_JS_WAITING: - /* semaphore timed out before a Release. */ - g_assert (wait_entry->refcount == 1); - /* unlink and free the wait entry, run the user timeout_cb. */ - lifo_js_wait_entry_unlink (&sem->head, wait_entry); - timeout_cb = wait_entry->timeout_cb; - user_data = wait_entry->user_data; - g_free (wait_entry); - call_timeout_cb = TRUE; - break; - case LIFO_JS_SIGNALED: - /* seamphore was signaled, but the timeout callback ran before the success callback arrived */ - g_assert (wait_entry->refcount == 2); - /* set state to LIFO_JS_SIGNALED_TIMEOUT_IGNORED, decrement refcount, return */ - wait_entry->state = LIFO_JS_SIGNALED_TIMEOUT_IGNORED; - wait_entry->refcount--; - break; - case LIFO_JS_SIGNALED_TIMEOUT_IGNORED: - default: - g_assert_not_reached(); - } - mono_coop_mutex_unlock (&sem->base.mutex); - if (call_timeout_cb) { - timeout_cb (sem, user_data); - } - MONO_EXIT_GC_UNSAFE; -} - -static void -lifo_js_wait_entry_on_success (void *wait_entry_as_user_data) -{ - LifoSemaphoreAsyncWaitWaitEntry *wait_entry = (LifoSemaphoreAsyncWaitWaitEntry *)wait_entry_as_user_data; - g_assert (pthread_equal (wait_entry->thread, pthread_self())); - g_assert (wait_entry->sem != NULL); - LifoSemaphoreAsyncWait *sem = wait_entry->sem; - gboolean call_success_cb = FALSE; - LifoSemaphoreAsyncWaitCallbackFn success_cb = NULL; - intptr_t user_data = 0; - MONO_ENTER_GC_UNSAFE; - mono_coop_mutex_lock (&sem->base.mutex); - switch (wait_entry->state) { - case LIFO_JS_SIGNALED: - g_assert (wait_entry->refcount == 2); - emscripten_clear_timeout (wait_entry->js_timeout_id); - /* emscripten safeSetTimeout calls keepalive push which is popped by the timeout - * callback. If we cancel the timeout, we have to pop the keepalive ourselves. */ - emscripten_runtime_keepalive_pop(); - wait_entry->refcount--; - /* fallthru */ - case LIFO_JS_SIGNALED_TIMEOUT_IGNORED: - g_assert (wait_entry->refcount == 1); - lifo_js_wait_entry_unlink (&sem->head, wait_entry); - success_cb = wait_entry->success_cb; - user_data = wait_entry->user_data; - g_free (wait_entry); - call_success_cb = TRUE; - break; - case LIFO_JS_WAITING: - default: - g_assert_not_reached(); - } - mono_coop_mutex_unlock (&sem->base.mutex); - g_assert (call_success_cb); - success_cb (sem, user_data); - MONO_EXIT_GC_UNSAFE; -} - -#endif /* HOST_BROWSER && !DISABLE_THREADS */ diff --git a/src/mono/mono/utils/lifo-semaphore.h b/src/mono/mono/utils/lifo-semaphore.h index 1a91a6f4d7c39..ad0492c6defb3 100644 --- a/src/mono/mono/utils/lifo-semaphore.h +++ b/src/mono/mono/utils/lifo-semaphore.h @@ -3,22 +3,6 @@ #include -typedef struct _LifoSemaphoreBase LifoSemaphoreBase; - -struct _LifoSemaphoreBase -{ - MonoCoopMutex mutex; - uint32_t pending_signals; - uint8_t kind; -}; - -enum { - LIFO_SEMAPHORE_NORMAL = 1, -#if defined(HOST_BROWSER) && !defined(DISABLE_THREADS) - LIFO_SEMAPHORE_ASYNCWAIT, -#endif -}; - typedef struct _LifoSemaphore LifoSemaphore; typedef struct _LifoSemaphoreWaitEntry LifoSemaphoreWaitEntry; @@ -30,7 +14,8 @@ struct _LifoSemaphoreWaitEntry { }; struct _LifoSemaphore { - LifoSemaphoreBase base; + MonoCoopMutex mutex; + uint32_t pending_signals; LifoSemaphoreWaitEntry *head; }; @@ -46,91 +31,4 @@ mono_lifo_semaphore_timed_wait (LifoSemaphore *semaphore, int32_t timeout_ms); void mono_lifo_semaphore_release (LifoSemaphore *semaphore, uint32_t count); -#if defined(HOST_BROWSER) && !defined(DISABLE_THREADS) -/* A type of lifo semaphore that can be waited from the JS event loop. - * - * Instead of a blocking timed_wait function, it uses a pair of callbacks: a success callback and a - * timeout callback. The wait function returns immediately and the callbacks will fire on the JS - * event loop when the semaphore is released or the timeout expires. - */ -typedef struct _LifoSemaphoreAsyncWait LifoSemaphoreAsyncWait; -/* - * Because the callbacks are asynchronous, it's possible for the same thread to attempt to wait - * multiple times for the same semaphore. For simplicity of reasoning, we dissallow that and - * assert. In principle we could support it, but we haven't implemented that. - */ -typedef struct _LifoSemaphoreAsyncWaitWaitEntry LifoSemaphoreAsyncWaitWaitEntry; - -typedef void (*LifoSemaphoreAsyncWaitCallbackFn)(LifoSemaphoreAsyncWait *semaphore, intptr_t user_data); - -struct _LifoSemaphoreAsyncWaitWaitEntry { - LifoSemaphoreAsyncWaitWaitEntry *previous; - LifoSemaphoreAsyncWaitWaitEntry *next; - LifoSemaphoreAsyncWaitCallbackFn success_cb; - LifoSemaphoreAsyncWaitCallbackFn timeout_cb; - LifoSemaphoreAsyncWait *sem; - intptr_t user_data; - pthread_t thread; - int32_t js_timeout_id; // only valid to access from the waiting thread - /* state and refcount are protected by the semaphore mutex */ - uint16_t state; /* 0 waiting, 1 signaled, 2 signaled - timeout ignored */ - uint16_t refcount; /* 1 if waiting, 2 if signaled, 1 if timeout fired while signaled and we're ignoring the timeout */ -}; - -struct _LifoSemaphoreAsyncWait { - LifoSemaphoreBase base; - LifoSemaphoreAsyncWaitWaitEntry *head; -}; - -LifoSemaphoreAsyncWait * -mono_lifo_semaphore_asyncwait_init (void); - -/* what to do with waiters? - * might be kind of academic - we don't expect to destroy these - */ -void -mono_lifo_semaphore_asyncwait_delete (LifoSemaphoreAsyncWait *semaphore); - -/* - * the timeout_cb is triggered by a JS setTimeout callback - * - * the success_cb is triggered using Emscripten's capability to push async work from one thread to - * another. That means the main thread will need to be able to process JS events (in order to - * assist threads in pushing work from one thread to another) in order for success callbacks to - * function. Emscripten also pumps the async work queues in other circumstances (during sleeps) but - * the main thread still needs to participate. - * - * There's a potential race the implementation needs to be careful about: - * when one thread releases a semaphore and queues the success callback to run, - * while the success callback is in flight, the timeout callback can fire. - * It is important that the called back functions don't destroy the wait entry until either both - * callbacks have fired, or the success callback has a chance to cancel the timeout callback. - * - * We use a refcount to delimit the lifetime of the wait entry. When the wait is created, the - * refcount is 1 and it is notionally owned by the timeout callback. When a sempahore is released, - * the refcount goes to 2. When a continuation fires, it decreases the refcount. If the timeout - * callback fires first if it sees a refcount of 2 it can decrement and return - we know a success - * continuation is in flight and we can allow it to complete. If the refcount is 1 we need to take the semaphore's mutex and remove the wait entry. (With double check locking - the refcount could go up). - * - * When the success continuation fires,it will examine the refcount. If the refcount is 1 at the - * outset, then the cancelation already tried to fire while we were in flight. If the refcount is 2 - * at the outset, then the success contination fired before the timeout, so we can cancel the - * timeout. In either case we can remove the wait entry. - * - * Both the success and timeout code only calls the user provided callbacks after the wait entry is - * destroyed. - * - * FIXME: should we just always use the mutex to protect the wait entry status+refcount? - */ -void -mono_lifo_semaphore_asyncwait_prepare_wait (LifoSemaphoreAsyncWait *semaphore, int32_t timeout_ms, - LifoSemaphoreAsyncWaitCallbackFn success_cb, - LifoSemaphoreAsyncWaitCallbackFn timeout_cb, - intptr_t user_data); - -void -mono_lifo_semaphore_asyncwait_release (LifoSemaphoreAsyncWait *semaphore, uint32_t count); - -#endif /* HOST_BROWSER && !DISABLE_THREADS */ - #endif // __MONO_LIFO_SEMAPHORE_H__ diff --git a/src/mono/mono/utils/mono-threads-posix.c b/src/mono/mono/utils/mono-threads-posix.c index 23c1e4f3056aa..ea4cf7f90fb5d 100644 --- a/src/mono/mono/utils/mono-threads-posix.c +++ b/src/mono/mono/utils/mono-threads-posix.c @@ -133,15 +133,6 @@ mono_threads_platform_exit (gsize exit_code) pthread_exit ((gpointer) exit_code); } -gboolean -mono_thread_platform_external_eventloop_keepalive_check (void) -{ - /* vanilla POSIX thread creation doesn't support an external eventloop: when the thread main - function returns, the thread is done. - */ - return FALSE; -} - #if HOST_FUCHSIA int mono_thread_info_get_system_max_stack_size (void) diff --git a/src/mono/mono/utils/mono-threads-wasm.c b/src/mono/mono/utils/mono-threads-wasm.c index caded5fb60969..8ba904a5f6a0f 100644 --- a/src/mono/mono/utils/mono-threads-wasm.c +++ b/src/mono/mono/utils/mono-threads-wasm.c @@ -315,21 +315,6 @@ mono_thread_platform_create_thread (MonoThreadStart thread_fn, gpointer thread_d #endif } -gboolean -mono_thread_platform_external_eventloop_keepalive_check (void) -{ -#if defined(HOST_BROWSER) && !defined(DISABLE_THREADS) - MONO_REQ_GC_SAFE_MODE; - /* if someone called emscripten_runtime_keepalive_push (), the - * thread will stay alive in the JS event loop after returning - * from the thread's main function. - */ - return emscripten_runtime_keepalive_check (); -#else - return FALSE; -#endif -} - void mono_threads_platform_init (void) { } diff --git a/src/mono/mono/utils/mono-threads-windows.c b/src/mono/mono/utils/mono-threads-windows.c index 169449b831e83..3e56205c0ab88 100644 --- a/src/mono/mono/utils/mono-threads-windows.c +++ b/src/mono/mono/utils/mono-threads-windows.c @@ -501,15 +501,6 @@ typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL); static gboolean is_wow64 = FALSE; #endif -gboolean -mono_thread_platform_external_eventloop_keepalive_check (void) -{ - /* We don't support thread creation with an external eventloop on WIN32: when the thread start - function returns, the thread is done. - */ - return FALSE; -} - /* We do this at init time to avoid potential races with module opening */ void mono_threads_platform_init (void) diff --git a/src/mono/mono/utils/mono-threads.h b/src/mono/mono/utils/mono-threads.h index a738460f58950..08db699e1d605 100644 --- a/src/mono/mono/utils/mono-threads.h +++ b/src/mono/mono/utils/mono-threads.h @@ -634,9 +634,6 @@ gboolean mono_threads_platform_in_critical_region (THREAD_INFO_TYPE *info); gboolean mono_threads_platform_yield (void); void mono_threads_platform_exit (gsize exit_code); -gboolean -mono_thread_platform_external_eventloop_keepalive_check (void); - void mono_threads_coop_begin_global_suspend (void); void mono_threads_coop_end_global_suspend (void);