WM13 (builder half): a key queued behind a package says wait-held and wait-ended in the one wait form
The builder says wait-held for a key queued behind a package, the package as its part and on (package:<ref>), on its stream waits, and wait-ended published once the key is claimed; a toolchain wait never ends by that, and a package wait never by a toolchain's end. Files: internal/build/waits.go (Package, On, WaitPublished), internal/build/run.go (Hold at the queued result, End at the claim), cmd/mesh-delivery-builder/waits.go (Part in the body, the End rule). Builds: design 47 G A-192, §499 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
@@ -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:<language>@<version>, `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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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, `<name>@<version>` (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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user