Skip to content

Commit d9b9eb9

Browse files
authored
[browser][MT] use regular POSIX portable threadpool (#99836)
1 parent adb639c commit d9b9eb9

23 files changed

+327
-1191
lines changed

src/coreclr/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.Unix.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ namespace System.Threading
1111
/// <summary>
1212
/// A LIFO semaphore implemented using the PAL's semaphore with uninterruptible waits.
1313
/// </summary>
14-
internal sealed partial class LowLevelLifoSemaphore : LowLevelLifoSemaphoreBase, IDisposable
14+
internal sealed partial class LowLevelLifoSemaphore : IDisposable
1515
{
1616
private Semaphore? _semaphore;
1717

@@ -34,7 +34,7 @@ public bool WaitCore(int timeoutMs)
3434
[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "WaitHandle_CorWaitOnePrioritizedNative")]
3535
private static partial int WaitNative(SafeWaitHandle handle, int timeoutMs);
3636

37-
protected override void ReleaseCore(int count)
37+
private void ReleaseCore(int count)
3838
{
3939
Debug.Assert(_semaphore != null);
4040
Debug.Assert(count > 0);

src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.Unix.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ namespace System.Threading
99
/// A LIFO semaphore.
1010
/// Waits on this semaphore are uninterruptible.
1111
/// </summary>
12-
internal sealed partial class LowLevelLifoSemaphore : LowLevelLifoSemaphoreBase, IDisposable
12+
internal sealed partial class LowLevelLifoSemaphore : IDisposable
1313
{
1414
private WaitSubsystem.WaitableObject _semaphore;
1515

@@ -27,7 +27,7 @@ private bool WaitCore(int timeoutMs)
2727
return WaitSubsystem.Wait(_semaphore, timeoutMs, false, true) == WaitHandle.WaitSuccess;
2828
}
2929

30-
protected override void ReleaseCore(int count)
30+
private void ReleaseCore(int count)
3131
{
3232
WaitSubsystem.ReleaseSemaphore(_semaphore, count);
3333
}

src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2685,7 +2685,6 @@
26852685
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\PortableThreadPool.ThreadCounts.cs" />
26862686
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\PortableThreadPool.WaitThread.cs" />
26872687
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\PortableThreadPool.WorkerThread.cs" />
2688-
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\PortableThreadPool.WorkerThread.NonBrowser.cs" Condition="'$(TargetsBrowser)' != 'true'" />
26892688
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\PortableThreadPool.WorkerTracking.cs" />
26902689
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\PortableThreadPool.Unix.cs" Condition="'$(TargetsUnix)' == 'true' or ('$(TargetsBrowser)' == 'true' and '$(FeatureWasmManagedThreads)' != 'true') or '$(TargetsWasi)' == 'true'" />
26912690
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\PortableThreadPool.Windows.cs" Condition="'$(TargetsWindows)' == 'true'" />
@@ -2697,7 +2696,6 @@
26972696
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\RegisteredWaitHandle.Windows.cs" Condition="'$(TargetsWindows)' == 'true'" />
26982697
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\RegisteredWaitHandle.Unix.cs" Condition="'$(TargetsWindows)' != 'true'" />
26992698
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\RegisteredWaitHandle.Portable.cs" />
2700-
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\LowLevelLifoSemaphoreBase.cs" />
27012699
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\ThreadPoolBoundHandle.Portable.cs" />
27022700
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\ThreadPoolBoundHandle.Unix.cs" Condition="'$(TargetsWindows)' != 'true'" />
27032701
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\ThreadPoolBoundHandle.Windows.cs" Condition="'$(TargetsWindows)' == 'true'" />

src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.Windows.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public bool WaitCore(int timeoutMs)
5050
return success;
5151
}
5252

53-
protected override void ReleaseCore(int count)
53+
private void ReleaseCore(int count)
5454
{
5555
Debug.Assert(count > 0);
5656

src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.cs

Lines changed: 191 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,29 @@ namespace System.Threading
1111
/// A LIFO semaphore.
1212
/// Waits on this semaphore are uninterruptible.
1313
/// </summary>
14-
internal sealed partial class LowLevelLifoSemaphore : LowLevelLifoSemaphoreBase, IDisposable
14+
internal sealed partial class LowLevelLifoSemaphore : IDisposable
1515
{
16+
private CacheLineSeparatedCounts _separated;
17+
18+
private readonly int _maximumSignalCount;
19+
private readonly int _spinCount;
20+
private readonly Action _onWait;
21+
1622
private const int SpinSleep0Threshold = 10;
1723

1824
public LowLevelLifoSemaphore(int initialSignalCount, int maximumSignalCount, int spinCount, Action onWait)
19-
: base(initialSignalCount, maximumSignalCount, spinCount, onWait)
2025
{
26+
Debug.Assert(initialSignalCount >= 0);
27+
Debug.Assert(initialSignalCount <= maximumSignalCount);
28+
Debug.Assert(maximumSignalCount > 0);
29+
Debug.Assert(spinCount >= 0);
30+
31+
_separated = default;
32+
_separated._counts.SignalCount = (uint)initialSignalCount;
33+
_maximumSignalCount = maximumSignalCount;
34+
_spinCount = spinCount;
35+
_onWait = onWait;
36+
2137
Create(maximumSignalCount);
2238
}
2339

@@ -185,5 +201,178 @@ private bool WaitForSignal(int timeoutMs)
185201
}
186202
}
187203
}
204+
205+
public void Release(int releaseCount)
206+
{
207+
Debug.Assert(releaseCount > 0);
208+
Debug.Assert(releaseCount <= _maximumSignalCount);
209+
210+
int countOfWaitersToWake;
211+
Counts counts = _separated._counts;
212+
while (true)
213+
{
214+
Counts newCounts = counts;
215+
216+
// Increase the signal count. The addition doesn't overflow because of the limit on the max signal count in constructor.
217+
newCounts.AddSignalCount((uint)releaseCount);
218+
219+
// Determine how many waiters to wake, taking into account how many spinners and waiters there are and how many waiters
220+
// have previously been signaled to wake but have not yet woken
221+
countOfWaitersToWake =
222+
(int)Math.Min(newCounts.SignalCount, (uint)counts.WaiterCount + counts.SpinnerCount) -
223+
counts.SpinnerCount -
224+
counts.CountOfWaitersSignaledToWake;
225+
if (countOfWaitersToWake > 0)
226+
{
227+
// Ideally, limiting to a maximum of releaseCount would not be necessary and could be an assert instead, but since
228+
// WaitForSignal() does not have enough information to tell whether a woken thread was signaled, and due to the cap
229+
// below, it's possible for countOfWaitersSignaledToWake to be less than the number of threads that have actually
230+
// been signaled to wake.
231+
if (countOfWaitersToWake > releaseCount)
232+
{
233+
countOfWaitersToWake = releaseCount;
234+
}
235+
236+
// Cap countOfWaitersSignaledToWake to its max value. It's ok to ignore some woken threads in this count, it just
237+
// means some more threads will be woken next time. Typically, it won't reach the max anyway.
238+
newCounts.AddUpToMaxCountOfWaitersSignaledToWake((uint)countOfWaitersToWake);
239+
}
240+
241+
Counts countsBeforeUpdate = _separated._counts.InterlockedCompareExchange(newCounts, counts);
242+
if (countsBeforeUpdate == counts)
243+
{
244+
Debug.Assert(releaseCount <= _maximumSignalCount - counts.SignalCount);
245+
if (countOfWaitersToWake > 0)
246+
ReleaseCore(countOfWaitersToWake);
247+
return;
248+
}
249+
250+
counts = countsBeforeUpdate;
251+
}
252+
}
253+
254+
private struct Counts : IEquatable<Counts>
255+
{
256+
private const byte SignalCountShift = 0;
257+
private const byte WaiterCountShift = 32;
258+
private const byte SpinnerCountShift = 48;
259+
private const byte CountOfWaitersSignaledToWakeShift = 56;
260+
261+
private ulong _data;
262+
263+
private Counts(ulong data) => _data = data;
264+
265+
private uint GetUInt32Value(byte shift) => (uint)(_data >> shift);
266+
private void SetUInt32Value(uint value, byte shift) =>
267+
_data = (_data & ~((ulong)uint.MaxValue << shift)) | ((ulong)value << shift);
268+
private ushort GetUInt16Value(byte shift) => (ushort)(_data >> shift);
269+
private void SetUInt16Value(ushort value, byte shift) =>
270+
_data = (_data & ~((ulong)ushort.MaxValue << shift)) | ((ulong)value << shift);
271+
private byte GetByteValue(byte shift) => (byte)(_data >> shift);
272+
private void SetByteValue(byte value, byte shift) =>
273+
_data = (_data & ~((ulong)byte.MaxValue << shift)) | ((ulong)value << shift);
274+
275+
public uint SignalCount
276+
{
277+
get => GetUInt32Value(SignalCountShift);
278+
set => SetUInt32Value(value, SignalCountShift);
279+
}
280+
281+
public void AddSignalCount(uint value)
282+
{
283+
Debug.Assert(value <= uint.MaxValue - SignalCount);
284+
_data += (ulong)value << SignalCountShift;
285+
}
286+
287+
public void IncrementSignalCount() => AddSignalCount(1);
288+
289+
public void DecrementSignalCount()
290+
{
291+
Debug.Assert(SignalCount != 0);
292+
_data -= (ulong)1 << SignalCountShift;
293+
}
294+
295+
public ushort WaiterCount
296+
{
297+
get => GetUInt16Value(WaiterCountShift);
298+
set => SetUInt16Value(value, WaiterCountShift);
299+
}
300+
301+
public void IncrementWaiterCount()
302+
{
303+
Debug.Assert(WaiterCount < ushort.MaxValue);
304+
_data += (ulong)1 << WaiterCountShift;
305+
}
306+
307+
public void DecrementWaiterCount()
308+
{
309+
Debug.Assert(WaiterCount != 0);
310+
_data -= (ulong)1 << WaiterCountShift;
311+
}
312+
313+
public void InterlockedDecrementWaiterCount()
314+
{
315+
var countsAfterUpdate = new Counts(Interlocked.Add(ref _data, unchecked((ulong)-1) << WaiterCountShift));
316+
Debug.Assert(countsAfterUpdate.WaiterCount != ushort.MaxValue); // underflow check
317+
}
318+
319+
public byte SpinnerCount
320+
{
321+
get => GetByteValue(SpinnerCountShift);
322+
set => SetByteValue(value, SpinnerCountShift);
323+
}
324+
325+
public void IncrementSpinnerCount()
326+
{
327+
Debug.Assert(SpinnerCount < byte.MaxValue);
328+
_data += (ulong)1 << SpinnerCountShift;
329+
}
330+
331+
public void DecrementSpinnerCount()
332+
{
333+
Debug.Assert(SpinnerCount != 0);
334+
_data -= (ulong)1 << SpinnerCountShift;
335+
}
336+
337+
public byte CountOfWaitersSignaledToWake
338+
{
339+
get => GetByteValue(CountOfWaitersSignaledToWakeShift);
340+
set => SetByteValue(value, CountOfWaitersSignaledToWakeShift);
341+
}
342+
343+
public void AddUpToMaxCountOfWaitersSignaledToWake(uint value)
344+
{
345+
uint availableCount = (uint)(byte.MaxValue - CountOfWaitersSignaledToWake);
346+
if (value > availableCount)
347+
{
348+
value = availableCount;
349+
}
350+
_data += (ulong)value << CountOfWaitersSignaledToWakeShift;
351+
}
352+
353+
public void DecrementCountOfWaitersSignaledToWake()
354+
{
355+
Debug.Assert(CountOfWaitersSignaledToWake != 0);
356+
_data -= (ulong)1 << CountOfWaitersSignaledToWakeShift;
357+
}
358+
359+
public Counts InterlockedCompareExchange(Counts newCounts, Counts oldCounts) =>
360+
new Counts(Interlocked.CompareExchange(ref _data, newCounts._data, oldCounts._data));
361+
362+
public static bool operator ==(Counts lhs, Counts rhs) => lhs.Equals(rhs);
363+
public static bool operator !=(Counts lhs, Counts rhs) => !lhs.Equals(rhs);
364+
365+
public override bool Equals([NotNullWhen(true)] object? obj) => obj is Counts other && Equals(other);
366+
public bool Equals(Counts other) => _data == other._data;
367+
public override int GetHashCode() => (int)_data + (int)(_data >> 32);
368+
}
369+
370+
[StructLayout(LayoutKind.Sequential)]
371+
private struct CacheLineSeparatedCounts
372+
{
373+
private readonly Internal.PaddingFor32 _pad1;
374+
public Counts _counts;
375+
private readonly Internal.PaddingFor32 _pad2;
376+
}
188377
}
189378
}

0 commit comments

Comments
 (0)