From fb3216632ad094c4825751d676413dfb8021e6ce Mon Sep 17 00:00:00 2001 From: Milos Kotlar Date: Tue, 12 May 2026 16:31:36 +0200 Subject: [PATCH] Add cLockAllowLeak attribute to RSLock and use it on DbgTransportTarget lock 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, so DbgTransportTarget::Shutdown() may never run and the embedded RSLock m_sLock is left initialized at static destruction. The RSLock destructor then asserts on the leak in checked builds and the host aborts with exit 134, marking otherwise successful debugger tests as failures. Add a new cLockAllowLeak bit to RSLock::ELockAttr. The destructor honors it by suppressing the leak assert. Init the DbgTransportTarget lock with this bit set to opt out of the assert, with a comment that the missing Shutdown call on Linux is technically a bug tracked separately as low priority. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/coreclr/debug/di/dbgtransportmanager.cpp | 4 +++- src/coreclr/debug/di/rspriv.h | 4 ++++ src/coreclr/debug/di/rspriv.inl | 6 +++--- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/coreclr/debug/di/dbgtransportmanager.cpp b/src/coreclr/debug/di/dbgtransportmanager.cpp index 677d30854b6503..de3ee71b093944 100644 --- a/src/coreclr/debug/di/dbgtransportmanager.cpp +++ b/src/coreclr/debug/di/dbgtransportmanager.cpp @@ -74,7 +74,9 @@ DbgTransportTarget::DbgTransportTarget() // Initialization routine called only by the DbgTransportManager. HRESULT DbgTransportTarget::Init() { - m_sLock.Init("DbgTransportTarget Lock", RSLock::cLockFlat, RSLock::LL_DBG_TRANSPORT_TARGET_LOCK); + // The Unix loader does not invoke DbgDllMain DLL_PROCESS_DETACH for mscordbi at process exit, + // so Shutdown() may never run. Mark the lock as allowing leak to skip the destructor assert. + m_sLock.Init("DbgTransportTarget Lock", RSLock::cLockFlat | RSLock::cLockAllowLeak, RSLock::LL_DBG_TRANSPORT_TARGET_LOCK); return S_OK; } diff --git a/src/coreclr/debug/di/rspriv.h b/src/coreclr/debug/di/rspriv.h index 6caf7a556bf06f..076720e7999b46 100644 --- a/src/coreclr/debug/di/rspriv.h +++ b/src/coreclr/debug/di/rspriv.h @@ -716,6 +716,10 @@ class RSLock // to count this lock in m_cTotalDbgApiLocks, which is asserted to be 0 on entry // to public APIs. Example of such a lock: LL_SHIM_PROCESS_DISPOSE_LOCK cLockNonDbgApi = 0x00000004, + + // Skip the leak assert in the destructor. Use for static-lifetime locks + // whose owning shutdown path is not guaranteed to run. + cLockAllowLeak = 0x00000008, }; // To prevent deadlocks, we order all locks. diff --git a/src/coreclr/debug/di/rspriv.inl b/src/coreclr/debug/di/rspriv.inl index c88416b505c247..755de8d2bc7a64 100644 --- a/src/coreclr/debug/di/rspriv.inl +++ b/src/coreclr/debug/di/rspriv.inl @@ -504,9 +504,9 @@ inline RSLock::RSLock() inline RSLock::~RSLock() { - // If this lock is still ininitialized, then no body ever deleted the critical section - // for it and we're leaking. - CONSISTENCY_CHECK_MSGF(!IsInit(), ("Leaking Critical section for RS Lock '%s'", m_szTag)); + // If this lock is still initialized, then nobody ever deleted the critical section + // for it and we're leaking. cLockAllowLeak opts out of this assert. + CONSISTENCY_CHECK_MSGF(!IsInit() || (m_eAttr & cLockAllowLeak), ("Leaking Critical section for RS Lock '%s'", m_szTag)); } #endif