diff --git a/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/waits.go b/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/waits.go index 29005677..e3781b5d 100644 --- a/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/waits.go +++ b/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/waits.go @@ -18,8 +18,10 @@ import ( // waitBody is the body of wait-held and wait-ended on the builder's stream waits (design 47 G §672): the fields of §629's // wait with `step` building, `on` toolchain:@, `module` and `fingerprint`, and, on wait-ended, `how`. type waitBody struct { - Step string `json:"step"` - On string `json:"on"` + Step string `json:"step"` + On string `json:"on"` + // Part is the package a key queued behind it waits for (design 47 G A-192); none for a toolchain wait. + Part string `json:"part,omitempty"` Module string `json:"module"` Fingerprint string `json:"fingerprint"` Since time.Time `json:"since"` @@ -46,7 +48,7 @@ func newWaitsKept(kv *process.AwaitedBucket, say func(context.Context, string, s } func (w *waitsKept) body(t build.ToolchainWait, how string) waitBody { - return waitBody{Step: waitStepBuilding, On: t.On(), Module: t.Module, Fingerprint: t.Fingerprint, Since: t.Since, Bound: w.bound.String(), How: how} + return waitBody{Step: waitStepBuilding, On: t.On(), Part: t.Package, Module: t.Module, Fingerprint: t.Fingerprint, Since: t.Since, Bound: w.bound.String(), How: how} } // start reads the state whole and says wait-held again for each key. @@ -110,7 +112,13 @@ func (w *waitsKept) End(ctx context.Context, module, fp, how string) bool { } w.mu.Lock() t, had := w.held[key] - delete(w.held, key) + // A key queued behind a package ends only when it is published, and a toolchain wait never by that (design 47 G A-192). + if had && (t.Package != "") != (how == build.WaitPublished) { + had = false + } + if had { + delete(w.held, key) + } w.mu.Unlock() if !had { return false diff --git a/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/waits_test.go b/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/waits_test.go new file mode 100644 index 00000000..b21a290f --- /dev/null +++ b/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/waits_test.go @@ -0,0 +1,21 @@ +package main + +import ( + "testing" + "time" + + "mesh-delivery-builder/internal/build" +) + +// Design 47 G A-192: a key queued behind a package says its wait in the one wait form, the package as its `part` and `on`; a toolchain +// wait names none. +func TestAWaitBodyNamesItsPart(t *testing.T) { + w := &waitsKept{bound: time.Hour, held: map[string]build.ToolchainWait{}} + pkg := w.body(build.ToolchainWait{Module: "m", Fingerprint: "src2:f", Package: "@novox/sdk@0.1.15"}, "") + if pkg.Part != "@novox/sdk@0.1.15" || pkg.On != "package:@novox/sdk@0.1.15" || pkg.Step != waitStepBuilding { + t.Errorf("a package wait says %+v", pkg) + } + if tc := w.body(build.ToolchainWait{Module: "m", Fingerprint: "src2:f", Language: "go", Version: "go1.26.0"}, ""); tc.Part != "" || tc.On != "toolchain:go@go1.26.0" { + t.Errorf("a toolchain wait says %+v", tc) + } +} diff --git a/modules/mesh-delivery-builder/internal/build/r4e_test.go b/modules/mesh-delivery-builder/internal/build/r4e_test.go index c1957e8f..a79c0ed0 100644 --- a/modules/mesh-delivery-builder/internal/build/r4e_test.go +++ b/modules/mesh-delivery-builder/internal/build/r4e_test.go @@ -173,6 +173,8 @@ func sdkModule(t *testing.T, r *rig, ref string) []byte { func TestAModuleWhoseLockNamesAnUnpublishedPackageWaitsQueuedAndIsBuiltOncePublished(t *testing.T) { // B7, ADR 0500 clause 6 const ref = "@novox/mesh-sdk@0.1.15" r := newRig(t, tsBundle) + w := &memWaits{} + r.b.Waits = w for _, n := range []string{"ace", "novox"} { r.bm.fails[n+"/build_node_fetch"] = waitsFor + ref + ": the lock names a version the registry does not hold" } @@ -180,6 +182,15 @@ func TestAModuleWhoseLockNamesAnUnpublishedPackageWaitsQueuedAndIsBuiltOncePubli if v.State != builds.BuildQueued || v.Waits != ref || v.Error != "" { t.Fatalf("build = %+v, want queued, waiting for %s, not failed", v, ref) } + // Design 47 G A-192: the queued key is a wait said in the one wait form, with its package as the part, and it stands while the package is unpublished. + if len(w.held) != 1 { + t.Fatalf("waits held %v, want the one of the package", w.held) + } + for _, h := range w.held { + if h.Package != ref || h.On() != "package:"+ref { + t.Errorf("the wait is %+v on %s, want the package %s", h, h.On(), ref) + } + } delete(r.bm.fails, "ace/build_node_fetch") delete(r.bm.fails, "novox/build_node_fetch") writes := r.st.writes @@ -188,11 +199,17 @@ func TestAModuleWhoseLockNamesAnUnpublishedPackageWaitsQueuedAndIsBuiltOncePubli if r.st.writes != writes { t.Fatalf("a key waiting for an unpublished package was claimed (%d writes, was %d)", r.st.writes, writes) } + if len(w.held) != 1 { + t.Fatalf("the wait ended while the package is unpublished: %v", w.held) + } r.b.Module(context.Background(), "sdk", sdkModule(t, r, ref)) // the package's trunk key is built and published r.b.Wait() if v = valueOf(t, r.st, r.key); v.State != builds.BuildDone || v.Waits != "" { t.Errorf("build = %+v, want built once the package it waits for is published", v) } + if len(w.held) != 0 || len(w.ended) != 1 || w.ended[0] != WaitPublished { + t.Errorf("held %v, ended %v: want the wait ended published", w.held, w.ended) + } } func TestAKeyWaitingForAPackageAlreadyPublishedIsBuiltWhenTheBuilderStarts(t *testing.T) { // B7: the scan at start diff --git a/modules/mesh-delivery-builder/internal/build/run.go b/modules/mesh-delivery-builder/internal/build/run.go index a6a59251..86ebbfe8 100644 --- a/modules/mesh-delivery-builder/internal/build/run.go +++ b/modules/mesh-delivery-builder/internal/build/run.go @@ -617,6 +617,9 @@ func (b *Builder) try(ctx context.Context, module string, m meshcatalog.ModulesB return } b.handClaimed(key, cl.Rev) + if b.Waits != nil { + b.Waits.End(ctx, module, m.Fp2, WaitPublished) // a key queued behind a package is claimed once it is published + } b.logBuild(LogStarted(module, m, "", b.Node, b.now())) if cl.Unasked != nil { b.logf("%v", cl.Unasked) @@ -717,6 +720,10 @@ func (b *Builder) try(ctx context.Context, module string, m meshcatalog.ModulesB } if result.State == builds.BuildQueued { b.logf("%s: waits for the package %s to be published", cl.Key, result.Waits) + if b.Waits != nil { + // Said as a wait in the one wait form, with its part, for the watchman to time (design 47 G A-192; ADR 0603 §8). + b.Waits.Hold(ctx, ToolchainWait{Module: module, Fingerprint: m.Fp2, Package: result.Waits, Since: b.now()}) + } } if b.notePublished(result) { // A key may wait for the package this build published (ADR 0500 clause 6). diff --git a/modules/mesh-delivery-builder/internal/build/waits.go b/modules/mesh-delivery-builder/internal/build/waits.go index b0f6d225..d314976c 100644 --- a/modules/mesh-delivery-builder/internal/build/waits.go +++ b/modules/mesh-delivery-builder/internal/build/waits.go @@ -26,8 +26,10 @@ const ( // ModeRelease is the Mode of a reasks value of builder_release. ModeRelease = "release" // WaitRan ends a wait when a toolchain-agreed names the version; WaitReleased when builder_release ended it. - WaitRan = "ran" - WaitReleased = "released" + WaitRan = "ran" + // WaitPublished ends the wait of a key queued behind a package, when the key is claimed (the package is published). + WaitPublished = "published" + WaitReleased = "released" ) // ToolchainWait is one wait: the entry's module and fingerprint, and the toolchain it waits for. @@ -37,10 +39,18 @@ type ToolchainWait struct { Language string `json:"language"` Version string `json:"version"` Since time.Time `json:"since"` + // Package is the package version a key queued behind it waits for, `@` (ADR 0603 §8; design 47 G A-192): the wait is the + // package's, not a toolchain's, and Language and Version are empty. + Package string `json:"package,omitempty"` } // On is what the wait waits for, as the wait's `on` says it. -func (w ToolchainWait) On() string { return "toolchain:" + w.Language + "@" + w.Version } +func (w ToolchainWait) On() string { + if w.Package != "" { + return "package:" + w.Package + } + return "toolchain:" + w.Language + "@" + w.Version +} // Waits keeps the builder's waits: Hold writes one in the state waits and says wait-held, once per wait; End deletes it and // says wait-ended with how, and answers whether one was held. diff --git a/modules/mesh-delivery-builder/internal/build/waits_test.go b/modules/mesh-delivery-builder/internal/build/waits_test.go index 2a63722f..72eb4608 100644 --- a/modules/mesh-delivery-builder/internal/build/waits_test.go +++ b/modules/mesh-delivery-builder/internal/build/waits_test.go @@ -28,7 +28,8 @@ func (w *memWaits) End(_ context.Context, module, fp, how string) bool { w.mu.Lock() defer w.mu.Unlock() k := module + "." + fp - if _, ok := w.held[k]; !ok { + t, ok := w.held[k] + if !ok || (t.Package != "") != (how == WaitPublished) { // as the builder's waitsKept ends them: a package wait by its publishing alone return false } delete(w.held, k)