fix(watch): interrupt stale rebuilds so watch converges - #124
Merged
glours merged 1 commit intoSep 11, 2026
Merged
Conversation
A rebuild whose build context was snapshotted before a newer change to one of its own services is doomed: its output will be replaced by the trailing rebuild anyway, and meanwhile a sync+restart change landing in that window was silently lost — the sync went to the doomed container, the restart was skipped, and the replacement image predated the edit. The scheduler now gives each run its own cancellable context and upholds one invariant: every request ends up covered by a rebuild whose snapshot postdates it. A request naming an actively-rebuilt service interrupts the run and folds its whole active set back into pending (interruption kills the other services' rebuild too), so the trailing rebuild redoes everything on a fresh snapshot. Requests for other services keep coalescing without interrupting. handleWatchBatch drops a restart only when the rebuild is pending (that one converges on its own) and folds it into a Request when the rebuild is in flight — recreate + start is the restart intent, converged. The run loop also stops draining once the watch context is cancelled, which silences the spurious "Build failed: context canceled" on Ctrl-C. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
glours
merged commit Sep 11, 2026
5089380
into
glours:watch-rebuild-coalescing
88 of 90 checks passed
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 this PR does, in one sentence: rebuilds interrupted the moment they turn stale, so
watchalways converges on a state that reflects the latest edits.Context
Follow-up to the review on docker#14202. With rebuilds now asynchronous, a change arriving while a rebuild of the same service is in flight lands in a blind spot: the running build's context snapshot predates it, the sync goes into the doomed container, and the restart is skipped by
InFlightOrPending— async+restartedit in that window is silently lost until the file is touched again. Burst editing (the very regime the coalescing targets) makes that window common.What this PR brings
One invariant, enforced by the scheduler: every request ends up covered by a rebuild whose build-context snapshot postdates it.
rebuildFuncnow takes a ctx). A request naming an actively-rebuilt service interrupts the run — no point finishing a build whose output will be replaced — and folds its whole active set back into pending, so the trailing rebuild redoes everything on a fresh snapshot. Requests for other services keep coalescing without interrupting, and interruption is bounded by the debounce quiet period.InFlightOrPendingsplits intoPending(restart dropped: the coming rebuild converges on its own) andInFlight(restart folded into aRequest: recreate + start is the restart intent, converged).Guardrails: the no-concurrent-rebuild property and lock discipline are unchanged; latency of an isolated edit is unchanged; new deterministic tests cover interruption (with active-set fold-back), non-interruption for other services, shutdown, and the restart-during-in-flight convergence at the
handleWatchBatchlevel (also fixing the stale#14051reference). The two coalescing tests are adjusted: re-requesting the active service now interrupts — previously their "duplicate, must not double up".go test -race ./pkg/compose/is green.🤖 Generated with Claude Code