fix(lifecycle): treat cancellation during actor startup as graceful shutdown - #17
Merged
Merged
Conversation
…hutdown A shutdown request (context cancellation) that arrives while an actor is still starting was reported as a startup failure: the startup loop returned the actor's error unconditionally, so Serve exited non-nil even though the run phase already treats context.Canceled as a clean exit. In practice this races e.g. a SIGTERM (or a test's cancel()) against a service whose Start performs I/O — seen as a flaky TestHealthyEndpoints in ciam-next where the challenge janitor's seed-job enqueue was aborted mid-transaction by the test's shutdown. Align the startup loop with the run-phase semantics: if the serve context is cancelled, log the aborted actor at info level and return nil so the deferred cleanup stops already-started actors. Detection uses ctx.Err() rather than the returned error because drivers may mask the cancellation; context.DeadlineExceeded still surfaces as an error, as in the run phase. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
molon
approved these changes
Jul 8, 2026
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.
Slack: https://theplant.slack.com/archives/C09H5U13GJY/p1783419772718129
Problem
A shutdown request (context cancellation) that arrives while an actor is still starting is reported as a startup failure. The startup loop in
Servereturns the actor's error unconditionally:…while the run phase already treats
context.Canceledas a clean exit:So the same shutdown signal is graceful if it lands one millisecond later, but a "failure" if it races the startup sequence.
Real-world impact
TestHealthyEndpointsin ciam-next (failing run): the test cancels the serve context right after health checks pass; when the cancel lands inside the challenge janitor's ~1.6ms seed-job enqueue transaction,database/sqlaborts withcontext canceled,Servereports it asFailed to start service, and the test fails.Fix
Align the startup loop with the run-phase semantics: if the serve context has been cancelled, log the aborted actor at info level and return nil — the deferred cleanup stops already-started actors as usual.
Two deliberate choices:
ctx.Err(), not the returned error. The cancellation may be masked by intermediate layers (database drivers can surface it as e.g.driver: bad connection), while "the serve context is cancelled" is unambiguous: once shutdown is requested, whateverStartreturned is moot.context.DeadlineExceededstill surfaces as an error, matching the run phase (TestServeConvenienceFunctionrelies on this distinction).Testing
TestServeCancelDuringActorStartupreproduces the race deterministically (fails onmain, passes with the fix) and pins that a real startup failure is still reported.go vet+golangci-lint run(v2.11.3, same as CI): clean.replace:TestHealthyEndpointspasses.🤖 Generated with Claude Code