mirror of
https://github.com/docker/compose.git
synced 2026-10-12 02:13:08 +00:00
executor: failed dependency waits keep their per-container event granularity
waitDependencies reports an optional dependency's failure as one Skipped event per container of the dependency, with the reason — and a required condition failure as per-container Error events. The plan walker's generic single Skipped on the wait node's resource ID would have silently changed that granularity, exactly the kind of divergence the event contract exists to prevent. Wait failures now flow through reportWaitFailure, which emits the historical per-container events (Skipped with reason when optional, Error otherwise) before handing the error to the walker; the walker skips its generic event for wait nodes. Validated design decision for #14081 (event parity contract). Signed-off-by: Nicolas De Loof <[email protected]>
This commit is contained in:
@@ -122,9 +122,13 @@ func (exec *planExecutor) run(ctx context.Context, plan *Plan) error {
|
||||
|
||||
if err != nil && node.Operation.Optional && ctx.Err() == nil {
|
||||
// A best-effort operation reports its failure as a skip and
|
||||
// does not fail the plan: dependent nodes still run.
|
||||
// does not fail the plan: dependent nodes still run. Wait
|
||||
// nodes emit their own per-container Skipped events (see
|
||||
// reportWaitFailure), so the generic one is theirs to skip.
|
||||
logrus.Warnf("%s (%s): %v", node.Operation.Cause, node.Operation.ResourceID, err)
|
||||
events.On(skippedEvent(nodeEventName(node), err.Error()))
|
||||
if node.Operation.Type != OpWaitCondition {
|
||||
events.On(skippedEvent(nodeEventName(node), err.Error()))
|
||||
}
|
||||
err = nil
|
||||
}
|
||||
|
||||
|
||||
@@ -228,13 +228,13 @@ func (exec *planExecutor) execWaitCondition(ctx context.Context, op Operation) e
|
||||
case <-ticker.C:
|
||||
case <-ctx.Done():
|
||||
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
|
||||
return fmt.Errorf("timeout waiting for dependencies")
|
||||
return exec.reportWaitFailure(op, waitingFor, fmt.Errorf("timeout waiting for dependencies"))
|
||||
}
|
||||
return ctx.Err()
|
||||
}
|
||||
satisfied, err := exec.checkWaitCondition(ctx, op, waitingFor)
|
||||
if err != nil {
|
||||
return err
|
||||
return exec.reportWaitFailure(op, waitingFor, err)
|
||||
}
|
||||
if satisfied {
|
||||
return nil
|
||||
@@ -242,6 +242,24 @@ func (exec *planExecutor) execWaitCondition(ctx context.Context, op Operation) e
|
||||
}
|
||||
}
|
||||
|
||||
// reportWaitFailure emits the per-container events a failed dependency wait
|
||||
// owes the progress display — Skipped with a reason when the dependency is
|
||||
// optional, Error otherwise — and returns the error for the walker (which
|
||||
// skips the generic event for wait nodes, see run()).
|
||||
func (exec *planExecutor) reportWaitFailure(op Operation, waitingFor Containers, err error) error {
|
||||
s := exec.compose
|
||||
if op.Optional {
|
||||
reason := fmt.Sprintf("optional dependency %q: %v", op.Name, err)
|
||||
s.events.On(containerReasonEvents(waitingFor, skippedEvent, reason)...)
|
||||
logrus.Warnf("%s", reason)
|
||||
} else {
|
||||
s.events.On(containerEvents(waitingFor, func(id string) api.Resource {
|
||||
return errorEventf(id, "dependency %s failed to start", op.Name)
|
||||
})...)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// checkWaitCondition performs one probe of a WaitCondition operation,
|
||||
// reporting whether the dependency satisfies the declared condition.
|
||||
func (exec *planExecutor) checkWaitCondition(ctx context.Context, op Operation, waitingFor Containers) (bool, error) {
|
||||
|
||||
Reference in New Issue
Block a user