fix(server): handle SIGTERM so docker stop is graceful - #122
Merged
Conversation
As PID 1 the kernel ignores SIGTERM unless a handler is installed, so tokio::signal::ctrl_c() alone made docker/podman/k8s stop wait out the grace period and SIGKILL (issue #114). Listen for SIGTERM as well as SIGINT. HTTP takes the existing graceful-shutdown path and cancels the streamable-HTTP token so a live session cannot pin the listener. Stdio cannot drain that way: rmcp reads stdin via spawn_blocking, and that read does not unblock while a client holds the pipe. Both the handshake wait and waiting() therefore process::exit(0) after the signal. Completed audit records are already on disk. Verified: cargo fmt --check, clippy -D warnings, cargo test --workspace --all-targets --locked, cargo deny check. As PID 1 via unshare, SIGTERM now exits 0 in ~10ms (was 13s / 137).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Install a
SIGTERMhandler next toSIGINTso a container running as PID 1 exits ondocker stop/podman stop/ Kubernetes instead of waiting out the runtime grace period and dying to SIGKILL (issue #114).shutdown_signal()feeds axum's graceful shutdown and then cancels the streamable-HTTP cancellation token, so a live MCP session cannot pin the listener.serve(handshake; where an unused stdio container sits) andwaiting. After the signal the processexit(0)s. rmcp reads stdin viaspawn_blocking; that read cannot be cancelled while a client holds the pipe, and returning frommaindrops the runtime onto that blocking thread.status.code() == Some(0)(an unhandled SIGTERM still kills a non-PID-1 child, but withcode() == None). Stdio tests keep the child's stdin open sowait_with_outputcannot EOF-unblock a missingprocess::exit.--initstays as optional defense in depth.Why
PID 1 does not get SIGTERM's default terminate action. Only
ctrl_c()(SIGINT) was installed, sodocker stopmeasured 13s + exit 137. Kubernetes has no--initequivalent.Invariants
No guard change. I1–I16 untouched.
process::exit(0)does not skip persist-before-response (I15):call_tool/initializeawait the sink write before any client-visible result. An in-flight call that is killed produces no response. I12: new log lines carry no key material.Verification
cargo fmt --checkcargo clippy --workspace --all-targets -- -D warningscargo clippy -p bugwarden --features gen --all-targets -- -D warningscargo test --workspace --all-targets --lockedcargo deny checkreturn Ok(())was a real miss: the first stdio test usedwait_with_output, which drops stdin and unblocks the hang. Both arms nowprocess::exit(0)and the handshake test holds stdin.unshare --user --pid --fork, SIGTERM now exits 0 in ~11ms (was 13s / 137).Closes #114