fix: cancel_invocation auto-escalates when graceful cancel isn't enough#976
Merged
gregmagolan merged 1 commit intomainfrom Mar 27, 2026
Merged
fix: cancel_invocation auto-escalates when graceful cancel isn't enough#976gregmagolan merged 1 commit intomainfrom
gregmagolan merged 1 commit intomainfrom
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 57d9545d3a
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
f27700e to
b4bc4f4
Compare
467feb7 to
b81b940
Compare
A single SIGINT only requests graceful cancellation from Bazel, which
often isn't enough to stop a running build. This caused wait() to hang
indefinitely.
cancel_invocation() now accepts force_kill_after_ms (default 5000ms).
When wait() is called and the build hasn't stopped within the grace
period, it automatically escalates: sends a second SIGINT to the Bazel
client (triggering Bazel's forceful cancel protocol), or if the client
has crashed, sends SIGKILL to the server daemon via the PID read from
<output_base>/server/server.pid.txt.
API:
# Happy path — auto-escalates after 5s (default)
cancellation = ctx.bazel.cancel_invocation()
cancellation.wait()
# Manual control
cancellation = ctx.bazel.cancel_invocation(force_kill_after_ms = 0)
if not cancellation.wait(timeout_ms = 5000):
cancellation.force()
Additional fixes:
- wait(timeout_ms) returns False on timeout instead of hanging forever
- Mixing timeout_ms and force_kill_after_ms is a hard error
- wait() with no timeout and no force_kill_after_ms is a hard error
- Capture stderr from bazel info for diagnostic logging
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.
Summary
cancel_invocation()now automatically escalates when a graceful cancel isn't enough to stop a Bazel build.How it works
cancel_invocation()sends the 1st SIGINT to the Bazel client (graceful cancel) and returns aCancellationobject. Whenwait()is called, if the build hasn't stopped withinforce_kill_after_ms(default 5000ms), it escalates following Bazel's 3-SIGINT protocol:cancel_invocation()force()/ auto-escalationforce()/ auto-escalationKillServerProcess— server SIGKILL'd, client exitsIf the client has already crashed, falls back to SIGKILL on the server daemon directly (PID read from
<output_base>/server/server.pid.txt). If the server isn't busy,force()is a no-op.AXL examples
Default — auto-escalates after 5s:
Custom grace period:
Manual control:
Safety rails
wait(timeout_ms=N)withforce_kill_after_ms > 0→ error (ambiguous — pick one mode)wait()withforce_kill_after_ms = 0and notimeout_ms→ error (would hang forever)Changes
cancel.rs:wait()supportstimeout_msand auto-escalation.force()sends 2nd + 3rd SIGINT (Bazel's 3-stage protocol), monitors client PID, falls back to SIGKILL. Guards against killing an idle server.mod.rs:cancel_invocationacceptsforce_kill_after_msparam.info.rs: Addedserver_pid_nonblocking()(reads PID from disk viaoutput_base). Capture stderr frombazel infofor diagnostics.axl.axl: New test cases for force-cancel, auto-escalation on a slow build, repeated cancel calls, and stale cancellation objects.examples/slow_build/: Test fixture with a 30s sleep genrule for cancel tests.Test plan
validate_wait_params— all 4 combinations oftimeout_ms/force_kill_after_mscancel_invocation()with bad startup flagsforce()on a running buildsleep 30build withforce_kill_after_ms=1000wait(timeout_ms)on non-busy server returns Truecancel_invocation()calls in a row (discarded results)busy/wait()/force()on a stale Cancellation from a finished build🤖 Generated with Claude Code