[clr-interp] Release DbgTransportTarget RSLock on static destruction#127754
[clr-interp] Release DbgTransportTarget RSLock on static destruction#127754kotlarmilos wants to merge 1 commit intodotnet:mainfrom
Conversation
|
Tagging subscribers to this area: @steveisok, @tommcdon, @dotnet/dotnet-diag |
There was a problem hiding this comment.
Pull request overview
This PR adds a defensive DbgTransportTarget destructor so the right-side debug transport can clean up its lock and tracked sessions during static destruction, covering Unix teardown paths where DbgDllMain(DLL_PROCESS_DETACH) may not run.
Changes:
- Declares an explicit destructor for
DbgTransportTarget. - Adds destructor logic that calls
Shutdown()when the transport lock appears initialized. - Keeps the fix localized to the debug transport manager used by the CoreCLR debugger transport layer.
Show a summary per file
| File | Description |
|---|---|
src/coreclr/debug/di/dbgtransportmanager.h |
Declares the new DbgTransportTarget destructor. |
src/coreclr/debug/di/dbgtransportmanager.cpp |
Implements the destructor and conditionally invokes Shutdown() during static destruction. |
Copilot's findings
- Files reviewed: 2/2 changed files
- Comments generated: 1
The static g_DbgTransportTarget instance is destroyed after the host process's main() returns. On Unix the dynamic loader does not invoke DbgDllMain(DLL_PROCESS_DETACH) for mscordbi.dylib, so DbgTransportTarget::Shutdown() may never run and the embedded RSLock m_sLock is left in its initialized state. In debug builds the RSLock destructor asserts that the lock was Destroy()'d before destruction, so the host process aborts with 'Leaking Critical section for RS Lock DbgTransportTarget Lock' and exits 134. The test harness then marks every otherwise successful debugger test as a failure during teardown. Add a defensive ~DbgTransportTarget() that invokes Shutdown() when the lock is still initialized. This affects every debugger test that exits cleanly through the static destructor path. Locally on osx-arm64 Debugger.Tests --clrinterpreter the xunit fail count drops from 230 to 65, with the following tests no longer reported as failing on top of the existing baseline: - Inspection.BasicInspection, Inspection.EnumNames, Inspection.GenericInterfaces, Inspection.GenericSharedStatic, Inspection.GenericStatics, Inspection.NestedGenerics, Inspection.inspect_SetVars - Stackwalking.ClrMethods - Stepping.setIPTestwithUnvaildPos, Stepping.stepin, Stepping.stepover - Async.AsyncStepInto Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
48c8e47 to
e964c1c
Compare
|
As @janvorli noted, this is nice-to-have. The tests are intended to run primarily against release where the assert does not fire, so this only cleans up checked-build noise. The downside is that the order in which destructors of statics are executed is undefined, so one destructor can easily depend on something that was already destroyed by another. |
|
I would prefer fixing the issue by not using a static instance of the DbgTransportTarget instead. When the However, I can see that @AaronRobinsonMSFT has introduced this static destructor recently and removed dynamic allocation of the transport, so I'd like to know his opinion first. |
Description
The static
g_DbgTransportTargetinstance is destroyed after the host process'smain()returns. On unix (desktop interpreter) the dynamic loader does not invokeDbgDllMain(DLL_PROCESS_DETACH)formscordbi.dylib, soDbgTransportTarget::Shutdown()may never run and the embeddedRSLock m_sLockis left in its initialized state. In debug builds theRSLockdestructor asserts that the lock wasDestroy()'d before destruction, so the host process aborts.This affects every debugger test that exits cleanly through the static destructor path. Locally on osx-arm64
Debugger.Tests --clrinterpreterthe xunit fail count drops from 230 to 65:Inspection.BasicInspection,Inspection.EnumNames,Inspection.GenericInterfaces,Inspection.GenericSharedStatic,Inspection.GenericStatics,Inspection.NestedGenerics,Inspection.inspect_SetVarsStackwalking.ClrMethodsStepping.setIPTestwithUnvaildPos,Stepping.stepin,Stepping.stepoverAsync.AsyncStepInto