From a277461a95583ef20a5c8a63c4ad3edf837536eb Mon Sep 17 00:00:00 2001 From: Mitchell Hwang Date: Thu, 9 Jul 2026 12:02:38 -0400 Subject: [PATCH] InProcCrashReporter: serialize concurrent reporters on direct-abort paths CreateReport() uses a CAS guard so only the first thread generates the crash report. Losing threads previously returned immediately, then continued through PROCAbort() -> abort(), tearing down the process while the winner was still mid-report and truncating it. On the common fatal-signal path this is already prevented upstream (EEPolicy::LogInfoForFatalError and the stack-overflow guard block losers before they reach the in-proc reporter). It can still be entered concurrently via direct PROCAbort paths that bypass those guards (e.g. exception-record pool exhaustion, FATAL_ASSERT, RaiseFailFastException). createdump's serialize=true path already blocks losers, but PROCCreateCrashDumpIfEnabled short-circuits into the in-proc callback and skips it, so the in-proc reporter must serialize itself. Mirror createdump: one InterlockedCompareExchange64 claims the guard and records the owner. Losers block in poll() until the winner's abort() tears the process down; a re-entrant fatal signal on the winning thread bails out instead of deadlocking. All threads are already captured in the winner's report, so there is no information loss. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: e9f8de07-6c05-4570-9f33-90bce6550391 --- .../debug/crashreport/inproccrashreporter.cpp | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/coreclr/debug/crashreport/inproccrashreporter.cpp b/src/coreclr/debug/crashreport/inproccrashreporter.cpp index 95d62e95a38651..255a7398b82d3b 100644 --- a/src/coreclr/debug/crashreport/inproccrashreporter.cpp +++ b/src/coreclr/debug/crashreport/inproccrashreporter.cpp @@ -15,6 +15,7 @@ #include #include +#include #include #include #include @@ -151,7 +152,7 @@ static void CacheSysctlString(const char* sysctlName, char* buffer, size_t buffe // just less compact for that frame. // // Single-instance because CreateReport is one-shot per process (guarded by -// the ``s_generating`` InterlockedCompareExchange in CreateReport). +// the ``s_generatingThreadId`` InterlockedCompareExchange64 in CreateReport). static constexpr int MAX_MODULES_IN_TABLE = 256; @@ -603,10 +604,24 @@ InProcCrashReporter::CreateReport( int signal, void* context) { - static LONG s_generating = 0; - if (InterlockedCompareExchange(&s_generating, 1, 0) != 0) + static LONGLONG s_generatingThreadId = 0; + + // INFTIM is not defined when including pal.h; -1 is the equivalent poll() "wait forever" timeout. + const int PollWaitForever = -1; + + LONGLONG currentThreadId = static_cast(minipal_get_current_thread_id()); + LONGLONG previousThreadId = InterlockedCompareExchange64(&s_generatingThreadId, currentThreadId, 0); + if (previousThreadId != 0) { - return; + if (previousThreadId == currentThreadId) + { + return; + } + + while (true) + { + poll(nullptr, 0, PollWaitForever); + } } m_reportFilePath[0] = '\0';