Wait for graceful Anvil exit before escalating to KILL

stopAnvil gave Anvil a single 200ms window after TERM before sending
KILL, risking a truncated state dump during regeneration. It also
checked liveness with kill -0, which succeeds for an exited-but-unreaped
zombie child, so the KILL escalation fired even after clean exits.
Poll osproc running() (which reaps the child) for up to 10s instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
stubbsta 2026-07-12 18:07:55 +02:00
parent be9cf1b2ad
commit 6ff7b14a24
No known key found for this signature in database

View File

@ -633,10 +633,17 @@ proc stopAnvil*(runAnvil: Process) {.used.} =
try:
when not defined(windows):
discard execCmdEx(fmt"kill -TERM {anvilPID}")
# Give Anvil time to dump state on graceful shutdown before escalating to KILL.
sleep(200)
let checkResult = execCmdEx(fmt"kill -0 {anvilPID} 2>/dev/null")
if checkResult.exitCode == 0:
# Give Anvil time to dump state on graceful shutdown before escalating to
# KILL; killing too early truncates the dump and corrupts the state file.
# Poll via osproc `running` (which reaps the child) rather than `kill -0`,
# since the latter also succeeds for an exited-but-unreaped zombie.
const gracefulExitTimeoutMs = 10_000
const pollIntervalMs = 100
var elapsed = 0
while runAnvil.running and elapsed < gracefulExitTimeoutMs:
sleep(pollIntervalMs)
elapsed += pollIntervalMs
if runAnvil.running:
warn "Anvil process still running after TERM signal, sending KILL",
anvilPID = anvilPID
discard execCmdEx(fmt"kill -9 {anvilPID}")