Skip to content

Commit ce0ae49

Browse files
authored
Add managed blocking info for lock and monitor waits (#101192)
* Add managed blocking info for lock and monitor waits - Added a managed `ThreadBlockingInfo` struct that is similar to CoreCLR's `DebugBlockingItem` - Updated `Lock`, `Condition`, and `Monitor` to record the info - The `LockOwnerOSThreadId` and `LockOwnerManagedThreadId` properties can be used by debuggers to determine which thread owns a lock that the current thread is waiting on - In CoreCLR, `Monitor` records the info using the `Monitor` object kinds. In NativeAOT, `Lock` and `Condition` are used for `Monitor` waits, so the object kinds would be `Lock/Condition`. For now, Mono's `Monitor` does not record this info.
1 parent 8f1e68a commit ce0ae49

File tree

12 files changed

+303
-13
lines changed

12 files changed

+303
-13
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ private static Waiter GetWaiterForCurrentThread()
3232
private Waiter? _waitersHead;
3333
private Waiter? _waitersTail;
3434

35+
internal Lock AssociatedLock => _lock;
36+
3537
private unsafe void AssertIsInList(Waiter waiter)
3638
{
3739
Debug.Assert(_waitersHead != null && _waitersTail != null);
@@ -106,6 +108,8 @@ public unsafe bool Wait(int millisecondsTimeout, object? associatedObjectForMoni
106108
if (!_lock.IsHeldByCurrentThread)
107109
throw new SynchronizationLockException();
108110

111+
using ThreadBlockingInfo.Scope threadBlockingScope = new(this, millisecondsTimeout);
112+
109113
Waiter waiter = GetWaiterForCurrentThread();
110114
AddWaiter(waiter);
111115

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ public sealed partial class Lock
2424
/// </summary>
2525
public Lock() => _spinCount = SpinCountNotInitialized;
2626

27+
#pragma warning disable CA1822 // can be marked as static - varies between runtimes
28+
internal ulong OwningOSThreadId => 0;
29+
#pragma warning restore CA1822
30+
31+
internal int OwningManagedThreadId => (int)_owningThreadId;
32+
2733
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2834
internal bool TryEnterOneShot(int currentManagedThreadId)
2935
{

src/coreclr/vm/appdomain.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1158,6 +1158,13 @@ void SystemDomain::Init()
11581158

11591159
// Finish loading CoreLib now.
11601160
m_pSystemAssembly->GetDomainAssembly()->EnsureActive();
1161+
1162+
// Set AwareLock's offset of the holding OS thread ID field into ThreadBlockingInfo's static field. That can be used
1163+
// when doing managed debugging to get the OS ID of the thread holding the lock. The offset is currently not zero, and
1164+
// zero is used in managed code to determine if the static variable has been initialized.
1165+
_ASSERTE(AwareLock::GetOffsetOfHoldingOSThreadId() != 0);
1166+
CoreLibBinder::GetField(FIELD__THREAD_BLOCKING_INFO__OFFSET_OF_LOCK_OWNER_OS_THREAD_ID)
1167+
->SetStaticValue32(AwareLock::GetOffsetOfHoldingOSThreadId());
11611168
}
11621169

11631170
#ifdef _DEBUG

src/coreclr/vm/corelib.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -583,6 +583,10 @@ END_ILLINK_FEATURE_SWITCH()
583583
DEFINE_CLASS(MONITOR, Threading, Monitor)
584584
DEFINE_METHOD(MONITOR, ENTER, Enter, SM_Obj_RetVoid)
585585

586+
DEFINE_CLASS(THREAD_BLOCKING_INFO, Threading, ThreadBlockingInfo)
587+
DEFINE_FIELD(THREAD_BLOCKING_INFO, OFFSET_OF_LOCK_OWNER_OS_THREAD_ID, s_monitorObjectOffsetOfLockOwnerOSThreadId)
588+
DEFINE_FIELD(THREAD_BLOCKING_INFO, FIRST, t_first)
589+
586590
DEFINE_CLASS(PARAMETER, Reflection, ParameterInfo)
587591

588592
DEFINE_CLASS(PARAMETER_MODIFIER, Reflection, ParameterModifier)

src/coreclr/vm/syncblk.cpp

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2851,22 +2851,23 @@ BOOL SyncBlock::Wait(INT32 timeOut)
28512851

28522852
_ASSERTE ((SyncBlock*)((DWORD_PTR)walk->m_Next->m_WaitSB & ~1)== this);
28532853

2854-
PendingSync syncState(walk);
2855-
2856-
OBJECTREF obj = m_Monitor.GetOwningObject();
2857-
syncState.m_Object = OBJECTREFToObject(obj);
2858-
2859-
m_Monitor.IncrementTransientPrecious();
2860-
28612854
// While we are in this frame the thread is considered blocked on the
2862-
// event of the monitor lock according to the debugger
2855+
// event of the monitor lock according to the debugger. DebugBlockingItemHolder
2856+
// can trigger a GC, so set it up before accessing the owning object.
28632857
DebugBlockingItem blockingMonitorInfo;
28642858
blockingMonitorInfo.dwTimeout = timeOut;
28652859
blockingMonitorInfo.pMonitor = &m_Monitor;
28662860
blockingMonitorInfo.pAppDomain = SystemDomain::GetCurrentDomain();
28672861
blockingMonitorInfo.type = DebugBlock_MonitorEvent;
28682862
DebugBlockingItemHolder holder(pCurThread, &blockingMonitorInfo);
28692863

2864+
PendingSync syncState(walk);
2865+
2866+
OBJECTREF obj = m_Monitor.GetOwningObject();
2867+
syncState.m_Object = OBJECTREFToObject(obj);
2868+
2869+
m_Monitor.IncrementTransientPrecious();
2870+
28702871
GCPROTECT_BEGIN(obj);
28712872
{
28722873
GCX_PREEMP();

src/coreclr/vm/syncblk.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -602,6 +602,12 @@ class AwareLock
602602
LIMITED_METHOD_CONTRACT;
603603
return m_HoldingThread;
604604
}
605+
606+
static int GetOffsetOfHoldingOSThreadId()
607+
{
608+
LIMITED_METHOD_CONTRACT;
609+
return (int)offsetof(AwareLock, m_HoldingOSThreadId);
610+
}
605611
};
606612

607613
#ifdef FEATURE_COMINTEROP

src/coreclr/vm/threaddebugblockinginfo.cpp

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,37 @@ VOID ThreadDebugBlockingInfo::VisitBlockingItems(DebugBlockingItemVisitor visito
7272
// Holder constructor pushes a blocking item on the blocking info stack
7373
#ifndef DACCESS_COMPILE
7474
DebugBlockingItemHolder::DebugBlockingItemHolder(Thread *pThread, DebugBlockingItem *pItem) :
75-
m_pThread(pThread)
75+
m_pThread(pThread), m_ppFirstBlockingInfo(nullptr)
7676
{
77-
LIMITED_METHOD_CONTRACT;
77+
CONTRACTL
78+
{
79+
THROWS;
80+
GC_TRIGGERS;
81+
MODE_COOPERATIVE;
82+
}
83+
CONTRACTL_END;
84+
85+
// Try to get the address of the thread-local slot for the managed ThreadBlockingInfo.t_first
86+
EX_TRY
87+
{
88+
FieldDesc *pFD = CoreLibBinder::GetField(FIELD__THREAD_BLOCKING_INFO__FIRST);
89+
m_ppFirstBlockingInfo = (ThreadBlockingInfo **)Thread::GetStaticFieldAddress(pFD);
90+
}
91+
EX_CATCH
92+
{
93+
}
94+
EX_END_CATCH(RethrowTerminalExceptions);
95+
96+
if (m_ppFirstBlockingInfo != nullptr)
97+
{
98+
// Push info for the managed ThreadBlockingInfo
99+
m_blockingInfo.objectPtr = pItem->pMonitor;
100+
m_blockingInfo.objectKind = (ThreadBlockingInfo::ObjectKind)pItem->type;
101+
m_blockingInfo.timeoutMs = (INT32)pItem->dwTimeout;
102+
m_blockingInfo.next = *m_ppFirstBlockingInfo;
103+
*m_ppFirstBlockingInfo = &m_blockingInfo;
104+
}
105+
78106
pThread->DebugBlockingInfo.PushBlockingItem(pItem);
79107
}
80108
#endif //DACCESS_COMPILE
@@ -84,6 +112,17 @@ m_pThread(pThread)
84112
DebugBlockingItemHolder::~DebugBlockingItemHolder()
85113
{
86114
LIMITED_METHOD_CONTRACT;
115+
87116
m_pThread->DebugBlockingInfo.PopBlockingItem();
117+
118+
if (m_ppFirstBlockingInfo != nullptr)
119+
{
120+
// Pop info for the managed ThreadBlockingInfo
121+
_ASSERTE(
122+
m_ppFirstBlockingInfo ==
123+
(void *)m_pThread->GetStaticFieldAddrNoCreate(CoreLibBinder::GetField(FIELD__THREAD_BLOCKING_INFO__FIRST)));
124+
_ASSERTE(*m_ppFirstBlockingInfo == &m_blockingInfo);
125+
*m_ppFirstBlockingInfo = m_blockingInfo.next;
126+
}
88127
}
89128
#endif //DACCESS_COMPILE

src/coreclr/vm/threaddebugblockinginfo.h

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
// Different ways thread can block that the debugger will expose
1515
enum DebugBlockingItemType
1616
{
17-
DebugBlock_MonitorCriticalSection,
18-
DebugBlock_MonitorEvent,
17+
DebugBlock_MonitorCriticalSection, // maps to ThreadBlockingInfo.ObjectKind.MonitorLock below and in managed code
18+
DebugBlock_MonitorEvent, // maps to ThreadBlockingInfo.ObjectKind.MonitorWait below and in managed code
1919
};
2020

2121
typedef DPTR(struct DebugBlockingItem) PTR_DebugBlockingItem;
@@ -65,15 +65,35 @@ class ThreadDebugBlockingInfo
6565
};
6666

6767
#ifndef DACCESS_COMPILE
68+
69+
// This is the equivalent of the managed ThreadBlockingInfo (see ThreadBlockingInfo.cs), which is used for tracking blocking
70+
// info from the managed side, similarly to DebugBlockingItem
71+
struct ThreadBlockingInfo
72+
{
73+
enum class ObjectKind : INT32
74+
{
75+
MonitorLock, // maps to DebugBlockingItemType::DebugBlock_MonitorCriticalSection
76+
MonitorWait // maps to DebugBlockingItemType::DebugBlock_MonitorEvent
77+
};
78+
79+
void *objectPtr;
80+
ObjectKind objectKind;
81+
INT32 timeoutMs;
82+
ThreadBlockingInfo *next;
83+
};
84+
6885
class DebugBlockingItemHolder
6986
{
7087
private:
7188
Thread *m_pThread;
89+
ThreadBlockingInfo **m_ppFirstBlockingInfo;
90+
ThreadBlockingInfo m_blockingInfo;
7291

7392
public:
7493
DebugBlockingItemHolder(Thread *pThread, DebugBlockingItem *pItem);
7594
~DebugBlockingItemHolder();
7695
};
96+
7797
#endif //!DACCESS_COMPILE
7898

7999
#endif // __ThreadBlockingInfo__

src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1265,6 +1265,7 @@
12651265
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\Thread.cs" />
12661266
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\ProcessorIdCache.cs" />
12671267
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\ThreadAbortException.cs" />
1268+
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\ThreadBlockingInfo.cs" />
12681269
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\ThreadExceptionEventArgs.cs" />
12691270
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\ThreadInt64PersistentCounter.cs" />
12701271
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\ThreadInterruptedException.cs" />
@@ -2786,4 +2787,4 @@
27862787
<Compile Include="$(MSBuildThisFileDirectory)System\Numerics\IUnaryPlusOperators.cs" />
27872788
<Compile Include="$(MSBuildThisFileDirectory)System\Numerics\IUnsignedNumber.cs" />
27882789
</ItemGroup>
2789-
</Project>
2790+
</Project>

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ public sealed partial class Lock
1616
/// </summary>
1717
public Lock() => _spinCount = s_maxSpinCount;
1818

19+
internal ulong OwningOSThreadId => _owningThreadId;
20+
21+
#pragma warning disable CA1822 // can be marked as static - varies between runtimes
22+
internal int OwningManagedThreadId => 0;
23+
#pragma warning restore CA1822
24+
1925
private static TryLockResult LazyInitializeOrEnter() => TryLockResult.Spin;
2026
private static bool IsSingleProcessor => Environment.IsSingleProcessor;
2127

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,8 @@ private ThreadId TryEnterSlow(int timeoutMs, ThreadId currentThreadId)
477477
waitStartTimeTicks = Stopwatch.GetTimestamp();
478478
}
479479

480+
using ThreadBlockingInfo.Scope threadBlockingScope = new(this, timeoutMs);
481+
480482
bool acquiredLock = false;
481483
int waitStartTimeMs = timeoutMs < 0 ? 0 : Environment.TickCount;
482484
int remainingTimeoutMs = timeoutMs;

0 commit comments

Comments
 (0)