Skip to content

fix(watch): interrupt stale rebuilds so watch converges - #124

Merged
glours merged 1 commit into
glours:watch-rebuild-coalescingfrom
ndeloof:watch-interruptible-rebuild
Sep 11, 2026
Merged

fix(watch): interrupt stale rebuilds so watch converges#124
glours merged 1 commit into
glours:watch-rebuild-coalescingfrom
ndeloof:watch-interruptible-rebuild

Conversation

@ndeloof

@ndeloof ndeloof commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator

What this PR does, in one sentence: rebuilds interrupted the moment they turn stale, so watch always 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 — a sync+restart edit 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.

  • Each run gets its own cancellable context (rebuildFunc now 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.
  • InFlightOrPending splits into Pending (restart dropped: the coming rebuild converges on its own) and InFlight (restart folded into a Request: recreate + start is the restart intent, converged).
  • The run loop stops draining once the watch context is cancelled — which also silences the spurious "Build failed: context canceled" after Ctrl-C.

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 handleWatchBatch level (also fixing the stale #14051 reference). 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

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
glours merged commit 5089380 into glours:watch-rebuild-coalescing Sep 11, 2026
88 of 90 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants