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 <tonistiigi@gmail.com>
This commit is contained in:
Tonis Tiigi
2026-03-04 18:09:41 -08:00
parent d315c086c3
commit 2adc53cfb3
+19 -1
View File
@@ -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) {