You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Test process crashes (0xC0000409 / 0xC0000005) after opening a workshared model: the managed WriteFile detour leaves a stale runtime Thread when Revit terminates its worksharing log thread #136
Run the test project many times (dotnet run -c Release.R25). It is a race, so most runs pass.
✔️ Expected Behavior
Every run finishes with a summary.
❌ Actual Behavior
About 1 run in 13 (11 of 142) ends without a summary: exit 0xC0000409 (fail-fast in coreclr!__report_gsfailure),
sometimes 0xC0000005 or a stack overflow. The crash comes at the next garbage collection after the open, so it shows up
in whatever test runs then.
Environment: Nice3point.TUnit.Revit 2025.1.8 and the release before it, TUnit 1.76.45, Revit 2025 (25.4.30.30),
.NET 8 (coreclr 8.0.32), Windows 11 x64.
Cause
Found with full dumps and a small debugger that logs every thread's start and end and stops at each TerminateThread:
The injector detours kernelbase!WriteFile, WriteConsoleA and WriteConsoleW with PolyHook2 X64Detour and
managed callbacks. So every native thread that writes any file enters the runtime: it gets a runtime Thread the
runtime did not create (state 0x20220), and an InlinedCallFrame on its stack while the original WriteFile runs.
Opening a workshared model starts Revit's worksharing log writer thread. It writes the .slog with LockFileEx and WriteFile and lives about 4 s. When the open finishes, ~WorksharingLog, still inside OpenDocumentFile on the RevitDispatcherThread, ends it with TerminateThread. Usually the writer has exited by then.
If the writer is inside the detoured WriteFile at that moment, it dies with the runtime's Frame on its stack. The
runtime keeps its Thread in the thread store, with m_pFrame pointing into a stack Windows has freed and hands to
the next new thread.
The next GC (GcScanRoots → StackFrameIterator::Init → CrawlFrame::CheckGSCookies) reads whatever now occupies
that memory as a Frame: a fail-fast on the GS cookie, an access violation, or a stack overflow on a garbage chain.
Evidence:
In a run that crashed, the debugger stopped at TerminateThread with the writer still alive. The dump taken there
shows the writer inside WriteFile, whose first bytes jump into the PolyHook trampoline. The fail-fast followed 4 s
later on the dispatcher thread, at the next GC.
Three other full dumps of the crash each hold exactly one stale native-origin Thread whose Frame lies 1.2 to 2.4 KB
below the top of a freed stack.
Workaround
Test models saved without worksets: Revit then starts no writer thread, and 10 runs in 10 passed.
Suggestion
Keep threads that write ordinary files out of managed code: filter the handle in a small native stub and call into
managed code only for the std handles, or hook only WriteConsole*. Then a thread Revit terminates never leaves a
runtime Frame behind.
Activity
Nice3point commentedon Nov 1, 2026
https://github.com/Nice3point/RevitUnit/releases/tag/2027.0.9