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);