Hold a merge for any failure to record it, so the bus handing it over again never loses it (issue 1583)
mesh/merge-gate head 5c94759cc0c2 merged into trunk c8539bf35a8d — pass: builds mesh-controller → novox; no bus step; every machine composes with the chang…
mesh/repo-check head 5c94759cc0c2 merged into trunk c8539bf35a8d — pass: its merge-check.sh passed
mesh/delivery delivered

This commit is contained in:
2026-10-21 08:43:09 +00:00
parent 0fee5404d9
commit 5c94759cc0
3 changed files with 136 additions and 12 deletions
+28 -12
View File
@@ -3,6 +3,7 @@ package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"slices"
@@ -170,6 +171,21 @@ func (f mergeFacts) plans() bool {
return len(f.from) > 0 || len(f.packaging) > 0 || len(f.later) > 0 || f.owed
}
// heldMerge marks a failure to act on a merge, whatever it was, as "not now": the announcement is held on the bus and
// handed over again, so the merge joins the next batch, never taken (issue 1583, after 921). The bus acknowledges it
// only when hearMerge answers without ErrTryAgain, and a merge heard once is not announced again: the bus answering
// "jetstream not enabled", a bucket missing for a moment or a store read cut short each lost the merge when only an
// outage of a known kind (notNow) was held. The delay between hands-over is bounded (link.MergeRedeliverAfter).
func heldMerge(err error) error {
if err == nil {
return nil
}
if errors.Is(err, link.ErrTryAgain) {
return err
}
return fmt.Errorf("%w: the merge is not kept yet: %w", link.ErrTryAgain, err)
}
// hearMerge puts a merge the forge announced into the batch that answers it, at the moment it was heard,
// and cuts what is due.
func (f following) hearMerge(ctx context.Context, m link.SourceMoved, now time.Time) error {
@@ -180,7 +196,7 @@ func (f following) hearMerge(ctx context.Context, m link.SourceMoved, now time.T
inv := f.open.inventory
repository := m.Owner + "/" + m.Repo
if kept, known, err := inv.MergeOf(ctx, repository, m.Commit); err != nil {
return notNow(err)
return heldMerge(err)
} else if known {
fmt.Printf("%s merged into %s (%.8s) was heard before; %s answers it\n", repository, m.Base, m.Commit,
orWords(kept.Plan, "a walk of its own commit, once the walks before it end"))
@@ -188,15 +204,15 @@ func (f following) hearMerge(ctx context.Context, m link.SourceMoved, now time.T
}
entries, err := inv.Catalogued(ctx)
if err != nil {
return notNow(err)
return heldMerge(err)
}
read, err := readForPlanning(ctx, inv)
if err != nil {
return notNow(err)
return heldMerge(err)
}
facts, err := factsOfMerge(ctx, inv, m, now, entries, read)
if err != nil {
return notNow(err)
return heldMerge(err)
}
merged, touches := facts.merged, facts.touches
if !facts.plans() {
@@ -210,7 +226,7 @@ func (f following) hearMerge(ctx context.Context, m link.SourceMoved, now time.T
}
if already == 0 {
if done, err := f.firstBuilds(ctx, m, entries, read, now); err != nil || done {
return err
return heldMerge(err)
}
}
fmt.Print(nothingReadsIt(repository, m))
@@ -220,16 +236,16 @@ func (f following) hearMerge(ctx context.Context, m link.SourceMoved, now time.T
own := ownPath(touches)
waits, holding, err := groupWaitHolding(ctx, inv, m, deliverySeatHeld(entries))
if err != nil {
return notNow(err)
return heldMerge(err)
}
release, err := inv.HoldPlans(ctx, true)
if err != nil {
return notNow(err)
return heldMerge(err)
}
defer release()
// Again under the hold: a controller handing over may have kept it since.
if _, known, err := inv.MergeOf(ctx, repository, m.Commit); err != nil || known {
return notNow(err)
return heldMerge(err)
}
var moves []string
for _, e := range touches {
@@ -248,11 +264,11 @@ func (f following) hearMerge(ctx context.Context, m link.SourceMoved, now time.T
// word** (decided during the build, 2026-10-10): batched together, the catalogue's deliveries would start
// with the controller's and skip their turn. Each kind has a batch of its own.
if p, ok, err := answeredByALaterMerge(ctx, inv, heard, touches, own, deliverySeatHeld(entries)); err != nil {
return notNow(err)
return heldMerge(err)
} else if ok {
heard.Plan = p.ID
if _, err := inv.AddMerge(ctx, heard); err != nil {
return notNow(err)
return heldMerge(err)
}
if p.Batch() {
if err := keepBatch(ctx, inv, &p, now, ""); err != nil {
@@ -268,11 +284,11 @@ func (f following) hearMerge(ctx context.Context, m link.SourceMoved, now time.T
batch, why, err := batchToJoin(ctx, inv, heard, entries, read, now, own, waits)
if err != nil {
return notNow(err)
return heldMerge(err)
}
heard.Plan = batch.ID
if added, err := inv.AddMerge(ctx, heard); err != nil || !added {
return notNow(err)
return heldMerge(err)
}
holdFor(&batch, m.Head, waits, holding, own, now)
if err := keepBatch(ctx, inv, &batch, now, ""); err != nil {