-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add new version of the ContentionStart event #72627
Changes from 7 commits
1988694
5fb79fd
d6d6398
d214618
4550dc5
1559e3d
ba3cca1
292656e
4d529a5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2237,10 +2237,14 @@ SyncBlock *ObjHeader::GetSyncBlock() | |
|
||
LEAVE_SPIN_LOCK(this); | ||
} | ||
// SyncBlockCache::LockHolder goes out of scope here | ||
} | ||
// SyncBlockCache::LockHolder goes out of scope here | ||
} | ||
|
||
// Fire the LockCreated event last, such that it is outside the FAULT_FORBID region above and outside the SyncBlockCache | ||
// lock. The eventing system may need to allocate. | ||
syncBlock->FireLockCreatedEvent(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this guarantee that we only fire this event when a lock is created? There are other paths that create syncblocks, right? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I didn't see any other paths that create sync blocks There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From what I can see, there are others. Here's an example: https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/object.cpp#L987 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like this path also goes through There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right. There are others too, so I don't think this is the right place to emit the event, as it will be potentially much more verbose (and duplicative) than we want. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
That's only true for precious syncblocks. Syncblocks that are not marked as precious can be reclaimed and re-created multiple times. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Wouldn't that mean that each time a sync block is recreated for an object, that it may have a different pointer to the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, that's correct. I think that Brian's concern is that we will emit this event for all SyncBlocks even the ones that are not ever used for locks. SyncBlocks are created for number of different reasons (the name "syncblock" is misleading). For example, interop creates them a lot for COM objects and marshalled delegates. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. What do you think about having the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see. Yes, it could probably be emitted lazily. Taking a step back, is the lock ID really necessary? The stack trace of the contention event would probably be sufficient to identify the lock. I figure the |
||
|
||
RETURN syncBlock; | ||
} | ||
|
||
|
@@ -2541,15 +2545,19 @@ BOOL AwareLock::EnterEpilogHelper(Thread* pCurThread, INT32 timeOut) | |
// the object associated with this lock. | ||
_ASSERTE(pCurThread->PreemptiveGCDisabled()); | ||
|
||
BOOLEAN IsContentionKeywordEnabled = ETW_TRACING_CATEGORY_ENABLED(MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_DOTNET_Context, TRACE_LEVEL_INFORMATION, CLR_CONTENTION_KEYWORD); | ||
bool isContentionKeywordEnabled = IsContentionKeywordEnabled(); | ||
LARGE_INTEGER startTicks = { {0} }; | ||
|
||
if (IsContentionKeywordEnabled) | ||
if (isContentionKeywordEnabled) | ||
{ | ||
QueryPerformanceCounter(&startTicks); | ||
|
||
// Fire a contention start event for a managed contention | ||
FireEtwContentionStart_V1(ETW::ContentionLog::ContentionStructs::ManagedContention, GetClrInstanceId()); | ||
FireEtwContentionStart_V2( | ||
ETW::ContentionLog::ContentionStructs::ManagedContention, | ||
GetClrInstanceId(), | ||
this, | ||
m_HoldingThread); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The last argument here should actually be the OS thread ID. This allows users of the event to match the lock up with the thread that is holding it in a trace. The memory address of the lock won't allow for this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I commented on this in #72627 (comment). It looks like the holding thread pointer cannot be dereferenced here because there's not guarantee that it would be alive. The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For threads that are created from outside the runtime and then enter the runtime, I believe the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we can depend on the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
That's also true for the new
I'm not sure about that, coop GC mode would ensure that the managed thread object would be alive if accessible, but once the managed thread object is deemed unreachable and the finalizer runs, perhaps the native thread object can be deleted irrespective of coop GC mode. Maybe there are further constraints that I'm not aware of? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Why would the
Why would there not be a similar problem to what you mentioned before regarding missing events when starting a trace on an already-running process? For instance, the create/destroy OS events for threads may not be captured by the trace.
The information stored is probably minimal, in the sense what is stored is what is most easily accessible for correct behavior, a different implementation may store something else and that should be ok. As I mentioned before, storing the thread ID would involve an indirection. Changing the implementation for this particular reason is probably something that would need a lot more scrutiny, I don't see enough reason for it yet. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
You're right, it shouldn't. I missed the fact that the cache lookup occurs at the beginning of this function, and returns early.
You technically don't need to see the thread creation event to know that it existed at the beginning of the trace, as long as you see samples or events on the thread. What you do need is to have a thread creation event during the trace to know that the thread ID has been re-used. That said, the OS provides thread/process rundown events at the beginning of the trace to tell profilers about the existence of all processes and traces.
I'm not sure I follow. Can you tell me a little bit more here? Are you saying that you don't think having the OS Thread ID is worthwhile here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Curious, does this work with circular buffers? More generally, is there some mechanism by which a trace taken with a circular buffer, where the buffer fills up and is overwritten, can still have an accurate snapshot of live objects through events at the beginning of the trace?
No, mapping to a thread ID would probably be the most useful part. I don't think we should change or add to what is stored for the lock-holding thread. Perhaps taking the thread store lock could enable emitting the OS thread ID. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A thread that orphans a lock does not clear the holding thread pointer before its There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated in the latest commit to store and send the OS thread ID, since I think it would be possible to continue doing so after the complete fixes. Still curious about the question in #72627 (comment), as I've run into that issue before and a solution to it may be useful elsewhere. |
||
} | ||
|
||
LogContention(); | ||
|
@@ -2684,7 +2692,7 @@ BOOL AwareLock::EnterEpilogHelper(Thread* pCurThread, INT32 timeOut) | |
GCPROTECT_END(); | ||
DecrementTransientPrecious(); | ||
|
||
if (IsContentionKeywordEnabled) | ||
if (isContentionKeywordEnabled) | ||
{ | ||
LARGE_INTEGER endTicks; | ||
QueryPerformanceCounter(&endTicks); | ||
|
@@ -2765,6 +2773,22 @@ BOOL AwareLock::OwnedByCurrentThread() | |
return (GetThread() == m_HoldingThread); | ||
} | ||
|
||
/* static */ | ||
bool AwareLock::IsContentionKeywordEnabled() | ||
{ | ||
WRAPPER_NO_CONTRACT; | ||
return ETW_TRACING_CATEGORY_ENABLED(MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_DOTNET_Context, TRACE_LEVEL_INFORMATION, CLR_CONTENTION_KEYWORD); | ||
} | ||
|
||
void AwareLock::FireLockCreatedEvent() const | ||
{ | ||
WRAPPER_NO_CONTRACT; | ||
|
||
if (IsContentionKeywordEnabled()) | ||
{ | ||
FireEtwLockCreated(this, GetClrInstanceId()); | ||
} | ||
} | ||
|
||
// *************************************************************************** | ||
// | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if putting the LockId and OwnerId wouldn't be better on the Stop event, WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it makes more sense on the start event, since you want to know what was holding it that triggered the contention