fix: execRunPreStart bails out on a canceled context

Same interim narrowing as execStartContainer and execRunPostStart: the
node hangs off the replica's create node, so when that create fails the
pre_start hooks could still fire in the window before errgroup cancels
the context. The structural fix stays tracked in #14081.

Signed-off-by: Nicolas De Loof <[email protected]>
This commit is contained in:
Nicolas De Loof
2026-10-06 14:11:22 +02:00
committed by Nicolas De loof
parent 51532f6d02
commit f07cdafc3e
2 changed files with 29 additions and 0 deletions
+9
View File
@@ -265,6 +265,15 @@ func (exec *planExecutor) execWaitCondition(ctx context.Context, op Operation) e
// accepts — skips a second, redundant run of the hooks rather than erroring
// on runners already consumed.
func (exec *planExecutor) execRunPreStart(ctx context.Context, op Operation) error {
// Same interim narrowing as execStartContainer's guard (see its
// comment), not a full fix: this node hangs off the replica's create
// node, whose done-channel closes before errgroup's ctx is canceled when
// that create fails for a genuine reason, so a narrow window remains
// where pre_start hooks could still run against the runner containers.
if err := ctx.Err(); err != nil {
return err
}
running, err := exec.compose.getContainers(ctx, exec.project.Name, oneOffExclude, false, op.Service.Name)
if err != nil {
return err
+20
View File
@@ -211,6 +211,26 @@ func TestExecRunPreStart_SkipsWhenReplicaAlreadyRunning(t *testing.T) {
assert.NilError(t, err)
}
// TestExecRunPreStart_BailsOutWhenContextCanceled pins the same interim
// narrowing execStartContainer and execRunPostStart carry: once ctx is
// canceled (a sibling node failed), the node must neither touch the daemon
// nor run hooks. No ContainerList expectation is registered: an unexpected
// call fails the test.
func TestExecRunPreStart_BailsOutWhenContextCanceled(t *testing.T) {
svc, _, _ := newStartPhaseTestService(t)
service := types.ServiceConfig{
Name: "web",
PreStart: []types.PreStartHook{{ContainerSpec: types.ContainerSpec{Command: types.ShellCommand{"init"}}}},
}
ctx, cancel := context.WithCancel(t.Context())
cancel()
exec := svc.newPlanExecutor(&types.Project{Name: "test"}, emptyObservedState("test"), nil)
err := exec.execRunPreStart(ctx, Operation{Service: &service})
assert.ErrorIs(t, err, context.Canceled)
}
// TestExecRunPostStart_RunsHookAndStreamsToListener verifies that
// execRunPostStart resolves the replica the start chain just brought up and
// runs its post_start hooks, forwarding their output to the listener the