Cut a closed batch beside the open walks it shares nothing with, queue a related one with the reason, fold only related waiting walks (issue 973)
mesh/merge-gate head 300d3c1bf466 merged into trunk 6a62a7e552ea — pass: builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step; every …
mesh/repo-check head 300d3c1bf466 merged into trunk 6a62a7e552ea — fail: its merge-check.sh failed: --- FAIL: TestSSHClientOwnsTheOperatorsSSHConfig (0.01s)
mesh/delivery superseded: a newer head of the same pull request
mesh/merge-gate head 300d3c1bf466 merged into trunk 6a62a7e552ea — pass: builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step; every …
mesh/repo-check head 300d3c1bf466 merged into trunk 6a62a7e552ea — fail: its merge-check.sh failed: --- FAIL: TestSSHClientOwnsTheOperatorsSSHConfig (0.01s)
mesh/delivery superseded: a newer head of the same pull request
This commit is contained in:
+122
-43
@@ -27,9 +27,10 @@ import (
|
||||
// commit in it, with at most one commit per repository, the latest of its branch, which contains the earlier
|
||||
// ones. The walk names every merge it answers.
|
||||
//
|
||||
// - One walk at a time. Merges that come while a walk runs join the next batch. A walk that started is never
|
||||
// superseded; one still waiting for its delivery's word is folded into the next batch's walk, which
|
||||
// answers every merge it answered — the only supersession left.
|
||||
// - One walk at a time beside a related one (ADR 0407 decision 4, issue 973). Merges that come while a walk
|
||||
// runs join the next batch; a closed batch that shares nothing with an open walk is cut beside it. A walk
|
||||
// that started is never superseded; one still waiting for its delivery's word is folded into the walk of a
|
||||
// batch related to it, which answers every merge it answered — the only supersession left.
|
||||
// - A merge heard after a later merge of its branch was put in a walk is answered by that walk: a batch
|
||||
// still assembling, an open walk that builds everything it moves, or a walk done that did, which then
|
||||
// names it at once. Otherwise it joins the next batch.
|
||||
@@ -39,8 +40,9 @@ import (
|
||||
//
|
||||
// **A merge waits only for a batch it shares something with** (novox/hq ADR 0304 decision 1, ADR 0306 decisions
|
||||
// 2 and 3, issue 525): it joins a batch it is related to, and one that is related to none and whose delivery
|
||||
// group has no member still to merge is a batch whose window is closed at once (batchToJoin). Walks stay one at
|
||||
// a time: such a batch is cut when no walk is open, and queued behind the one that is otherwise.
|
||||
// group has no member still to merge is a batch whose window is closed at once (batchToJoin). Such a batch is cut
|
||||
// at once, beside any open walk it shares nothing with, and queued behind the first one it is related to
|
||||
// (cutUnrelated).
|
||||
//
|
||||
// A merge on the controller's own path (a module whose walk waits for nobody's word) never shares a batch with
|
||||
// one that waits for mesh-delivery's: each kind has a batch of its own, an own-path batch is cut first and folds
|
||||
@@ -340,8 +342,8 @@ func ownPath(touches []inventory.Entry) bool {
|
||||
// names why): a new batch, which waits for the window, beside the ones it shares nothing with; its words say
|
||||
// why.
|
||||
// - none, and no member of its group is still to merge: a new batch whose window is closed at once. Cut at
|
||||
// once means only that: walks stay one at a time (ADR 0276 decision 4), so with a walk open the batch is
|
||||
// queued behind it as any closed batch is.
|
||||
// once means only that: with a walk open that it is related to the batch is queued behind it as any closed
|
||||
// batch is (ADR 0276 decision 4, ADR 0407 decision 4); beside one it shares nothing with it is cut.
|
||||
func batchToJoin(ctx context.Context, inv *inventory.Inventory, heard inventory.BatchedMerge, entries []inventory.Entry,
|
||||
read map[string][]inventory.ReadRepository, now time.Time, own bool, waits string) (inventory.Plan, string, error) {
|
||||
edges, err := inv.Dependencies(ctx)
|
||||
@@ -406,7 +408,9 @@ func relatedBatches(ctx context.Context, inv *inventory.Inventory, merges []inve
|
||||
}
|
||||
carry := newFootprint()
|
||||
if !own {
|
||||
// An own-path batch folds no waiting walk (cutBatchesHeld); a catalogue batch may fold every one.
|
||||
// An own-path batch folds no waiting walk (cutBatchesHeld); a catalogue batch folds those it is related to
|
||||
// (cutUnrelated). Every one is counted here, which only makes a merge join a batch more readily: it waits
|
||||
// for the window, never walks beside what it shares something with (issue 973).
|
||||
plans, err := inv.OpenPlans(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
@@ -544,6 +548,11 @@ func newBatch(ctx context.Context, inv *inventory.Inventory, first inventory.Bat
|
||||
// keepBatch writes a batch as its merges and the clock say it now: the commits it holds, the merges they
|
||||
// answer, its window, and assembling or queued behind a walk. Saved only when that changed.
|
||||
func keepBatch(ctx context.Context, inv *inventory.Inventory, b *inventory.Plan, now time.Time, behind string) error {
|
||||
return keepBatchBecause(ctx, inv, b, now, behind, "")
|
||||
}
|
||||
|
||||
// keepBatchBecause is keepBatch for a batch queued behind a walk it is related to, which its words say (issue 973).
|
||||
func keepBatchBecause(ctx context.Context, inv *inventory.Inventory, b *inventory.Plan, now time.Time, behind, why string) error {
|
||||
merges, err := inv.MergesOf(ctx, b.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -574,13 +583,13 @@ func keepBatch(ctx context.Context, inv *inventory.Inventory, b *inventory.Plan,
|
||||
cutAtOnce = was.CutAtOnce // the merge's, until another joins it (ADR 0304 decision 1)
|
||||
waits = was.Waits // its group's, as said when it was made (ADR 0306 decision 2)
|
||||
}
|
||||
b.Delivery.Batch = &inventory.PlanBatch{ClosesAt: closes, AtMost: latest, Behind: behind, Own: b.OwnPath(),
|
||||
b.Delivery.Batch = &inventory.PlanBatch{ClosesAt: closes, AtMost: latest, Behind: behind, BehindWhy: why, Own: b.OwnPath(),
|
||||
Closed: closed, Withheld: withheld, CutAtOnce: cutAtOnce, Waits: waits}
|
||||
b.State = inventory.PlanAssembling
|
||||
if behind != "" && windowClosed(*b, now) {
|
||||
b.State = inventory.PlanQueued
|
||||
} else {
|
||||
b.Delivery.Batch.Behind = ""
|
||||
b.Delivery.Batch.Behind, b.Delivery.Batch.BehindWhy = "", ""
|
||||
}
|
||||
b.Note = batchWords(*b, now)
|
||||
if unNoted(*b) == before {
|
||||
@@ -761,9 +770,9 @@ func batchCutter(ctx context.Context, open *stores) {
|
||||
}
|
||||
|
||||
// cutBatchesHeld takes the step that is due, for a caller holding the plans: after a failed walk, the search
|
||||
// for the merge that brought the failure; with no walk open, the next merge of that search walked alone, or a
|
||||
// closed batch cut into one walk with the waiting walks folded in; with a walk open, a closed batch said
|
||||
// queued behind it.
|
||||
// for the merge that brought the failure; with no walk open, the next merge of that search walked alone; else
|
||||
// each closed batch cut into a walk with the waiting walks it is related to folded in, unless it is related to a
|
||||
// walk open, behind which it is said queued (cutUnrelated). While the search runs everything waits.
|
||||
func cutBatchesHeld(ctx context.Context, open *stores, now time.Time) error {
|
||||
inv := open.inventory
|
||||
if err := searchAfterFailures(ctx, inv); err != nil {
|
||||
@@ -780,8 +789,7 @@ func cutBatchesHeld(ctx context.Context, open *stores, now time.Time) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var started *inventory.Plan
|
||||
var waiting []inventory.Plan
|
||||
var started, waiting []inventory.Plan
|
||||
for i := range plans {
|
||||
p := plans[i]
|
||||
if p.Release != nil {
|
||||
@@ -789,8 +797,8 @@ func cutBatchesHeld(ctx context.Context, open *stores, now time.Time) error {
|
||||
}
|
||||
if p.Waiting() {
|
||||
waiting = append(waiting, p)
|
||||
} else if started == nil {
|
||||
started = &plans[i]
|
||||
} else {
|
||||
started = append(started, p)
|
||||
}
|
||||
}
|
||||
batches, err := inv.Batches(ctx)
|
||||
@@ -805,46 +813,113 @@ func cutBatchesHeld(ctx context.Context, open *stores, now time.Time) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if started != nil {
|
||||
// **One walk at a time** (ADR 0276 decision 4): every batch waits behind it, whatever its window says.
|
||||
return keepAll(started.ID)
|
||||
}
|
||||
if len(alone) > 0 {
|
||||
// A failed walk's search walks its merges alone, one at a time: everything waits for it (kept as it was,
|
||||
// issue 973).
|
||||
if len(started) > 0 {
|
||||
return keepAll(started[0].ID)
|
||||
}
|
||||
if len(waiting) > 0 {
|
||||
// The waiting walk is the one open; the search walks after it, and the batches wait behind both.
|
||||
return keepAll(waiting[0].ID)
|
||||
}
|
||||
return walkAlone(ctx, open, alone[0], now)
|
||||
}
|
||||
if err := keepAll(""); err != nil {
|
||||
return cutUnrelated(ctx, open, batches, started, waiting, now)
|
||||
}
|
||||
|
||||
// cutUnrelated cuts the closed batches that share nothing with an open walk, each into a walk beside the open
|
||||
// ones, and says the others queued behind the first walk they are related to (novox/hq ADR 0407 decision 4,
|
||||
// issue 973). A batch cut in this look counts as open for the batches after it.
|
||||
//
|
||||
// - An own-path batch goes first and folds no waiting walk; a catalogue batch folds the waiting walks it is
|
||||
// related to, and those it shares nothing with keep waiting.
|
||||
// - **The controller's own path is related to everything** (decided for issue 973): a walk or batch that
|
||||
// replaces the controller, the node-engine, the tool runner, the bus or mesh-delivery never runs beside
|
||||
// another, and nothing runs beside it.
|
||||
// - A batch whose window is still open is assembling; one related to an open walk is queued behind it, and
|
||||
// ADR 0276 decision 4 stands for it: nothing supersedes a walk that started.
|
||||
func cutUnrelated(ctx context.Context, open *stores, batches, started, waiting []inventory.Plan, now time.Time) error {
|
||||
inv := open.inventory
|
||||
sort.SliceStable(batches, func(i, j int) bool { return batches[i].OwnPath() && !batches[j].OwnPath() })
|
||||
var entries []inventory.Entry
|
||||
var read map[string][]inventory.ReadRepository
|
||||
var edges []inventory.Edge
|
||||
judged := false
|
||||
openWalks, err := openWalksOf(ctx, inv, started)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// The oldest batch whose window closed is cut, an own-path one before a catalogue one (it goes first, and
|
||||
// the catalogue batch queues behind it rather than being cut and overtaken). Several of a kind may be open
|
||||
// since a merge that shares nothing is cut at once (ADR 0304 decision 1): walks stay one at a time, so one
|
||||
// is cut per look, and once its walk started the others are queued behind it.
|
||||
sort.SliceStable(batches, func(i, j int) bool { return batches[i].OwnPath() && !batches[j].OwnPath() })
|
||||
for i := range batches {
|
||||
b := &batches[i]
|
||||
if !windowClosed(*b, now) {
|
||||
continue
|
||||
}
|
||||
if b.OwnPath() {
|
||||
// A walk on the controller's own path folds no walk waiting for its word (decided during the build,
|
||||
// 2026-10-10): that walk keeps waiting beside it, and starts once this one ended (advanceOnce).
|
||||
waiting = nil
|
||||
}
|
||||
if err := cutBatch(ctx, open, b, waiting, now); err != nil {
|
||||
return err
|
||||
}
|
||||
// The batches left wait behind the walk just cut when it started; the next look does the same.
|
||||
if b.Open() && !b.Waiting() {
|
||||
if batches, err = inv.Batches(ctx); err != nil {
|
||||
if err := keepBatch(ctx, inv, b, now, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
return keepAll(b.ID)
|
||||
continue
|
||||
}
|
||||
if !judged {
|
||||
var err error
|
||||
if entries, err = inv.Catalogued(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if read, err = readForPlanning(ctx, inv); err != nil {
|
||||
return err
|
||||
}
|
||||
if edges, err = inv.Dependencies(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
judged = true
|
||||
}
|
||||
held, err := inv.MergesOf(ctx, b.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fp := footprintAsCut(held, entries, read, edges)
|
||||
var fold, keep []inventory.Plan
|
||||
if !b.OwnPath() {
|
||||
for _, w := range waiting {
|
||||
ww, err := openWalkOf(ctx, inv, w)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ok, _ := related(fp, ww.fp, edges); ok {
|
||||
fold = append(fold, w)
|
||||
fp = unionFootprint(fp, ww.fp)
|
||||
} else {
|
||||
keep = append(keep, w)
|
||||
}
|
||||
}
|
||||
}
|
||||
if behind, why := relatedOpen(fp, b.OwnPath(), openWalks, edges); behind != "" {
|
||||
if err := keepBatchBecause(ctx, inv, b, now, behind, why); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err := keepBatch(ctx, inv, b, now, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := cutBatch(ctx, open, b, fold, now); err != nil {
|
||||
return err
|
||||
}
|
||||
waiting = keep
|
||||
cut, err := inv.PlanByID(ctx, b.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !cut.Open() {
|
||||
continue
|
||||
}
|
||||
w, err := openWalkOf(ctx, inv, cut)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cut.Waiting() {
|
||||
waiting = append(waiting, cut)
|
||||
} else {
|
||||
openWalks = append(openWalks, w)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1396,7 +1471,11 @@ func batchWords(b inventory.Plan, now time.Time) string {
|
||||
grouped += " (on the controller's own path)"
|
||||
}
|
||||
if b.State == inventory.PlanQueued {
|
||||
return fmt.Sprintf("queued behind %s; grouped: %s; plan not yet calculated", w.Behind, grouped)
|
||||
because := ""
|
||||
if w.BehindWhy != "" {
|
||||
because = " (" + w.BehindWhy + ")"
|
||||
}
|
||||
return fmt.Sprintf("queued behind %s%s; grouped: %s; plan not yet calculated", w.Behind, because, grouped)
|
||||
}
|
||||
if w.CutAtOnce {
|
||||
return fmt.Sprintf("cut at once, sharing nothing with a batch assembling; grouped: %s; plan not yet calculated",
|
||||
|
||||
Reference in New Issue
Block a user