Skip to content

Commit fc7fe1d

Browse files
author
Koundinya Veluri
committed
Use a bit in the _state field instead
1 parent 559b18b commit fc7fe1d

File tree

3 files changed

+36
-47
lines changed

3 files changed

+36
-47
lines changed

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,14 +207,18 @@ private static bool TryInitializeStatics()
207207
// Returns false until the static variable is lazy-initialized
208208
internal static bool IsSingleProcessor => s_isSingleProcessor;
209209

210-
// Used to transfer the state when inflating thin locks
211-
internal void InitializeLocked(int managedThreadId, uint recursionCount)
210+
// Used to transfer the state when inflating thin locks. The lock is considered unlocked if managedThreadId is zero, and
211+
// locked otherwise.
212+
internal void ResetForMonitor(int managedThreadId, uint recursionCount)
212213
{
213214
Debug.Assert(recursionCount == 0 || managedThreadId != 0);
215+
Debug.Assert(!new State(this).UseTrivialWaits);
214216

215217
_state = managedThreadId == 0 ? State.InitialStateValue : State.LockedStateValue;
216218
_owningThreadId = (uint)managedThreadId;
217219
_recursionCount = recursionCount;
220+
221+
Debug.Assert(!new State(this).UseTrivialWaits);
218222
}
219223

220224
internal struct ThreadId

src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/SyncTable.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ public static void MoveThinLockToNewEntry(int syncIndex, int threadId, uint recu
274274
Debug.Assert(s_lock.IsHeldByCurrentThread);
275275
Debug.Assert((0 < syncIndex) && (syncIndex < s_unusedEntryIndex));
276276

277-
s_entries[syncIndex].Lock.InitializeLocked(threadId, recursionLevel);
277+
s_entries[syncIndex].Lock.ResetForMonitor(threadId, recursionLevel);
278278
}
279279

280280
/// <summary>

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

Lines changed: 29 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public sealed partial class Lock
2222
private const short DefaultMaxSpinCount = 22;
2323
private const short DefaultAdaptiveSpinPeriod = 100;
2424
private const short SpinSleep0Threshold = 10;
25-
private const int MaxDurationMsForPreemptingWaiters = 100;
25+
private const ushort MaxDurationMsForPreemptingWaiters = 100;
2626

2727
private static long s_contentionCount;
2828

@@ -38,10 +38,7 @@ public sealed partial class Lock
3838
private uint _state; // see State for layout
3939
private uint _recursionCount;
4040
private short _spinCount;
41-
42-
// The lowest bit is a flag, when set it indicates that the lock should use trivial waits
43-
private ushort _waiterStartTimeMsAndFlags;
44-
41+
private ushort _waiterStartTimeMs;
4542
private AutoResetEvent? _waitEvent;
4643

4744
#if NATIVEAOT // The method needs to be public in NativeAOT so that other private libraries can access it
@@ -51,10 +48,7 @@ internal Lock(bool useTrivialWaits)
5148
#endif
5249
: this()
5350
{
54-
if (useTrivialWaits)
55-
{
56-
_waiterStartTimeMsAndFlags = 1;
57-
}
51+
State.InitializeUseTrivialWaits(this, useTrivialWaits);
5852
}
5953

6054
/// <summary>
@@ -488,7 +482,7 @@ private ThreadId TryEnterSlow(int timeoutMs, ThreadId currentThreadId)
488482
int remainingTimeoutMs = timeoutMs;
489483
while (true)
490484
{
491-
if (!waitEvent.WaitOneNoCheck(remainingTimeoutMs, UseTrivialWaits))
485+
if (!waitEvent.WaitOneNoCheck(remainingTimeoutMs, new State(this).UseTrivialWaits))
492486
{
493487
break;
494488
}
@@ -567,43 +561,18 @@ private ThreadId TryEnterSlow(int timeoutMs, ThreadId currentThreadId)
567561
return new ThreadId(0);
568562
}
569563

570-
// Trivial waits are:
571-
// - Not interruptible by Thread.Interrupt
572-
// - Don't allow reentrance through APCs or message pumping
573-
// - Not forwarded to SynchronizationContext wait overrides
574-
private bool UseTrivialWaits => (_waiterStartTimeMsAndFlags & 1) != 0;
575-
576-
// The stored waiter start time (ms) only includes the lower 15 bits since the field is also used for a flag. This mask
577-
// should be used when recording and comparing waiter times.
578-
private const int WaiterTimeMsMask = 0x7fff;
579-
580-
// Only the lower 15 bits are stored/retrieved. Callers should use WaiterTimeMsMask when recording and comparing waiter
581-
// times.
582-
private int WaiterStartTimeMs
583-
{
584-
get => _waiterStartTimeMsAndFlags >> 1;
585-
set
586-
{
587-
Debug.Assert((value & WaiterTimeMsMask) == value);
588-
_waiterStartTimeMsAndFlags = (ushort)((value << 1) | (_waiterStartTimeMsAndFlags & 1));
589-
}
590-
}
591-
592-
private void ResetWaiterStartTime() => WaiterStartTimeMs = 0;
564+
private void ResetWaiterStartTime() => _waiterStartTimeMs = 0;
593565

594566
[MethodImpl(MethodImplOptions.AggressiveInlining)]
595567
private void RecordWaiterStartTime()
596568
{
597-
int currentTimeMs = Environment.TickCount & WaiterTimeMsMask;
598-
599-
// Don't record zero, that value is reserved for indicating that a time is not recorded
569+
ushort currentTimeMs = (ushort)Environment.TickCount;
600570
if (currentTimeMs == 0)
601571
{
602-
currentTimeMs = (currentTimeMs - 1) & WaiterTimeMsMask;
572+
// Don't record zero, that value is reserved for indicating that a time is not recorded
573+
currentTimeMs--;
603574
}
604-
605-
Debug.Assert(currentTimeMs != 0);
606-
WaiterStartTimeMs = currentTimeMs;
575+
_waiterStartTimeMs = currentTimeMs;
607576
}
608577

609578
private bool ShouldStopPreemptingWaiters
@@ -612,10 +581,10 @@ private bool ShouldStopPreemptingWaiters
612581
get
613582
{
614583
// If the recorded time is zero, a time has not been recorded yet
615-
int waiterStartTimeMs = WaiterStartTimeMs;
584+
ushort waiterStartTimeMs = _waiterStartTimeMs;
616585
return
617586
waiterStartTimeMs != 0 &&
618-
((Environment.TickCount - waiterStartTimeMs) & WaiterTimeMsMask) >= MaxDurationMsForPreemptingWaiters;
587+
(ushort)(Environment.TickCount - waiterStartTimeMs) >= MaxDurationMsForPreemptingWaiters;
619588
}
620589
}
621590

@@ -720,8 +689,8 @@ private struct State : IEquatable<State>
720689
private const uint SpinnerCountIncrement = (uint)1 << 2; // bits 2-4
721690
private const uint SpinnerCountMask = (uint)0x7 << 2;
722691
private const uint IsWaiterSignaledToWakeMask = (uint)1 << 5; // bit 5
723-
private const byte WaiterCountShift = 6;
724-
private const uint WaiterCountIncrement = (uint)1 << WaiterCountShift; // bits 6-31
692+
private const uint UseTrivialWaitsMask = (uint)1 << 6; // bit 6
693+
private const uint WaiterCountIncrement = (uint)1 << 7; // bits 7-31
725694

726695
private uint _state;
727696

@@ -800,6 +769,22 @@ private void ClearIsWaiterSignaledToWake()
800769
_state -= IsWaiterSignaledToWakeMask;
801770
}
802771

772+
// Trivial waits are:
773+
// - Not interruptible by Thread.Interrupt
774+
// - Don't allow reentrance through APCs or message pumping
775+
// - Not forwarded to SynchronizationContext wait overrides
776+
public bool UseTrivialWaits => (_state & UseTrivialWaitsMask) != 0;
777+
778+
public static void InitializeUseTrivialWaits(Lock lockObj, bool useTrivialWaits)
779+
{
780+
Debug.Assert(lockObj._state == 0);
781+
782+
if (useTrivialWaits)
783+
{
784+
lockObj._state = UseTrivialWaitsMask;
785+
}
786+
}
787+
803788
public bool HasAnyWaiters => _state >= WaiterCountIncrement;
804789

805790
private bool TryIncrementWaiterCount()

0 commit comments

Comments
 (0)