From 2adc53cfb3e2718eec66c9b82d15387e390a3b17 Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Wed, 4 Mar 2026 18:09:41 -0800 Subject: [PATCH] build: parallelize forced reference evaluation Use an errgroup helper to evaluate result refs concurrently during forced evaluation, and fail fast on the first evaluation error. This mitigates current case in bake where chained targets with a multi-platform build could miss secrets and other session properties. The outline for the issue case: - Base target is solved but only lazily and not really loaded/tracked in build graph yet. - Child targets are loaded, base target waits. - Evaluate is called for base stage (because it might be missing result condition), child stage is processed as build result. This happens in parallel. - Because `Evaluate()` was called synchronously, it may have not been called yet for the second platform while the child target already needs to run `RUN --mount=type=secret`. Signed-off-by: Tonis Tiigi --- build/build.go | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/build/build.go b/build/build.go index a09f4d982..efb54d11e 100644 --- a/build/build.go +++ b/build/build.go @@ -630,7 +630,7 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opts map[ return nil, err } } else if forceEval { - if err := res.EachRef(func(ref gateway.Reference) error { + if err := eachRefParallel(ctx, res, func(ctx context.Context, ref gateway.Reference) error { return ref.Evaluate(ctx) }); err != nil { return nil, err @@ -1321,6 +1321,24 @@ func solve(ctx context.Context, c gateway.Client, req gateway.SolveRequest) (*ga return res, nil } +func eachRefParallel(ctx context.Context, res *gateway.Result, fn func(context.Context, gateway.Reference) error) error { + var refs []gateway.Reference + if err := res.EachRef(func(ref gateway.Reference) error { + refs = append(refs, ref) + return nil + }); err != nil { + return err + } + + eg, ctx := errgroup.WithContext(ctx) + for _, ref := range refs { + eg.Go(func() error { + return fn(ctx, ref) + }) + } + return eg.Wait() +} + func catchFrontendError(retErr, frontendErr *error) { *frontendErr = *retErr if errors.Is(*retErr, ErrRestart) {