Conversation
|
Tagging subscribers to this area: @steveisok, @tommcdon, @dotnet/dotnet-diag |
There was a problem hiding this comment.
Pull request overview
This PR fixes cDAC thread “dead” semantics by treating ThreadState.ReportDead as dead for enumeration and IsThreadMarkedDead, while still allowing callers to distinguish Dead vs ReportDead when needed (e.g., monitor-lock ownership queries).
Changes:
- Update thread enumeration and
IsThreadMarkedDeadto considerReportDeadas dead. - Extend the managed thread contract to surface
ReportDeadand convert runtime state bits into contract flags. - Adjust Thread contract documentation to reflect that raw runtime state requires conversion to the contract enum.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs | Treat ReportDead threads as dead for enumeration and IsThreadMarkedDead. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs | Add mapping from runtime Thread::State bits (incl. TS_ReportDead) into contract ThreadState. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IThread.cs | Add ThreadState.ReportDead to the public contract enum. |
| docs/design/datacontracts/Thread.md | Note that Thread::State should be converted to the contract enum rather than used as a raw integer. |
...tive/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IThread.cs
Outdated
Show resolved
Hide resolved
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs
Outdated
Show resolved
Hide resolved
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs
Show resolved
Hide resolved
| Background = 0x00000200, // Thread is a background thread | ||
| Unstarted = 0x00000400, // Thread has never been started | ||
| Dead = 0x00000800, // Thread is dead | ||
| ReportDead = 0x00001000, // Thread is dead for the purposes of the debugger |
There was a problem hiding this comment.
What does it mean "for the purpose of the debugger"?
There was a problem hiding this comment.
We set ReportDead before Dead to indicate that a thread is done with useful work and is in cleanup - that is, not interesting to a debugger.
runtime/src/coreclr/vm/threads.cpp
Line 836 in 1c60926
Do you think we should or can combine the two under Dead? Aside from the debugger, we use the flag mostly for GC-related checks, and I'm not 100% sure if it'd be safe to do so.
There was a problem hiding this comment.
I would like to see the flag to be described in terms of invariants that it guarantees.
I understand Dead. it means that the thread is not going to run any managed code (nor native runtime code - except the very small amount of non-interesting unmanaged runtime code between this flag being set and return from the thread exit callback).
What are the invariants for ReportDead? I do not see anything that prevents ReportDead threads running managed code. If it has a fuzzy meaning "the thread is probably not going to do any interesting work and it is probably going to exit soon", we should document it as such. I wish we can come up with something more precise. I would be fine with adjusting the implementation to fit the more precise definition.
There was a problem hiding this comment.
Do you think we should or can combine the two under Dead?
Or delete the ReportDead flag. I am not sure.
You can give it a try and see what breaks to understand what the ReportFlag flag is actually used for.
There was a problem hiding this comment.
ReportDead is set in three places; immediately before thread destruction
runtime/src/coreclr/vm/threads.cpp
Line 836 in 76853ac
runtime/src/coreclr/vm/threads.cpp
Line 4397 in 76853ac
runtime/src/coreclr/vm/threads.cpp
Line 914 in 76853ac
ReportDead as "this thread will not do more interesting work" and leave it at that.
There was a problem hiding this comment.
How about this:
-
Delete the confusing remapping of
TS_ReportDeadtoTS_DeadfromGetSnapshotStateand adjust callers accordingly. It should be fine to assume that anyTS_Deadthread isTS_ReportedDeadas well, so it should be sufficient to check forTS_ReportedDeadinstead of both bits. It is possible that cDAC won't actually needTS_Deadafter this cleanup, I am not able to tell. -
Description for
TS_ReportDead:TS_ReportDeadis set when the thread starts shutting down and it is not expected to run managed code anymore. It corresponds toThreadState.Stoppedreported by the managedThread.ThreadStateAPI. Would it make sense to renameTS_ReportDeadtoTS_Stoppedto make it clear that it is same as managedThreadState.Stopped? -
Description for
TS_Dead:TS_Deadis set after the .NET runtime finished shutting down the thread, and the OS is about to terminate the thread.
87ae76b to
6561442
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated 5 comments.
Comments suppressed due to low confidence (1)
src/coreclr/vm/comsynchronizable.cpp:408
- ThreadNative_GetThreadState now reads the raw m_State via GetState(), but still only treats TS_Dead as "stopped". Previously GetSnapshotState() ORed TS_Dead when the reporting bit was set (now TS_Stopped), so this will fail to report shutting-down threads as stopped. Update the condition to treat TS_Stopped (and likely TS_Dead as well) as ThreadStopped to preserve behavior.
// grab a snapshot
Thread::ThreadState state = thread->GetState();
if (state & Thread::TS_Dead)
res |= ThreadNative::ThreadStopped;
...tive/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IThread.cs
Show resolved
Hide resolved
...native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs
Show resolved
Hide resolved
...native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs
Show resolved
Hide resolved
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs
Show resolved
Hide resolved
| // So ensure we mark the thread as dead before we send the tool notifications. | ||
| // The TS_ReportDead bit will cause the debugger to view this as TS_Dead. | ||
| _ASSERTE(HasThreadState(TS_ReportDead)); | ||
| // The TS_Stopped bit will cause the debugger to view this as TS_Dead. |
There was a problem hiding this comment.
| // The TS_Stopped bit will cause the debugger to view this as TS_Dead. |
Debugger sees TS_Stopped as TS_Stopped
We have to ensure that the thread is reported as dead when either
DeadorReportDeadare set. There are, however, certain cases where it is useful to distinguish between the two, for exampleGetThreadOwningMonitorLock.