Root cause: dotnet runs as container PID 1 and Linux ignores default-action
signals sent to PID 1, so the runtime's unhandled-exception path (banner,
then abort() -> SIGABRT) could never terminate the process — it printed the
trace and spun the main thread at 100% CPU with the container `running`,
so `restart: unless-stopped` never fired. Reproduced deterministically:
same StartupValidator throw exits 134 under an init process and wedges
without one.
Two layers, each covering the other's gap:
- Program.cs registers an AppDomain.UnhandledException handler before the
first statement that can throw: prints the trace, best-effort flushes
Serilog, Environment.Exit(134) — exit() is a syscall PID 1 CAN perform,
134 preserves the 128+SIGABRT crash code, and it covers every thread,
not just the boot window. It cannot fire under WebApplicationFactory
(the test host catches entry-point exceptions), so the designed
boot-refusal exceptions still propagate to tests unchanged.
- docker-compose: init: true on all 8 nodes for the crash paths that
bypass the managed event (Environment.FailFast, runtime-internal aborts).
The CoordinatedShutdown no-Environment.Exit guard gains a precise carve-out
(exactly one call, only inside the handler); Environment.Exit still fires
the CLR shutdown hook Akka binds via run-by-clr-shutdown-hook = on, so the
crash path skips nothing abort() kept. New pin test keeps the handler ahead
of the configuration build.
Live-verified on the rig image: crash now yields Exited (134) +
RestartCount climbing under `unless-stopped`, trace intact, with and
without init; full 8-node rig redeployed healthy with docker-init as PID 1.
Closes#34.
Claude-Session: https://claude.ai/code/session_014WNM4vjoVksyyBraTXSZE1