diff --git a/client/build_test.go b/client/build_test.go index 80c6996e195e..b2f18aca8f09 100644 --- a/client/build_test.go +++ b/client/build_test.go @@ -60,6 +60,7 @@ func TestClientGatewayIntegration(t *testing.T) { testWarnings, testClientGatewayFrontendAttrs, testClientGatewayNilResult, + testClientGatewayEmptyImageExec, ), integration.WithMirroredImages(integration.OfficialImages("busybox:latest"))) integration.Run(t, integration.TestFuncs( @@ -2086,6 +2087,53 @@ func testClientGatewayNilResult(t *testing.T, sb integration.Sandbox) { require.NoError(t, err) } +func testClientGatewayEmptyImageExec(t *testing.T, sb integration.Sandbox) { + c, err := New(sb.Context(), sb.Address()) + require.NoError(t, err) + defer c.Close() + + registry, err := sb.NewRegistry() + if errors.Is(err, integration.ErrRequirements) { + t.Skip(err.Error()) + } + require.NoError(t, err) + target := registry + "/buildkit/testemptyimage:latest" + + // push an empty image + _, err = c.Build(sb.Context(), SolveOpt{ + Exports: []ExportEntry{ + { + Type: ExporterImage, + Attrs: map[string]string{ + "name": target, + "push": "true", + }, + }, + }, + }, "", func(ctx context.Context, c client.Client) (*client.Result, error) { + return client.NewResult(), nil + }, nil) + require.NoError(t, err) + + _, err = c.Build(sb.Context(), SolveOpt{}, "", func(ctx context.Context, gw client.Client) (*client.Result, error) { + // create an exec on that empty image (expected to fail, but not to panic) + st := llb.Image(target).Run( + llb.Args([]string{"echo", "hello"}), + ).Root() + def, err := st.Marshal(sb.Context()) + if err != nil { + return nil, err + } + _, err = gw.Solve(ctx, client.SolveRequest{ + Definition: def.ToPB(), + Evaluate: true, + }) + require.ErrorContains(t, err, `process "echo hello" did not complete successfully`) + return nil, nil + }, nil) + require.NoError(t, err) +} + type nopCloser struct { io.Writer } diff --git a/frontend/gateway/gateway.go b/frontend/gateway/gateway.go index 3bf938ad8c5a..5c14fa2940ff 100644 --- a/frontend/gateway/gateway.go +++ b/frontend/gateway/gateway.go @@ -353,7 +353,7 @@ func (lbf *llbBridgeForwarder) Discard() { } for id, workerRef := range lbf.workerRefByID { - workerRef.ImmutableRef.Release(context.TODO()) + workerRef.Release(context.TODO()) delete(lbf.workerRefByID, id) } if lbf.err != nil && lbf.result != nil { diff --git a/worker/result.go b/worker/result.go index 5691c630f6ff..26054cf8c206 100644 --- a/worker/result.go +++ b/worker/result.go @@ -26,6 +26,13 @@ func (wr *WorkerRef) ID() string { return wr.Worker.ID() + "::" + refID } +func (wr *WorkerRef) Release(ctx context.Context) error { + if wr.ImmutableRef == nil { + return nil + } + return wr.ImmutableRef.Release(ctx) +} + // GetRemotes method abstracts ImmutableRef's GetRemotes to allow a Worker to override. // This is needed for moby integration. // Use this method instead of calling ImmutableRef.GetRemotes() directly.