Skip to content

Commit 77141ae

Browse files
authored
Reapply revert of #97227, fix Lock's waiter duration computation (#98876)
* Reapply "Add an internal mode to `Lock` to have it use non-alertable waits (#97227)" (#98867) This reverts commit f129701. * Fix Lock's waiter duration computation PR #97227 introduced a tick count masking issue where the stored waiter start time excludes the upper bit from the ushort tick count, but comparisons with it were not doing the appropriate masking. This was leading to a lock convoy on some heavily contended locks once in a while due to waiters incorrectly appearing to have waited for a long time. Fixes #98021 * Fix wraparound issue * Fix recording waiter start time * Use a bit in the _state field instead
1 parent ed1e0ab commit 77141ae

24 files changed

Lines changed: 117 additions & 67 deletions

File tree

src/coreclr/System.Private.CoreLib/src/System/Threading/WaitHandle.CoreCLR.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ namespace System.Threading
99
public abstract partial class WaitHandle
1010
{
1111
[MethodImpl(MethodImplOptions.InternalCall)]
12-
private static extern int WaitOneCore(IntPtr waitHandle, int millisecondsTimeout);
12+
private static extern int WaitOneCore(IntPtr waitHandle, int millisecondsTimeout, bool useTrivialWaits);
1313

1414
private static unsafe int WaitMultipleIgnoringSyncContextCore(Span<IntPtr> waitHandles, bool waitAll, int millisecondsTimeout)
1515
{

src/coreclr/nativeaot/Common/src/System/Collections/Concurrent/ConcurrentUnifier.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ internal abstract class ConcurrentUnifier<K, V>
6565
{
6666
protected ConcurrentUnifier()
6767
{
68-
_lock = new Lock();
68+
_lock = new Lock(useTrivialWaits: true);
6969
_container = new Container(this);
7070
}
7171

src/coreclr/nativeaot/Common/src/System/Collections/Concurrent/ConcurrentUnifierW.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ internal abstract class ConcurrentUnifierW<K, V>
7575
{
7676
protected ConcurrentUnifierW()
7777
{
78-
_lock = new Lock();
78+
_lock = new Lock(useTrivialWaits: true);
7979
_container = new Container(this);
8080
}
8181

src/coreclr/nativeaot/Common/src/System/Collections/Concurrent/ConcurrentUnifierWKeyed.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ internal abstract class ConcurrentUnifierWKeyed<K, V>
8484
{
8585
protected ConcurrentUnifierWKeyed()
8686
{
87-
_lock = new Lock();
87+
_lock = new Lock(useTrivialWaits: true);
8888
_container = new Container(this);
8989
}
9090

src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -833,6 +833,10 @@
833833
<DiagnosticId>CP0002</DiagnosticId>
834834
<Target>M:System.Reflection.MethodBase.GetParametersAsSpan</Target>
835835
</Suppression>
836+
<Suppression>
837+
<DiagnosticId>CP0002</DiagnosticId>
838+
<Target>M:System.Threading.Lock.#ctor(System.Boolean)</Target>
839+
</Suppression>
836840
<Suppression>
837841
<DiagnosticId>CP0015</DiagnosticId>
838842
<Target>M:System.Diagnostics.Tracing.EventSource.Write``1(System.String,``0):[T:System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute]</Target>

src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/FrozenObjectHeapManager.cs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ internal unsafe partial class FrozenObjectHeapManager
1616
{
1717
public static readonly FrozenObjectHeapManager Instance = new FrozenObjectHeapManager();
1818

19-
private readonly LowLevelLock m_Crst = new LowLevelLock();
19+
private readonly Lock m_Crst = new Lock(useTrivialWaits: true);
2020
private FrozenObjectSegment m_CurrentSegment;
2121

2222
// Default size to reserve for a frozen segment
@@ -34,9 +34,7 @@ internal unsafe partial class FrozenObjectHeapManager
3434
{
3535
HalfBakedObject* obj = null;
3636

37-
m_Crst.Acquire();
38-
39-
try
37+
using (m_Crst.EnterScope())
4038
{
4139
Debug.Assert(type != null);
4240
// _ASSERT(FOH_COMMIT_SIZE >= MIN_OBJECT_SIZE);
@@ -84,10 +82,6 @@ internal unsafe partial class FrozenObjectHeapManager
8482
Debug.Assert(obj != null);
8583
}
8684
} // end of m_Crst lock
87-
finally
88-
{
89-
m_Crst.Release();
90-
}
9185

9286
IntPtr result = (IntPtr)obj;
9387

src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/ClassConstructorRunner.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ public static CctorHandle GetCctor(StaticClassConstructionContext* pContext)
275275
#if TARGET_WASM
276276
if (s_cctorGlobalLock == null)
277277
{
278-
Interlocked.CompareExchange(ref s_cctorGlobalLock, new Lock(), null);
278+
Interlocked.CompareExchange(ref s_cctorGlobalLock, new Lock(useTrivialWaits: true), null);
279279
}
280280
if (s_cctorArrays == null)
281281
{
@@ -342,7 +342,7 @@ public static CctorHandle GetCctor(StaticClassConstructionContext* pContext)
342342

343343
Debug.Assert(resultArray[resultIndex]._pContext == default(StaticClassConstructionContext*));
344344
resultArray[resultIndex]._pContext = pContext;
345-
resultArray[resultIndex].Lock = new Lock();
345+
resultArray[resultIndex].Lock = new Lock(useTrivialWaits: true);
346346
s_count++;
347347
}
348348

@@ -489,7 +489,7 @@ public static CctorHandle GetCctorThatThreadIsBlockedOn(int managedThreadId)
489489
internal static void Initialize()
490490
{
491491
s_cctorArrays = new Cctor[10][];
492-
s_cctorGlobalLock = new Lock();
492+
s_cctorGlobalLock = new Lock(useTrivialWaits: true);
493493
}
494494

495495
[Conditional("ENABLE_NOISY_CCTOR_LOG")]

src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.NativeAot.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public abstract partial class ComWrappers
4444
private static readonly List<GCHandle> s_referenceTrackerNativeObjectWrapperCache = new List<GCHandle>();
4545

4646
private readonly ConditionalWeakTable<object, ManagedObjectWrapperHolder> _ccwTable = new ConditionalWeakTable<object, ManagedObjectWrapperHolder>();
47-
private readonly Lock _lock = new Lock();
47+
private readonly Lock _lock = new Lock(useTrivialWaits: true);
4848
private readonly Dictionary<IntPtr, GCHandle> _rcwCache = new Dictionary<IntPtr, GCHandle>();
4949

5050
internal static bool TryGetComInstanceForIID(object obj, Guid iid, out IntPtr unknown, out long wrapperId)

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ public unsafe bool Wait(int millisecondsTimeout, object? associatedObjectForMoni
114114
success =
115115
waiter.ev.WaitOneNoCheck(
116116
millisecondsTimeout,
117+
false, // useTrivialWaits
117118
associatedObjectForMonitorWait,
118119
associatedObjectForMonitorWait != null
119120
? NativeRuntimeEventSource.WaitHandleWaitSourceMap.MonitorWait

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

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,18 @@ internal void Reenter(uint previousRecursionCount)
9292
_recursionCount = previousRecursionCount;
9393
}
9494

95+
private static bool IsFullyInitialized
96+
{
97+
get
98+
{
99+
// If NativeRuntimeEventSource is already being class-constructed by this thread earlier in the stack, Log can
100+
// be null. This property is used to avoid going down the wait path in that case to avoid null checks in several
101+
// other places.
102+
Debug.Assert((StaticsInitializationStage)s_staticsInitializationStage == StaticsInitializationStage.Complete);
103+
return NativeRuntimeEventSource.Log != null;
104+
}
105+
}
106+
95107
[MethodImpl(MethodImplOptions.AggressiveInlining)]
96108
private TryLockResult LazyInitializeOrEnter()
97109
{
@@ -101,6 +113,10 @@ private TryLockResult LazyInitializeOrEnter()
101113
case StaticsInitializationStage.Complete:
102114
if (_spinCount == SpinCountNotInitialized)
103115
{
116+
if (!IsFullyInitialized)
117+
{
118+
goto case StaticsInitializationStage.Started;
119+
}
104120
_spinCount = s_maxSpinCount;
105121
}
106122
return TryLockResult.Spin;
@@ -121,7 +137,7 @@ private TryLockResult LazyInitializeOrEnter()
121137
}
122138

123139
stage = (StaticsInitializationStage)Volatile.Read(ref s_staticsInitializationStage);
124-
if (stage == StaticsInitializationStage.Complete)
140+
if (stage == StaticsInitializationStage.Complete && IsFullyInitialized)
125141
{
126142
goto case StaticsInitializationStage.Complete;
127143
}
@@ -166,14 +182,17 @@ private static bool TryInitializeStatics()
166182
return true;
167183
}
168184

185+
bool isFullyInitialized;
169186
try
170187
{
171188
s_isSingleProcessor = Environment.IsSingleProcessor;
172189
s_maxSpinCount = DetermineMaxSpinCount();
173190
s_minSpinCount = DetermineMinSpinCount();
174191

175-
// Also initialize some types that are used later to prevent potential class construction cycles
176-
_ = NativeRuntimeEventSource.Log;
192+
// Also initialize some types that are used later to prevent potential class construction cycles. If
193+
// NativeRuntimeEventSource is already being class-constructed by this thread earlier in the stack, Log can be
194+
// null. Avoid going down the wait path in that case to avoid null checks in several other places.
195+
isFullyInitialized = NativeRuntimeEventSource.Log != null;
177196
}
178197
catch
179198
{
@@ -182,20 +201,24 @@ private static bool TryInitializeStatics()
182201
}
183202

184203
Volatile.Write(ref s_staticsInitializationStage, (int)StaticsInitializationStage.Complete);
185-
return true;
204+
return isFullyInitialized;
186205
}
187206

188207
// Returns false until the static variable is lazy-initialized
189208
internal static bool IsSingleProcessor => s_isSingleProcessor;
190209

191-
// Used to transfer the state when inflating thin locks
192-
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)
193213
{
194214
Debug.Assert(recursionCount == 0 || managedThreadId != 0);
215+
Debug.Assert(!new State(this).UseTrivialWaits);
195216

196217
_state = managedThreadId == 0 ? State.InitialStateValue : State.LockedStateValue;
197218
_owningThreadId = (uint)managedThreadId;
198219
_recursionCount = recursionCount;
220+
221+
Debug.Assert(!new State(this).UseTrivialWaits);
199222
}
200223

201224
internal struct ThreadId

0 commit comments

Comments
 (0)