From e1dca0a39fbde8bdcc26d24e82f7cf5a3d03a559 Mon Sep 17 00:00:00 2001 From: jochen Date: Fri, 23 Oct 2026 15:30:32 +0200 Subject: [PATCH] Release an ask's workspace when its job ends, so a pulled build or check leaves nothing for the sweep (issue 1887 A3go, ADR 0509) --- go/buildstep/askrun.go | 17 ++++++++++++++++- go/buildstep/askrun_test.go | 8 +++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/go/buildstep/askrun.go b/go/buildstep/askrun.go index a66c023..9956271 100644 --- a/go/buildstep/askrun.go +++ b/go/buildstep/askrun.go @@ -29,7 +29,10 @@ type AskSteps struct { // Kept looks up a kept verdict by level and digest (the checker's kept bucket, delivery.KeptKey): whether one is // kept and how many tests it counted. Nil takes none. Kept func(ctx context.Context, level, digest string) (tests int, found bool, err error) - Logf func(format string, args ...any) + // Release removes a workspace once the ask's steps on it ended (the module's release tool's body); nil leaves it to + // the module's sweep. + Release func(workspace string) error + Logf func(format string, args ...any) } // RunAsk runs ask's job with steps and answers a delivery.BuildAnswer or a delivery.CheckAnswer. @@ -92,6 +95,7 @@ func (s AskSteps) build(ctx context.Context, job delivery.BuildJob) (any, error) answer.Toolchain = &tc } var f fetched + defer func() { s.release(f.Workspace) }() for _, a := range job.Artifacts { if a.From == "" && f.Workspace == "" { r, got, err := s.fetch(ctx, job.Fetch) @@ -153,6 +157,7 @@ func (s AskSteps) check(ctx context.Context, job delivery.CheckJob) (any, error) if err != nil { return nil, err } + defer s.release(f.Workspace) answer := delivery.CheckAnswer{Fetch: r} if len(f.Conflicts) > 0 { return answer, nil // the asker reads the conflict from the fetch's answer: a verdict, not an error @@ -240,6 +245,16 @@ func (s AskSteps) check(ctx context.Context, job delivery.CheckJob) (any, error) return answer, nil } +// release removes an ask's workspace, saying a failure: the module's sweep removes it later. +func (s AskSteps) release(workspace string) { + if workspace == "" || s.Release == nil { + return + } + if err := s.Release(workspace); err != nil && s.Logf != nil { + s.Logf("the workspace %s is left to the sweep: %v", workspace, err) + } +} + // plan digests the level and takes the kept verdicts: the packages left to test, or all when the level runs whole (a // try of some tests, or a digest that named nothing). func (s AskSteps) plan(ctx context.Context, job delivery.CheckJob, f fetched, withRunner func(map[string]any) map[string]any, answer *delivery.CheckAnswer) ([]string, bool, error) { diff --git a/go/buildstep/askrun_test.go b/go/buildstep/askrun_test.go index 044d59d..34c5e26 100644 --- a/go/buildstep/askrun_test.go +++ b/go/buildstep/askrun_test.go @@ -68,7 +68,10 @@ func ask(job any) delivery.StepAsk { // fetch, and answers each artifact's digest and reference. func TestRunAskBuilds(t *testing.T) { r := &recorder{fetch: map[string]any{"workspace": "/w/1"}} - out, err := RunAsk(context.Background(), r.steps(), ask(delivery.BuildJob{Module: "m", Path: "modules/m", Publish: true, + steps := r.steps() + var released []string + steps.Release = func(ws string) error { released = append(released, ws); return nil } + out, err := RunAsk(context.Background(), steps, ask(delivery.BuildJob{Module: "m", Path: "modules/m", Publish: true, Fetch: map[string]any{"repository": "novox/x", "commit": runCommit}, Artifacts: []delivery.AskArtifact{{Artifact: "base", From: "registry/b@sha256:00"}, {Artifact: "bundle"}, {Artifact: "lib"}}})) if err != nil { @@ -82,6 +85,9 @@ build {"artifact":"lib","module":"modules/m","publish":true,"workspace":"/w/1"}` if got != want { t.Fatalf("calls:\n%s\nwant:\n%s", got, want) } + if len(released) != 1 || released[0] != "/w/1" { + t.Errorf("released %v, want the one workspace", released) + } a := out.(delivery.BuildAnswer) if len(a.Artifacts) != 3 || a.Artifacts[1].Reference != "artifact-store://bundle" { t.Fatalf("answer %+v", a)