diff --git a/cmd/mesh-controller/batches.go b/cmd/mesh-controller/batches.go index a0db5821..7b4704a7 100644 --- a/cmd/mesh-controller/batches.go +++ b/cmd/mesh-controller/batches.go @@ -768,6 +768,7 @@ func planBatch(ctx context.Context, open *stores, batch *inventory.Plan, carry [ } sort.Strings(also) ordered := groupOrderEdges(members, entries, read, edges) + var withheld []string plan := planOfMoves(moved, append(append([]inventory.Edge{}, edges...), ordered...)) // **Withheld, a stopped merge's modules** (novox/hq ADR 0299): a module is built at its branch, which holds the // merge a stop left out of the walk these merges were walked again from — built here only as a dependent, it @@ -775,6 +776,7 @@ func planBatch(ctx context.Context, open *stores, batch *inventory.Plan, carry [ // and the stop named it. if batch.Delivery != nil && batch.Delivery.Batch != nil { if out := withhold(&plan, batch.Delivery.Batch.Withheld, moved); len(out) > 0 { + withheld = out fmt.Printf(" withheld, a stopped merge's modules built only as dependents: %s\n", strings.Join(out, ", ")) } } @@ -788,6 +790,7 @@ func planBatch(ctx context.Context, open *stores, batch *inventory.Plan, carry [ } plan.Delivery.Merges = named plan.Delivery.Alone = batch.Delivery != nil && batch.Delivery.Alone + plan.Delivery.Withheld = withheld // **Its moments and its class** (novox/hq ADR 0282 decision 6): measured, never acted on. plan.Times = walkTimesAtCut(*batch, plan, entries, now) if len(moved) == 0 { diff --git a/cmd/mesh-controller/delivery.go b/cmd/mesh-controller/delivery.go index f19ee10b..56e4a077 100644 --- a/cmd/mesh-controller/delivery.go +++ b/cmd/mesh-controller/delivery.go @@ -191,7 +191,15 @@ func stopWalkWithout(ctx context.Context, inv *inventory.Inventory, id, by, why if batch { return p, said, stopInBatch(ctx, inv, &p, merges, again, &said, stopped, now) } - said.Kept = keptOf(p, movesOf(again)) + entries, err := inv.Catalogued(ctx) + if err != nil { + return p, said, err + } + running := map[string][]string{} + for _, e := range entries { + running[e.Manifest.Module] = e.On + } + said.Kept = keptOf(p, movesOf(again), running) stopped(&p) p.Delivery.Kept = said.Kept if len(again) == 0 { @@ -413,8 +421,10 @@ func movesOf(merges []inventory.BatchedMerge) []string { } // keptOf are the modules a stopped walk sent that no merge walked again moves, and the machines each reached: -// those keep running the stopped walk's build. -func keptOf(p inventory.Plan, walkedAgain []string) []inventory.PlanKept { +// those keep running the stopped walk's build. A module sent to its first machines reached those, and the rest it +// was sent to after them; one sent with no first machine (its policy sends it to all together, or a later tier is +// built by it) reached every machine running it. +func keptOf(p inventory.Plan, walkedAgain []string, running map[string][]string) []inventory.PlanKept { var out []inventory.PlanKept for name, m := range p.Modules { if m == nil || slices.Contains(walkedAgain, name) { @@ -424,6 +434,9 @@ func keptOf(p inventory.Plan, walkedAgain []string) []inventory.PlanKept { if m.FirstAt != nil || m.SentAt != nil { machines = union(machines, m.First) } + if m.SentAt != nil && len(m.First) == 0 { + machines = union(machines, running[name]) + } if m.Gate != nil && m.Gate.Since != nil { machines = union(machines, m.Gate.Machines) } diff --git a/cmd/mesh-controller/issue459_test.go b/cmd/mesh-controller/issue459_test.go index b57accc3..0aa17fbc 100644 --- a/cmd/mesh-controller/issue459_test.go +++ b/cmd/mesh-controller/issue459_test.go @@ -391,3 +391,22 @@ func TestSavingPlansAndMovingMergesIsOneAct(t *testing.T) { t.Fatalf("the merge moved to %q though the act was refused", m.Plan) } } + +// What a stopped walk leaves on the machines names every machine its sends reached (review of #232): a module sent +// with no first machine reached every machine running it; one sent first reached those, and the rest it was sent +// to; a module a merge walked again moves is sent again, and is not named. +func TestKeptNamesEveryMachineASendReached(t *testing.T) { + at := t0 + p := inventory.Plan{Modules: map[string]*inventory.PlanModule{ + "together": {SentAt: &at}, + "first": {First: []string{"anchor"}, FirstAt: &at, Rest: map[string]inventory.SentDeclaration{"laptop": {}}}, + "again": {SentAt: &at}, + "unsent": {}, + }} + got := keptOf(p, []string{"again"}, map[string][]string{"together": {"laptop", "anchor"}, "again": {"anchor"}}) + want := []inventory.PlanKept{{Module: "first", Machines: []string{"anchor", "laptop"}}, + {Module: "together", Machines: []string{"anchor", "laptop"}}} + if !reflect.DeepEqual(got, want) { + t.Fatalf("kept %+v; want %+v", got, want) + } +} diff --git a/internal/inventory/plans.go b/internal/inventory/plans.go index c5b43a8b..b7d5ad24 100644 --- a/internal/inventory/plans.go +++ b/internal/inventory/plans.go @@ -201,6 +201,9 @@ type PlanDelivery struct { // Kept are the modules the stopped walk already sent and nothing walks again: the machines named keep running // the stopped walk's build (novox/hq ADR 0299). Kept []PlanKept `json:"kept,omitempty"` + // Withheld are, on a walk cut from a batch a stop walked merges again in, the modules it did not build: a + // stopped merge moves them, and built only as dependents at the branch they would deliver it (ADR 0299). + Withheld []string `json:"withheld,omitempty"` // Batch is the window of a batch; nil once it is cut into a walk. Batch *PlanBatch `json:"batch,omitempty"` // Alone says the walk walks one merge on its own commit, after a failed walk carried it in a later one: diff --git a/testdata/store-tests b/testdata/store-tests index 1a66198b..17c6cc1e 100644 --- a/testdata/store-tests +++ b/testdata/store-tests @@ -1,3 +1,9 @@ # The tests that need the throwaway store and must have run against it (novox/hq issue 459): merge-check.sh runs # each one named here, by package, and fails unless every one passed — a test that needs the store skips without # one, and a skip passes unseen. One per line: . Lines starting with # are said nothing of. +./cmd/mesh-controller TestAStopWithoutAMergeWalksTheOthersAgain +./cmd/mesh-controller TestAStopWithoutAMergeEndsTheMergesThatContainIt +./cmd/mesh-controller TestAStopWalksTheOthersAgainInTheOpenBatch +./cmd/mesh-controller TestAStopNamesWhatStaysOnTheMachinesAndWhatStillCarriesIt +./cmd/mesh-controller TestAStopWithoutAMergeLeavesItOutOfABatch +./cmd/mesh-controller TestSavingPlansAndMovingMergesIsOneAct