feat(daemon): Windows console ctrl handler for graceful shutdown (#18)#61
feat(daemon): Windows console ctrl handler for graceful shutdown (#18)#61
Conversation
…down (#18) On Windows, tokio::signal::ctrl_c() covers CTRL_C_EVENT and CTRL_BREAK_EVENT but NOT CTRL_CLOSE_EVENT (terminal window closed), CTRL_LOGOFF_EVENT, or CTRL_SHUTDOWN_EVENT. Without an explicit handler, those events terminate the daemon immediately, skipping graceful shutdown and leaving stale PID / port files behind. Register a SetConsoleCtrlHandler routine that funnels those three events into the same shutdown_tx channel the HTTP shutdown endpoint and Ctrl+C paths use. The handler blocks for ~3.5s to give the main graceful-shutdown path room to run before Windows force-kills at the ~5s CTRL_CLOSE deadline; the sleep is cut short if `main` exits first. Addresses the "SetConsoleCtrlHandler" half of the B5a hardening leftovers tracked in #18. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 29 minutes and 52 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…leaks (partial #32) Set SO_LINGER=0 on the daemon's listening socket before `listen(2)` so that accepted client sockets inherit a zero linger and force an immediate RST on close instead of going through FIN / CLOSE_WAIT / TIME_WAIT. After a hard-kill of the daemon, this prevents the kernel from leaking dangling CLOSE_WAIT state on client sockets that outlives the daemon itself and would otherwise block a fresh instance from re-binding the port. SO_LINGER is inherited from the listener by accept(2) on Linux, macOS, and Windows (AFD.sys), so setting it once on the listener covers every accepted connection without needing to hook axum 0.7's internal accept loop (axum 0.7 hands accept off to hyper-util inside `serve` with no per-connection extension point). Verified with the pre-existing regression test at crates/fbuild-daemon/tests/port_recovery.rs (run with `--ignored`), which hard-kills a daemon holding an open TCP connection and asserts a second daemon can re-bind the port cleanly. Remaining on #32: process containment via the `running-process` crate (Windows Job Object + POSIX process group parent-death). The SetConsoleCtrlHandler piece landed earlier in PR #61. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…leaks (partial #32) Set SO_LINGER=0 on the daemon's listening socket before `listen(2)` so that accepted client sockets inherit a zero linger and force an immediate RST on close instead of going through FIN / CLOSE_WAIT / TIME_WAIT. After a hard-kill of the daemon, this prevents the kernel from leaking dangling CLOSE_WAIT state on client sockets that outlives the daemon itself and would otherwise block a fresh instance from re-binding the port. SO_LINGER is inherited from the listener by accept(2) on Linux, macOS, and Windows (AFD.sys), so setting it once on the listener covers every accepted connection without needing to hook axum 0.7's internal accept loop (axum 0.7 hands accept off to hyper-util inside `serve` with no per-connection extension point). Verified with the pre-existing regression test at crates/fbuild-daemon/tests/port_recovery.rs (run with `--ignored`), which hard-kills a daemon holding an open TCP connection and asserts a second daemon can re-bind the port cleanly. Remaining on #32: process containment via the `running-process` crate (Windows Job Object + POSIX process group parent-death). The SetConsoleCtrlHandler piece landed earlier in PR #61. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Registers a Windows
SetConsoleCtrlHandlerso the daemon catches CTRL_CLOSE_EVENT (terminal window closed), CTRL_LOGOFF_EVENT, and CTRL_SHUTDOWN_EVENT and funnels them into the sameshutdown_txchannel the HTTP shutdown endpoint and Ctrl+C paths already use.Without this hook, those three events terminate the daemon immediately — skipping graceful shutdown and leaving stale PID / port files behind.
tokio::signal::ctrl_c()only covers CTRL_C_EVENT / CTRL_BREAK_EVENT on Windows, so it doesn't help here.Context
Addresses the
SetConsoleCtrlHandlerhalf of the B5a hardening leftovers in #18. The companionSO_LINGER 0piece stays deferred because it needs per-accepted-socket access that axum doesn't currently expose.Implementation notes
#[cfg(windows)], call site is#[cfg(windows)]— no behavior change on Unix builds.OnceLock<watch::Sender<bool>>holds the shutdown sender becauseSetConsoleCtrlHandler's callback has a fixed C-ABI signature with no user-data pointer.CTRL_CLOSE_EVENTdeadline forces termination. Ifmainexits sooner, the sleep is cut short.Test plan
uv run cargo clippy --workspace --all-targets -- -D warningscleanuv run cargo test -p fbuild-daemon --lib— 88 passed🤖 Generated with Claude Code