diff --git a/src/coreclr/System.Private.CoreLib/src/System/Threading/WaitHandle.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Threading/WaitHandle.CoreCLR.cs index 1427473092185..b70a688e9d55d 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Threading/WaitHandle.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Threading/WaitHandle.CoreCLR.cs @@ -9,7 +9,7 @@ namespace System.Threading public abstract partial class WaitHandle { [MethodImpl(MethodImplOptions.InternalCall)] - private static extern int WaitOneCore(IntPtr waitHandle, int millisecondsTimeout); + private static extern int WaitOneCore(IntPtr waitHandle, int millisecondsTimeout, bool useTrivialWaits); private static unsafe int WaitMultipleIgnoringSyncContextCore(Span waitHandles, bool waitAll, int millisecondsTimeout) { diff --git a/src/coreclr/nativeaot/Common/src/System/Collections/Concurrent/ConcurrentUnifier.cs b/src/coreclr/nativeaot/Common/src/System/Collections/Concurrent/ConcurrentUnifier.cs index a790e0aeb5010..4e8b3ed564db5 100644 --- a/src/coreclr/nativeaot/Common/src/System/Collections/Concurrent/ConcurrentUnifier.cs +++ b/src/coreclr/nativeaot/Common/src/System/Collections/Concurrent/ConcurrentUnifier.cs @@ -65,7 +65,7 @@ internal abstract class ConcurrentUnifier { protected ConcurrentUnifier() { - _lock = new Lock(); + _lock = new Lock(useTrivialWaits: true); _container = new Container(this); } diff --git a/src/coreclr/nativeaot/Common/src/System/Collections/Concurrent/ConcurrentUnifierW.cs b/src/coreclr/nativeaot/Common/src/System/Collections/Concurrent/ConcurrentUnifierW.cs index 69cfde1e48e3b..efca6e2efaeb0 100644 --- a/src/coreclr/nativeaot/Common/src/System/Collections/Concurrent/ConcurrentUnifierW.cs +++ b/src/coreclr/nativeaot/Common/src/System/Collections/Concurrent/ConcurrentUnifierW.cs @@ -75,7 +75,7 @@ internal abstract class ConcurrentUnifierW { protected ConcurrentUnifierW() { - _lock = new Lock(); + _lock = new Lock(useTrivialWaits: true); _container = new Container(this); } diff --git a/src/coreclr/nativeaot/Common/src/System/Collections/Concurrent/ConcurrentUnifierWKeyed.cs b/src/coreclr/nativeaot/Common/src/System/Collections/Concurrent/ConcurrentUnifierWKeyed.cs index 9ce60a5151857..7cd63314c35ee 100644 --- a/src/coreclr/nativeaot/Common/src/System/Collections/Concurrent/ConcurrentUnifierWKeyed.cs +++ b/src/coreclr/nativeaot/Common/src/System/Collections/Concurrent/ConcurrentUnifierWKeyed.cs @@ -84,7 +84,7 @@ internal abstract class ConcurrentUnifierWKeyed { protected ConcurrentUnifierWKeyed() { - _lock = new Lock(); + _lock = new Lock(useTrivialWaits: true); _container = new Container(this); } diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml b/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml index 805f58f68fa95..3161e1aa4c1ce 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml @@ -913,6 +913,10 @@ CP0002 M:System.Reflection.MethodBase.GetParametersAsSpan + + CP0002 + M:System.Threading.Lock.#ctor(System.Boolean) + CP0015 M:System.Diagnostics.Tracing.EventSource.Write``1(System.String,``0):[T:System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute] diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/FrozenObjectHeapManager.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/FrozenObjectHeapManager.cs index d81a5409bf42e..81f1eec9cfdf2 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/FrozenObjectHeapManager.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/FrozenObjectHeapManager.cs @@ -16,7 +16,7 @@ internal unsafe partial class FrozenObjectHeapManager { public static readonly FrozenObjectHeapManager Instance = new FrozenObjectHeapManager(); - private readonly LowLevelLock m_Crst = new LowLevelLock(); + private readonly Lock m_Crst = new Lock(useTrivialWaits: true); private FrozenObjectSegment m_CurrentSegment; // Default size to reserve for a frozen segment @@ -34,9 +34,7 @@ internal unsafe partial class FrozenObjectHeapManager { HalfBakedObject* obj = null; - m_Crst.Acquire(); - - try + using (m_Crst.EnterScope()) { Debug.Assert(type != null); // _ASSERT(FOH_COMMIT_SIZE >= MIN_OBJECT_SIZE); @@ -84,10 +82,6 @@ internal unsafe partial class FrozenObjectHeapManager Debug.Assert(obj != null); } } // end of m_Crst lock - finally - { - m_Crst.Release(); - } IntPtr result = (IntPtr)obj; diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/ClassConstructorRunner.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/ClassConstructorRunner.cs index 737a3db6c5984..c3baa5a7dad04 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/ClassConstructorRunner.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/ClassConstructorRunner.cs @@ -275,7 +275,7 @@ public static CctorHandle GetCctor(StaticClassConstructionContext* pContext) #if TARGET_WASM if (s_cctorGlobalLock == null) { - Interlocked.CompareExchange(ref s_cctorGlobalLock, new Lock(), null); + Interlocked.CompareExchange(ref s_cctorGlobalLock, new Lock(useTrivialWaits: true), null); } if (s_cctorArrays == null) { @@ -342,7 +342,7 @@ public static CctorHandle GetCctor(StaticClassConstructionContext* pContext) Debug.Assert(resultArray[resultIndex]._pContext == default(StaticClassConstructionContext*)); resultArray[resultIndex]._pContext = pContext; - resultArray[resultIndex].Lock = new Lock(); + resultArray[resultIndex].Lock = new Lock(useTrivialWaits: true); s_count++; } @@ -489,7 +489,7 @@ public static CctorHandle GetCctorThatThreadIsBlockedOn(int managedThreadId) internal static void Initialize() { s_cctorArrays = new Cctor[10][]; - s_cctorGlobalLock = new Lock(); + s_cctorGlobalLock = new Lock(useTrivialWaits: true); } [Conditional("ENABLE_NOISY_CCTOR_LOG")] diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.NativeAot.cs index 4d156c039422a..e06ac7457049a 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.NativeAot.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.NativeAot.cs @@ -44,7 +44,7 @@ public abstract partial class ComWrappers private static readonly List s_referenceTrackerNativeObjectWrapperCache = new List(); private readonly ConditionalWeakTable _ccwTable = new ConditionalWeakTable(); - private readonly Lock _lock = new Lock(); + private readonly Lock _lock = new Lock(useTrivialWaits: true); private readonly Dictionary _rcwCache = new Dictionary(); internal static bool TryGetComInstanceForIID(object obj, Guid iid, out IntPtr unknown, out long wrapperId) diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Condition.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Condition.cs index 70843a3b940fc..1c6cdadaf0227 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Condition.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Condition.cs @@ -114,6 +114,7 @@ public unsafe bool Wait(int millisecondsTimeout, object? associatedObjectForMoni success = waiter.ev.WaitOneNoCheck( millisecondsTimeout, + false, // useTrivialWaits associatedObjectForMonitorWait, associatedObjectForMonitorWait != null ? NativeRuntimeEventSource.WaitHandleWaitSourceMap.MonitorWait diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Lock.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Lock.NativeAot.cs index 318a8cc768024..78fc774540199 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Lock.NativeAot.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Lock.NativeAot.cs @@ -92,6 +92,18 @@ internal void Reenter(uint previousRecursionCount) _recursionCount = previousRecursionCount; } + private static bool IsFullyInitialized + { + get + { + // If NativeRuntimeEventSource is already being class-constructed by this thread earlier in the stack, Log can + // be null. This property is used to avoid going down the wait path in that case to avoid null checks in several + // other places. + Debug.Assert((StaticsInitializationStage)s_staticsInitializationStage == StaticsInitializationStage.Complete); + return NativeRuntimeEventSource.Log != null; + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private TryLockResult LazyInitializeOrEnter() { @@ -101,6 +113,10 @@ private TryLockResult LazyInitializeOrEnter() case StaticsInitializationStage.Complete: if (_spinCount == SpinCountNotInitialized) { + if (!IsFullyInitialized) + { + goto case StaticsInitializationStage.Started; + } _spinCount = s_maxSpinCount; } return TryLockResult.Spin; @@ -121,7 +137,7 @@ private TryLockResult LazyInitializeOrEnter() } stage = (StaticsInitializationStage)Volatile.Read(ref s_staticsInitializationStage); - if (stage == StaticsInitializationStage.Complete) + if (stage == StaticsInitializationStage.Complete && IsFullyInitialized) { goto case StaticsInitializationStage.Complete; } @@ -166,14 +182,17 @@ private static bool TryInitializeStatics() return true; } + bool isFullyInitialized; try { s_isSingleProcessor = Environment.IsSingleProcessor; s_maxSpinCount = DetermineMaxSpinCount(); s_minSpinCount = DetermineMinSpinCount(); - // Also initialize some types that are used later to prevent potential class construction cycles - _ = NativeRuntimeEventSource.Log; + // Also initialize some types that are used later to prevent potential class construction cycles. If + // NativeRuntimeEventSource is already being class-constructed by this thread earlier in the stack, Log can be + // null. Avoid going down the wait path in that case to avoid null checks in several other places. + isFullyInitialized = NativeRuntimeEventSource.Log != null; } catch { @@ -182,7 +201,7 @@ private static bool TryInitializeStatics() } Volatile.Write(ref s_staticsInitializationStage, (int)StaticsInitializationStage.Complete); - return true; + return isFullyInitialized; } // Returns false until the static variable is lazy-initialized diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/SyncTable.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/SyncTable.cs index 02d7b4167ca6b..c3a273d32739a 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/SyncTable.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/SyncTable.cs @@ -65,7 +65,7 @@ internal static class SyncTable /// /// Protects all mutable operations on s_entries, s_freeEntryList, s_unusedEntryIndex. Also protects growing the table. /// - internal static readonly Lock s_lock = new Lock(); + internal static readonly Lock s_lock = new Lock(useTrivialWaits: true); /// /// The dynamically growing array of sync entries. diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.Windows.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.Windows.cs index d0a48bed6c8d5..d6ea54412e105 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.Windows.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.Windows.cs @@ -167,7 +167,7 @@ private bool JoinInternal(int millisecondsTimeout) } else { - result = WaitHandle.WaitOneCore(waitHandle.DangerousGetHandle(), millisecondsTimeout); + result = WaitHandle.WaitOneCore(waitHandle.DangerousGetHandle(), millisecondsTimeout, useTrivialWaits: false); } return result == (int)Interop.Kernel32.WAIT_OBJECT_0; diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.cs index 5bdb703b44527..7fc526fc8347e 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.cs @@ -31,7 +31,7 @@ public sealed partial class Thread private Exception? _startException; // Protects starting the thread and setting its priority - private Lock _lock = new Lock(); + private Lock _lock = new Lock(useTrivialWaits: true); // This is used for a quick check on thread pool threads after running a work item to determine if the name, background // state, or priority were changed by the work item, and if so to reset it. Other threads may also change some of those, diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Type.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Type.NativeAot.cs index 4d9b2f3981b5f..9589edaab42b3 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Type.NativeAot.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Type.NativeAot.cs @@ -31,15 +31,14 @@ internal static unsafe RuntimeType GetTypeFromMethodTable(MethodTable* pMT) private static class AllocationLockHolder { - public static LowLevelLock AllocationLock = new LowLevelLock(); + public static Lock AllocationLock = new Lock(useTrivialWaits: true); } [MethodImpl(MethodImplOptions.NoInlining)] private static unsafe RuntimeType GetTypeFromMethodTableSlow(MethodTable* pMT) { // Allocate and set the RuntimeType under a lock - there's no way to free it if there is a race. - AllocationLockHolder.AllocationLock.Acquire(); - try + using (AllocationLockHolder.AllocationLock.EnterScope()) { ref RuntimeType? runtimeTypeCache = ref Unsafe.AsRef(pMT->WritableData); if (runtimeTypeCache != null) @@ -55,10 +54,6 @@ private static unsafe RuntimeType GetTypeFromMethodTableSlow(MethodTable* pMT) return type; } - finally - { - AllocationLockHolder.AllocationLock.Release(); - } } // diff --git a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeLoaderEnvironment.ConstructedGenericsRegistration.cs b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeLoaderEnvironment.ConstructedGenericsRegistration.cs index cebd5d917896f..72c65865ff2af 100644 --- a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeLoaderEnvironment.ConstructedGenericsRegistration.cs +++ b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeLoaderEnvironment.ConstructedGenericsRegistration.cs @@ -24,7 +24,7 @@ internal struct DynamicGenericsRegistrationData } // To keep the synchronization simple, we execute all dynamic generic type registration/lookups under a global lock - private Lock _dynamicGenericsLock = new Lock(); + private Lock _dynamicGenericsLock = new Lock(useTrivialWaits: true); internal void RegisterDynamicGenericTypesAndMethods(DynamicGenericsRegistrationData registrationData) { diff --git a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeLoaderEnvironment.StaticsLookup.cs b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeLoaderEnvironment.StaticsLookup.cs index e442bfa940ea7..1070b50fd75b8 100644 --- a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeLoaderEnvironment.StaticsLookup.cs +++ b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeLoaderEnvironment.StaticsLookup.cs @@ -15,7 +15,7 @@ namespace Internal.Runtime.TypeLoader public sealed partial class TypeLoaderEnvironment { // To keep the synchronization simple, we execute all TLS registration/lookups under a global lock - private Lock _threadStaticsLock = new Lock(); + private Lock _threadStaticsLock = new Lock(useTrivialWaits: true); // Counter to keep track of generated offsets for TLS cells of dynamic types; private LowLevelDictionary _maxThreadLocalIndex = new LowLevelDictionary(); diff --git a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeLoaderEnvironment.cs b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeLoaderEnvironment.cs index 46ebec4d4d650..36f2f5dee29fe 100644 --- a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeLoaderEnvironment.cs +++ b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeLoaderEnvironment.cs @@ -145,7 +145,7 @@ internal static void Initialize() } // To keep the synchronization simple, we execute all type loading under a global lock - private Lock _typeLoaderLock = new Lock(); + private Lock _typeLoaderLock = new Lock(useTrivialWaits: true); public void VerifyTypeLoaderLockHeld() { diff --git a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeSystemContextFactory.cs b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeSystemContextFactory.cs index 7f3037fc9a0cb..410296beaf0b9 100644 --- a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeSystemContextFactory.cs +++ b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeSystemContextFactory.cs @@ -18,7 +18,7 @@ public static class TypeSystemContextFactory // 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 private static GCHandle s_cachedContext = GCHandle.Alloc(null, GCHandleType.Weak); - private static Lock s_lock = new Lock(); + private static Lock s_lock = new Lock(useTrivialWaits: true); public static TypeSystemContext Create() { diff --git a/src/coreclr/vm/comwaithandle.cpp b/src/coreclr/vm/comwaithandle.cpp index 8ec141aa2a4e9..3af42e09ecdf1 100644 --- a/src/coreclr/vm/comwaithandle.cpp +++ b/src/coreclr/vm/comwaithandle.cpp @@ -16,7 +16,7 @@ #include "excep.h" #include "comwaithandle.h" -FCIMPL2(INT32, WaitHandleNative::CorWaitOneNative, HANDLE handle, INT32 timeout) +FCIMPL3(INT32, WaitHandleNative::CorWaitOneNative, HANDLE handle, INT32 timeout, CLR_BOOL useTrivialWaits) { FCALL_CONTRACT; @@ -28,7 +28,8 @@ FCIMPL2(INT32, WaitHandleNative::CorWaitOneNative, HANDLE handle, INT32 timeout) Thread* pThread = GET_THREAD(); - retVal = pThread->DoAppropriateWait(1, &handle, TRUE, timeout, (WaitMode)(WaitMode_Alertable | WaitMode_IgnoreSyncCtx)); + WaitMode waitMode = (WaitMode)((!useTrivialWaits ? WaitMode_Alertable : WaitMode_None) | WaitMode_IgnoreSyncCtx); + retVal = pThread->DoAppropriateWait(1, &handle, TRUE, timeout, waitMode); HELPER_METHOD_FRAME_END(); return retVal; diff --git a/src/coreclr/vm/comwaithandle.h b/src/coreclr/vm/comwaithandle.h index ae250a1b9a966..c892d7aae8551 100644 --- a/src/coreclr/vm/comwaithandle.h +++ b/src/coreclr/vm/comwaithandle.h @@ -18,7 +18,7 @@ class WaitHandleNative { public: - static FCDECL2(INT32, CorWaitOneNative, HANDLE handle, INT32 timeout); + static FCDECL3(INT32, CorWaitOneNative, HANDLE handle, INT32 timeout, CLR_BOOL useTrivialWaits); static FCDECL4(INT32, CorWaitMultipleNative, HANDLE *handleArray, INT32 numHandles, CLR_BOOL waitForAll, INT32 timeout); static FCDECL3(INT32, CorSignalAndWaitOneNative, HANDLE waitHandleSignalUNSAFE, HANDLE waitHandleWaitUNSAFE, INT32 timeout); }; diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Lock.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Lock.cs index 056ff96d41b1e..66cf6a03f607a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Lock.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Lock.cs @@ -38,9 +38,25 @@ public sealed partial class Lock private uint _state; // see State for layout private uint _recursionCount; private short _spinCount; - private ushort _waiterStartTimeMs; + + // The lowest bit is a flag, when set it indicates that the lock should use trivial waits + private ushort _waiterStartTimeMsAndFlags; + private AutoResetEvent? _waitEvent; +#if NATIVEAOT // The method needs to be public in NativeAOT so that other private libraries can access it + public Lock(bool useTrivialWaits) +#else + internal Lock(bool useTrivialWaits) +#endif + : this() + { + if (useTrivialWaits) + { + _waiterStartTimeMsAndFlags = 1; + } + } + /// /// Enters the lock. Once the method returns, the calling thread would be the only thread that holds the lock. /// @@ -444,9 +460,9 @@ private ThreadId TryEnterSlow(int timeoutMs, ThreadId currentThreadId) Wait: bool areContentionEventsEnabled = - NativeRuntimeEventSource.Log?.IsEnabled( + NativeRuntimeEventSource.Log.IsEnabled( EventLevel.Informational, - NativeRuntimeEventSource.Keywords.ContentionKeyword) ?? false; + NativeRuntimeEventSource.Keywords.ContentionKeyword); AutoResetEvent waitEvent = _waitEvent ?? CreateWaitEvent(areContentionEventsEnabled); if (State.TryLockBeforeWait(this)) { @@ -463,7 +479,7 @@ private ThreadId TryEnterSlow(int timeoutMs, ThreadId currentThreadId) long waitStartTimeTicks = 0; if (areContentionEventsEnabled) { - NativeRuntimeEventSource.Log!.ContentionStart(this); + NativeRuntimeEventSource.Log.ContentionStart(this); waitStartTimeTicks = Stopwatch.GetTimestamp(); } @@ -472,7 +488,7 @@ private ThreadId TryEnterSlow(int timeoutMs, ThreadId currentThreadId) int remainingTimeoutMs = timeoutMs; while (true) { - if (!waitEvent.WaitOne(remainingTimeoutMs)) + if (!waitEvent.WaitOneNoCheck(remainingTimeoutMs, UseTrivialWaits)) { break; } @@ -535,7 +551,7 @@ private ThreadId TryEnterSlow(int timeoutMs, ThreadId currentThreadId) { double waitDurationNs = (Stopwatch.GetTimestamp() - waitStartTimeTicks) * 1_000_000_000.0 / Stopwatch.Frequency; - NativeRuntimeEventSource.Log!.ContentionStop(waitDurationNs); + NativeRuntimeEventSource.Log.ContentionStop(waitDurationNs); } return currentThreadId; @@ -551,7 +567,19 @@ private ThreadId TryEnterSlow(int timeoutMs, ThreadId currentThreadId) return new ThreadId(0); } - private void ResetWaiterStartTime() => _waiterStartTimeMs = 0; + // Trivial waits are: + // - Not interruptible by Thread.Interrupt + // - Don't allow reentrance through APCs or message pumping + // - Not forwarded to SynchronizationContext wait overrides + private bool UseTrivialWaits => (_waiterStartTimeMsAndFlags & 1) != 0; + + private ushort WaiterStartTimeMs + { + get => (ushort)(_waiterStartTimeMsAndFlags >> 1); + set => _waiterStartTimeMsAndFlags = (ushort)((value << 1) | (_waiterStartTimeMsAndFlags & 1)); + } + + private void ResetWaiterStartTime() => WaiterStartTimeMs = 0; [MethodImpl(MethodImplOptions.AggressiveInlining)] private void RecordWaiterStartTime() @@ -562,7 +590,7 @@ private void RecordWaiterStartTime() // Don't record zero, that value is reserved for indicating that a time is not recorded currentTimeMs--; } - _waiterStartTimeMs = currentTimeMs; + WaiterStartTimeMs = currentTimeMs; } private bool ShouldStopPreemptingWaiters @@ -571,7 +599,7 @@ private bool ShouldStopPreemptingWaiters get { // If the recorded time is zero, a time has not been recorded yet - ushort waiterStartTimeMs = _waiterStartTimeMs; + ushort waiterStartTimeMs = WaiterStartTimeMs; return waiterStartTimeMs != 0 && (ushort)Environment.TickCount - waiterStartTimeMs >= MaxDurationMsForPreemptingWaiters; diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitHandle.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitHandle.Unix.cs index 3ba1cb132e272..96253742b0319 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitHandle.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitHandle.Unix.cs @@ -7,8 +7,8 @@ namespace System.Threading { public abstract partial class WaitHandle { - private static int WaitOneCore(IntPtr handle, int millisecondsTimeout) => - WaitSubsystem.Wait(handle, millisecondsTimeout, true); + private static int WaitOneCore(IntPtr handle, int millisecondsTimeout, bool useTrivialWaits) => + WaitSubsystem.Wait(handle, millisecondsTimeout, interruptible: !useTrivialWaits); private static int WaitMultipleIgnoringSyncContextCore(Span handles, bool waitAll, int millisecondsTimeout) => WaitSubsystem.Wait(handles, waitAll, millisecondsTimeout); diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitHandle.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitHandle.Windows.cs index bae0a8f3a23c5..42827b5525653 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitHandle.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitHandle.Windows.cs @@ -14,11 +14,11 @@ private static unsafe int WaitMultipleIgnoringSyncContextCore(Span handl { fixed (IntPtr* pHandles = &MemoryMarshal.GetReference(handles)) { - return WaitForMultipleObjectsIgnoringSyncContext(pHandles, handles.Length, waitAll, millisecondsTimeout); + return WaitForMultipleObjectsIgnoringSyncContext(pHandles, handles.Length, waitAll, millisecondsTimeout, useTrivialWaits: false); } } - private static unsafe int WaitForMultipleObjectsIgnoringSyncContext(IntPtr* pHandles, int numHandles, bool waitAll, int millisecondsTimeout) + private static unsafe int WaitForMultipleObjectsIgnoringSyncContext(IntPtr* pHandles, int numHandles, bool waitAll, int millisecondsTimeout, bool useTrivialWaits) { Debug.Assert(millisecondsTimeout >= -1); @@ -27,7 +27,8 @@ private static unsafe int WaitForMultipleObjectsIgnoringSyncContext(IntPtr* pHan waitAll = false; #if NATIVEAOT // TODO: reentrant wait support https://github.com/dotnet/runtime/issues/49518 - bool reentrantWait = Thread.ReentrantWaitsEnabled; + // Trivial waits don't allow reentrance + bool reentrantWait = !useTrivialWaits && Thread.ReentrantWaitsEnabled; if (reentrantWait) { @@ -92,9 +93,9 @@ private static unsafe int WaitForMultipleObjectsIgnoringSyncContext(IntPtr* pHan return result; } - internal static unsafe int WaitOneCore(IntPtr handle, int millisecondsTimeout) + internal static unsafe int WaitOneCore(IntPtr handle, int millisecondsTimeout, bool useTrivialWaits) { - return WaitForMultipleObjectsIgnoringSyncContext(&handle, 1, false, millisecondsTimeout); + return WaitForMultipleObjectsIgnoringSyncContext(&handle, 1, false, millisecondsTimeout, useTrivialWaits); } private static int SignalAndWaitCore(IntPtr handleToSignal, IntPtr handleToWaitOn, int millisecondsTimeout) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitHandle.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitHandle.cs index 58b5d8341414b..21920bc39b754 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitHandle.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitHandle.cs @@ -106,6 +106,7 @@ public virtual bool WaitOne(int millisecondsTimeout) internal bool WaitOneNoCheck( int millisecondsTimeout, + bool useTrivialWaits = false, object? associatedObject = null, NativeRuntimeEventSource.WaitHandleWaitSourceMap waitSource = NativeRuntimeEventSource.WaitHandleWaitSourceMap.Unknown) { @@ -122,22 +123,26 @@ internal bool WaitOneNoCheck( waitHandle.DangerousAddRef(ref success); int waitResult = WaitFailed; - SynchronizationContext? context = SynchronizationContext.Current; - if (context != null && context.IsWaitNotificationRequired()) + + // Check if the wait should be forwarded to a SynchronizationContext wait override. Trivial waits don't allow + // reentrance or interruption, and are not forwarded. + bool usedSyncContextWait = false; + if (!useTrivialWaits) { - waitResult = context.Wait(new[] { waitHandle.DangerousGetHandle() }, false, millisecondsTimeout); + SynchronizationContext? context = SynchronizationContext.Current; + if (context != null && context.IsWaitNotificationRequired()) + { + usedSyncContextWait = true; + waitResult = context.Wait(new[] { waitHandle.DangerousGetHandle() }, false, millisecondsTimeout); + } } - else + + if (!usedSyncContextWait) { #if !CORECLR // CoreCLR sends the wait events from the native side bool sendWaitEvents = millisecondsTimeout != 0 && -#if NATIVEAOT - // A null check is necessary in NativeAOT due to the possibility of reentrance during class - // construction, as this path can be reached through Lock. See - // https://github.com/dotnet/runtime/issues/94728 for a call stack. - NativeRuntimeEventSource.Log != null && -#endif + !useTrivialWaits && NativeRuntimeEventSource.Log.IsEnabled( EventLevel.Verbose, NativeRuntimeEventSource.Keywords.WaitHandleKeyword); @@ -149,7 +154,7 @@ internal bool WaitOneNoCheck( waitSource != NativeRuntimeEventSource.WaitHandleWaitSourceMap.MonitorWait; if (tryNonblockingWaitFirst) { - waitResult = WaitOneCore(waitHandle.DangerousGetHandle(), millisecondsTimeout: 0); + waitResult = WaitOneCore(waitHandle.DangerousGetHandle(), 0 /* millisecondsTimeout */, useTrivialWaits); if (waitResult == WaitTimeout) { // Do a full wait and send the wait events @@ -171,7 +176,7 @@ internal bool WaitOneNoCheck( if (!tryNonblockingWaitFirst) #endif { - waitResult = WaitOneCore(waitHandle.DangerousGetHandle(), millisecondsTimeout); + waitResult = WaitOneCore(waitHandle.DangerousGetHandle(), millisecondsTimeout, useTrivialWaits); } #if !CORECLR // CoreCLR sends the wait events from the native side