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

File tree

24 files changed

+117
-67
lines changed

24 files changed

+117
-67
lines changed

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

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ internal static class SyncTable
6565
/// <summary>
6666
/// Protects all mutable operations on s_entries, s_freeEntryList, s_unusedEntryIndex. Also protects growing the table.
6767
/// </summary>
68-
internal static readonly Lock s_lock = new Lock();
68+
internal static readonly Lock s_lock = new Lock(useTrivialWaits: true);
6969

7070
/// <summary>
7171
/// The dynamically growing array of sync entries.
@@ -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/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.Windows.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ private bool JoinInternal(int millisecondsTimeout)
167167
}
168168
else
169169
{
170-
result = WaitHandle.WaitOneCore(waitHandle.DangerousGetHandle(), millisecondsTimeout);
170+
result = WaitHandle.WaitOneCore(waitHandle.DangerousGetHandle(), millisecondsTimeout, useTrivialWaits: false);
171171
}
172172

173173
return result == (int)Interop.Kernel32.WAIT_OBJECT_0;

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public sealed partial class Thread
3131
private Exception? _startException;
3232

3333
// Protects starting the thread and setting its priority
34-
private Lock _lock = new Lock();
34+
private Lock _lock = new Lock(useTrivialWaits: true);
3535

3636
// This is used for a quick check on thread pool threads after running a work item to determine if the name, background
3737
// state, or priority were changed by the work item, and if so to reset it. Other threads may also change some of those,

src/coreclr/nativeaot/System.Private.CoreLib/src/System/Type.NativeAot.cs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,14 @@ internal static unsafe RuntimeType GetTypeFromMethodTable(MethodTable* pMT)
3131

3232
private static class AllocationLockHolder
3333
{
34-
public static LowLevelLock AllocationLock = new LowLevelLock();
34+
public static Lock AllocationLock = new Lock(useTrivialWaits: true);
3535
}
3636

3737
[MethodImpl(MethodImplOptions.NoInlining)]
3838
private static unsafe RuntimeType GetTypeFromMethodTableSlow(MethodTable* pMT)
3939
{
4040
// Allocate and set the RuntimeType under a lock - there's no way to free it if there is a race.
41-
AllocationLockHolder.AllocationLock.Acquire();
42-
try
41+
using (AllocationLockHolder.AllocationLock.EnterScope())
4342
{
4443
ref RuntimeType? runtimeTypeCache = ref Unsafe.AsRef<RuntimeType?>(pMT->WritableData);
4544
if (runtimeTypeCache != null)
@@ -55,10 +54,6 @@ private static unsafe RuntimeType GetTypeFromMethodTableSlow(MethodTable* pMT)
5554

5655
return type;
5756
}
58-
finally
59-
{
60-
AllocationLockHolder.AllocationLock.Release();
61-
}
6257
}
6358

6459
//

src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeLoaderEnvironment.ConstructedGenericsRegistration.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ internal struct DynamicGenericsRegistrationData
2424
}
2525

2626
// To keep the synchronization simple, we execute all dynamic generic type registration/lookups under a global lock
27-
private Lock _dynamicGenericsLock = new Lock();
27+
private Lock _dynamicGenericsLock = new Lock(useTrivialWaits: true);
2828

2929
internal void RegisterDynamicGenericTypesAndMethods(DynamicGenericsRegistrationData registrationData)
3030
{

src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeLoaderEnvironment.StaticsLookup.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ namespace Internal.Runtime.TypeLoader
1515
public sealed partial class TypeLoaderEnvironment
1616
{
1717
// To keep the synchronization simple, we execute all TLS registration/lookups under a global lock
18-
private Lock _threadStaticsLock = new Lock();
18+
private Lock _threadStaticsLock = new Lock(useTrivialWaits: true);
1919

2020
// Counter to keep track of generated offsets for TLS cells of dynamic types;
2121
private LowLevelDictionary<IntPtr, uint> _maxThreadLocalIndex = new LowLevelDictionary<IntPtr, uint>();

src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeLoaderEnvironment.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ internal static void Initialize()
145145
}
146146

147147
// To keep the synchronization simple, we execute all type loading under a global lock
148-
private Lock _typeLoaderLock = new Lock();
148+
private Lock _typeLoaderLock = new Lock(useTrivialWaits: true);
149149

150150
public void VerifyTypeLoaderLockHeld()
151151
{

src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeSystemContextFactory.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public static class TypeSystemContextFactory
1818
// This allows us to avoid recreating the type resolution context again and again, but still allows it to go away once the types are no longer being built
1919
private static GCHandle s_cachedContext = GCHandle.Alloc(null, GCHandleType.Weak);
2020

21-
private static Lock s_lock = new Lock();
21+
private static Lock s_lock = new Lock(useTrivialWaits: true);
2222

2323
public static TypeSystemContext Create()
2424
{

src/coreclr/vm/comwaithandle.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
#include "excep.h"
1717
#include "comwaithandle.h"
1818

19-
FCIMPL2(INT32, WaitHandleNative::CorWaitOneNative, HANDLE handle, INT32 timeout)
19+
FCIMPL3(INT32, WaitHandleNative::CorWaitOneNative, HANDLE handle, INT32 timeout, CLR_BOOL useTrivialWaits)
2020
{
2121
FCALL_CONTRACT;
2222

@@ -28,7 +28,8 @@ FCIMPL2(INT32, WaitHandleNative::CorWaitOneNative, HANDLE handle, INT32 timeout)
2828

2929
Thread* pThread = GET_THREAD();
3030

31-
retVal = pThread->DoAppropriateWait(1, &handle, TRUE, timeout, (WaitMode)(WaitMode_Alertable | WaitMode_IgnoreSyncCtx));
31+
WaitMode waitMode = (WaitMode)((!useTrivialWaits ? WaitMode_Alertable : WaitMode_None) | WaitMode_IgnoreSyncCtx);
32+
retVal = pThread->DoAppropriateWait(1, &handle, TRUE, timeout, waitMode);
3233

3334
HELPER_METHOD_FRAME_END();
3435
return retVal;

src/coreclr/vm/comwaithandle.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
class WaitHandleNative
1919
{
2020
public:
21-
static FCDECL2(INT32, CorWaitOneNative, HANDLE handle, INT32 timeout);
21+
static FCDECL3(INT32, CorWaitOneNative, HANDLE handle, INT32 timeout, CLR_BOOL useTrivialWaits);
2222
static FCDECL4(INT32, CorWaitMultipleNative, HANDLE *handleArray, INT32 numHandles, CLR_BOOL waitForAll, INT32 timeout);
2323
static FCDECL3(INT32, CorSignalAndWaitOneNative, HANDLE waitHandleSignalUNSAFE, HANDLE waitHandleWaitUNSAFE, INT32 timeout);
2424
};

0 commit comments

Comments
 (0)