From 93fc2f48a16d43d24f08c09fee3a67ece6917f46 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 22 Oct 2026 21:05:01 +0200 Subject: [PATCH] Name a retry's modules still waiting for the bus, read as the plan it resumes (issue 1851 A11a, issue 1403) With the hand rebuild gone, a retry of a plan failed past the bus's bound read the waiting modules while the plan still said failed, named none, and answered "asked" with nothing after it. --- cmd/mesh-controller/ask_bus_wait_test.go | 23 +++++++++++++++++++++++ cmd/mesh-controller/plan_retry.go | 10 +++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 cmd/mesh-controller/ask_bus_wait_test.go diff --git a/cmd/mesh-controller/ask_bus_wait_test.go b/cmd/mesh-controller/ask_bus_wait_test.go new file mode 100644 index 00000000..3df88f5a --- /dev/null +++ b/cmd/mesh-controller/ask_bus_wait_test.go @@ -0,0 +1,23 @@ +package main + +import ( + "strings" + "testing" + "time" + + "github.com/novox/mesh-controller/internal/inventory" +) + +// A retry of a plan failed past the bus's bound, whose module the bus still does not answer, names that module as waiting: +// read while the plan still says failed, it was named nowhere, and the retry said "asked" with nothing after it (issue +// 1403, met again once the hand rebuild no longer resumed the plan first). +func TestARetryNamesTheModulesStillWaitingForTheBus(t *testing.T) { + since := time.Now().UTC() + p := inventory.Plan{ID: "plan-bus", State: inventory.PlanFailed, Tiers: [][]string{{"a"}}, + Modules: map[string]*inventory.PlanModule{"a": {BusWaitFrom: &since}}} + waiting := busWaitingOnRetry(p) + _, said := retryWords(p, []string{"a"}, nil, waiting) + if !strings.Contains(said, "a not asked yet: the bus did not answer") || strings.Contains(said, "asked ;") { + t.Fatalf("a retry left for the bus is said as %q", said) + } +} diff --git a/cmd/mesh-controller/plan_retry.go b/cmd/mesh-controller/plan_retry.go index 6cca81db..5db2d366 100644 --- a/cmd/mesh-controller/plan_retry.go +++ b/cmd/mesh-controller/plan_retry.go @@ -408,7 +408,7 @@ func retryPlan(ctx context.Context, open *stores, id string) (string, error) { asked = append(asked, m+" as "+s.Build) } } - waiting := busWaitingIn(p) + waiting := busWaitingOnRetry(p) note, said := retryWords(p, failed, asked, waiting) resumed(&p, note) if err := inv.SavePlan(ctx, &p); err != nil { @@ -420,6 +420,14 @@ func retryPlan(ctx context.Context, open *stores, id string) (string, error) { return said, nil } +// busWaitingOnRetry are the modules a retry left waiting for the bus. Read before the plan is resumed, while it still says +// failed, so read as the plan the retry resumes: a failed plan is not open, and busWaitingIn would name none, leaving the +// answer "asked" with nothing after it (issue 1403). +func busWaitingOnRetry(p inventory.Plan) []string { + p.State = inventory.PlanBuilding + return busWaitingIn(p) +} + // retryWords are the plan's note and the verb's answer for a retry: what was asked again (as the builds it got), and what waits // for the bus. A retry whose modules all wait for the bus asked none, and says so rather than "asked" with nothing after it // (issue 1403).