Skip to content

Commit 2d2a5a2

Browse files
authored
Fix Lock spin-waiting on single-proc machines (#101513)
* Fix Lock spin-waiting on single-proc machines Small fix to check for a single proc during initialization. Also renamed things referring to "minSpinCount" to clarify it a bit.
1 parent 71e9c46 commit 2d2a5a2

File tree

3 files changed

+37
-19
lines changed

3 files changed

+37
-19
lines changed

src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Lock.NativeAot.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public sealed partial class Lock
1818
private static int s_staticsInitializationStage;
1919
private static bool s_isSingleProcessor;
2020
private static short s_maxSpinCount;
21-
private static short s_minSpinCount;
21+
private static short s_minSpinCountForAdaptiveSpin;
2222

2323
/// <summary>
2424
/// Initializes a new instance of the <see cref="Lock"/> class.
@@ -202,7 +202,7 @@ private static bool TryInitializeStatics()
202202
// here. Initialize s_isSingleProcessor first, as it may be used by other initialization afterwards.
203203
s_isSingleProcessor = RuntimeImports.RhGetProcessCpuCount() == 1;
204204
s_maxSpinCount = DetermineMaxSpinCount();
205-
s_minSpinCount = DetermineMinSpinCount();
205+
s_minSpinCountForAdaptiveSpin = DetermineMinSpinCountForAdaptiveSpin();
206206
}
207207

208208
// Also initialize some types that are used later to prevent potential class construction cycles. If

src/libraries/System.Private.CoreLib/src/System/Threading/Lock.NonNativeAot.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ namespace System.Threading
99
public sealed partial class Lock
1010
{
1111
private static readonly short s_maxSpinCount = DetermineMaxSpinCount();
12-
private static readonly short s_minSpinCount = DetermineMinSpinCount();
12+
private static readonly short s_minSpinCountForAdaptiveSpin = DetermineMinSpinCountForAdaptiveSpin();
1313

1414
/// <summary>
1515
/// Initializes a new instance of the <see cref="Lock"/> class.

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

+34-16
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,15 @@ public sealed partial class Lock
3737

3838
private uint _state; // see State for layout
3939
private uint _recursionCount;
40+
41+
// This field serves a few purposes currently:
42+
// - When positive, it indicates the number of spin-wait iterations that most threads would do upon contention
43+
// - When zero, it indicates that spin-waiting is to be attempted by a thread to test if it is successful
44+
// - When negative, it serves as a rough counter for contentions that would increment it towards zero
45+
//
46+
// See references to this field and "AdaptiveSpin" in TryEnterSlow for more information.
4047
private short _spinCount;
48+
4149
private ushort _waiterStartTimeMs;
4250
private AutoResetEvent? _waitEvent;
4351

@@ -297,7 +305,7 @@ private void ExitImpl()
297305
}
298306
}
299307

300-
private static bool IsAdaptiveSpinEnabled(short minSpinCount) => minSpinCount <= 0;
308+
private static bool IsAdaptiveSpinEnabled(short minSpinCountForAdaptiveSpin) => minSpinCountForAdaptiveSpin <= 0;
301309

302310
[MethodImpl(MethodImplOptions.NoInlining)]
303311
private ThreadId TryEnterSlow(int timeoutMs, ThreadId currentThreadId)
@@ -350,20 +358,19 @@ private ThreadId TryEnterSlow(int timeoutMs, ThreadId currentThreadId)
350358
goto Locked;
351359
}
352360

353-
bool isSingleProcessor = IsSingleProcessor;
354361
short maxSpinCount = s_maxSpinCount;
355362
if (maxSpinCount == 0)
356363
{
357364
goto Wait;
358365
}
359366

360-
short minSpinCount = s_minSpinCount;
367+
short minSpinCountForAdaptiveSpin = s_minSpinCountForAdaptiveSpin;
361368
short spinCount = _spinCount;
362369
if (spinCount < 0)
363370
{
364371
// When negative, the spin count serves as a counter for contentions such that a spin-wait can be attempted
365372
// periodically to see if it would be beneficial. Increment the spin count and skip spin-waiting.
366-
Debug.Assert(IsAdaptiveSpinEnabled(minSpinCount));
373+
Debug.Assert(IsAdaptiveSpinEnabled(minSpinCountForAdaptiveSpin));
367374
_spinCount = (short)(spinCount + 1);
368375
goto Wait;
369376
}
@@ -388,7 +395,7 @@ private ThreadId TryEnterSlow(int timeoutMs, ThreadId currentThreadId)
388395

389396
for (short spinIndex = 0; ;)
390397
{
391-
LowLevelSpinWaiter.Wait(spinIndex, SpinSleep0Threshold, isSingleProcessor);
398+
LowLevelSpinWaiter.Wait(spinIndex, SpinSleep0Threshold, isSingleProcessor: false);
392399

393400
if (++spinIndex >= spinCount)
394401
{
@@ -405,7 +412,7 @@ private ThreadId TryEnterSlow(int timeoutMs, ThreadId currentThreadId)
405412

406413
if (tryLockResult == TryLockResult.Locked)
407414
{
408-
if (isFirstSpinner && IsAdaptiveSpinEnabled(minSpinCount))
415+
if (isFirstSpinner && IsAdaptiveSpinEnabled(minSpinCountForAdaptiveSpin))
409416
{
410417
// Since the first spinner does a full-length spin-wait, and to keep upward and downward changes to the
411418
// spin count more balanced, only the first spinner adjusts the spin count
@@ -426,7 +433,7 @@ private ThreadId TryEnterSlow(int timeoutMs, ThreadId currentThreadId)
426433

427434
// Unregister the spinner and try to acquire the lock
428435
tryLockResult = State.TryLockAfterSpinLoop(this);
429-
if (isFirstSpinner && IsAdaptiveSpinEnabled(minSpinCount))
436+
if (isFirstSpinner && IsAdaptiveSpinEnabled(minSpinCountForAdaptiveSpin))
430437
{
431438
// Since the first spinner does a full-length spin-wait, and to keep upward and downward changes to the
432439
// spin count more balanced, only the first spinner adjusts the spin count
@@ -444,7 +451,7 @@ private ThreadId TryEnterSlow(int timeoutMs, ThreadId currentThreadId)
444451
// number of contentions, the first spinner will attempt a spin-wait again to see if it is effective.
445452
Debug.Assert(tryLockResult == TryLockResult.Wait);
446453
spinCount = _spinCount;
447-
_spinCount = spinCount > 0 ? (short)(spinCount - 1) : minSpinCount;
454+
_spinCount = spinCount > 0 ? (short)(spinCount - 1) : minSpinCountForAdaptiveSpin;
448455
}
449456
}
450457

@@ -517,7 +524,7 @@ private ThreadId TryEnterSlow(int timeoutMs, ThreadId currentThreadId)
517524
break;
518525
}
519526

520-
LowLevelSpinWaiter.Wait(spinIndex, SpinSleep0Threshold, isSingleProcessor);
527+
LowLevelSpinWaiter.Wait(spinIndex, SpinSleep0Threshold, isSingleProcessor: false);
521528
}
522529

523530
if (acquiredLock)
@@ -661,14 +668,25 @@ internal nint LockIdForEvents
661668

662669
internal ulong OwningThreadId => _owningThreadId;
663670

664-
private static short DetermineMaxSpinCount() =>
665-
AppContextConfigHelper.GetInt16Config(
666-
"System.Threading.Lock.SpinCount",
667-
"DOTNET_Lock_SpinCount",
668-
DefaultMaxSpinCount,
669-
allowNegative: false);
671+
private static short DetermineMaxSpinCount()
672+
{
673+
if (IsSingleProcessor)
674+
{
675+
return 0;
676+
}
677+
678+
return
679+
AppContextConfigHelper.GetInt16Config(
680+
"System.Threading.Lock.SpinCount",
681+
"DOTNET_Lock_SpinCount",
682+
DefaultMaxSpinCount,
683+
allowNegative: false);
684+
}
670685

671-
private static short DetermineMinSpinCount()
686+
// When the returned value is zero or negative, indicates the lowest value that the _spinCount field will have when
687+
// adaptive spin chooses to pause spin-waiting, see the comment on the _spinCount field for more information. When the
688+
// returned value is positive, adaptive spin is disabled.
689+
private static short DetermineMinSpinCountForAdaptiveSpin()
672690
{
673691
// The config var can be set to -1 to disable adaptive spin
674692
short adaptiveSpinPeriod =

0 commit comments

Comments
 (0)