ConsoleRunner.Run returns without disposing MessageBus.ReporterWorker — test EXE lingers after run completes
Versions
- xunit.v3 3.2.2 (
xunit.v3.core.mtp-v1 3.2.2 transitively)
- xunit.runner.visualstudio (latest)
- Microsoft.NET.Test.Sdk + Microsoft.Testing.Platform.MSBuild present
- .NET 10.0.300 SDK
- Windows 11 25H2, x64
- Test project:
<OutputType>Exe</OutputType> + <UseWPF>true</UseWPF>
Symptom
After dotnet test reports Passed! and a zero exit code, the test EXE
process (the xUnit v3 auto-generated self-host) stays alive 20 to 220 seconds
before the OS reaps it. The lingering EXE holds an exclusive lock on the
test assembly DLL in tests/bin/Release/, so the next dotnet test build
fails with MSB3027 trying to copy the apphost over the locked EXE.
The linger time is non-deterministic and varies with which test class ran:
| Test selection |
Test runtime |
Process linger |
| 1 trivial pure-data test |
32ms |
0s |
| ~77 pure-data assertion tests in one class |
889ms |
0s |
| Any 1 test from a class whose nested types reference EF Core + SQLite |
45ms |
20-60s |
| Full suite (~3700 tests) |
9 min |
20-220s |
The trigger correlates with classes whose type graph references EF Core +
SQLite, but the actual hang is in the runner, not in those tests — even a
pure-data static-method test in such a class triggers the linger.
Root cause (confirmed via dotnet-dump)
Main thread blocked at:
XunitAutoGeneratedEntryPoint.Main (line 13)
→ TestPlatformTestFramework.RunAsync(...).GetAwaiter().GetResult()
→ Task.SpinThenBlockingWait → Monitor.Wait
The Task being waited on never completes because a foreground thread is
stuck at:
Xunit.Internal.MessageBus.ReporterWorker() line 91
→ WaitHandle.WaitOne()
MessageBus.ReporterWorker is not a background thread, so the .NET runtime
won't let the process exit while it's running. ConsoleRunner.Run
(and TestPlatformTestFramework.RunAsync underneath) report results and
return the exit code without signalling the reporter worker to shut down or
disposing the MessageBus.
Reproduction
Minimal repro shape (have not extracted a standalone project, but should be
reproducible with any xunit.v3 3.2.2 self-host EXE that touches EF Core
SQLite from a test):
<OutputType>Exe</OutputType> test project referencing xunit.v3 3.2.2.
- One test that constructs an
IDbContextFactory<TContext> backed by
SQLite and calls db.Database.EnsureCreated() inside a using.
dotnet test --filter <ThatOneTest>.
- Tests pass cleanly. Observe the test EXE stays alive in Task Manager for
20+ seconds after dotnet test returns.
Confirmed on both code paths:
- Auto-generated entry point in MTP server mode (
--server arg present)
- Custom entry point calling
ConsoleRunner.Run directly (MTP bypassed)
Both stop in the same place — the bus reporter worker is shared.
Workaround attempts and their downsides
-
<UseMicrosoftTestingPlatformRunner>false</UseMicrosoftTestingPlatformRunner> in csproj
— no effect; the entry-point cache wasn't invalidated and the regenerated
Main still branched on --server.
-
Custom Program.cs with ConsoleRunner.Run only (suppressing the
auto-generated entry via <XunitAutoGeneratedEntryPoint>false</...>)
— hang still occurs at the same place in MessageBus.ReporterWorker.
-
Environment.Exit(exitCode) after ConsoleRunner.Run returns
— eliminates the linger but dotnet test reports "Test host process
crashed" → "Test Run Aborted" even on green test runs, breaking CI.
Asks
- Confirm whether
MessageBus.ReporterWorker should be marked background, or
whether ConsoleRunner.Run should explicitly dispose the MessageBus before
returning.
- A fix in xunit.v3 itself would let consumers drop pre-build orphan-kill
workarounds.
ConsoleRunner.Run returns without disposing MessageBus.ReporterWorker — test EXE lingers after run completes
Versions
xunit.v3.core.mtp-v13.2.2 transitively)<OutputType>Exe</OutputType>+<UseWPF>true</UseWPF>Symptom
After
dotnet testreportsPassed!and a zero exit code, the test EXEprocess (the xUnit v3 auto-generated self-host) stays alive 20 to 220 seconds
before the OS reaps it. The lingering EXE holds an exclusive lock on the
test assembly DLL in
tests/bin/Release/, so the nextdotnet testbuildfails with MSB3027 trying to copy the apphost over the locked EXE.
The linger time is non-deterministic and varies with which test class ran:
The trigger correlates with classes whose type graph references EF Core +
SQLite, but the actual hang is in the runner, not in those tests — even a
pure-data static-method test in such a class triggers the linger.
Root cause (confirmed via dotnet-dump)
Main thread blocked at:
The Task being waited on never completes because a foreground thread is
stuck at:
MessageBus.ReporterWorkeris not a background thread, so the .NET runtimewon't let the process exit while it's running.
ConsoleRunner.Run(and
TestPlatformTestFramework.RunAsyncunderneath) report results andreturn the exit code without signalling the reporter worker to shut down or
disposing the MessageBus.
Reproduction
Minimal repro shape (have not extracted a standalone project, but should be
reproducible with any xunit.v3 3.2.2 self-host EXE that touches EF Core
SQLite from a test):
<OutputType>Exe</OutputType>test project referencingxunit.v33.2.2.IDbContextFactory<TContext>backed bySQLite and calls
db.Database.EnsureCreated()inside ausing.dotnet test --filter <ThatOneTest>.20+ seconds after
dotnet testreturns.Confirmed on both code paths:
--serverarg present)ConsoleRunner.Rundirectly (MTP bypassed)Both stop in the same place — the bus reporter worker is shared.
Workaround attempts and their downsides
<UseMicrosoftTestingPlatformRunner>false</UseMicrosoftTestingPlatformRunner>in csproj— no effect; the entry-point cache wasn't invalidated and the regenerated
Main still branched on
--server.Custom
Program.cswithConsoleRunner.Runonly (suppressing theauto-generated entry via
<XunitAutoGeneratedEntryPoint>false</...>)— hang still occurs at the same place in MessageBus.ReporterWorker.
Environment.Exit(exitCode)afterConsoleRunner.Runreturns— eliminates the linger but
dotnet testreports "Test host processcrashed" → "Test Run Aborted" even on green test runs, breaking CI.
Asks
MessageBus.ReporterWorkershould be marked background, orwhether
ConsoleRunner.Runshould explicitly dispose the MessageBus beforereturning.
workarounds.