Lay the builds out before the start reads them, so the controller does not exit on its own start (issue 1121) #382

Merged
mesh-tools merged 2 commits from fix/lay-builds-out-before-serving into main 2026-10-18 12:50:38 +00:00
4 changed files with 58 additions and 11 deletions
+8 -2
View File
@@ -43,8 +43,14 @@ func attachTheChain(ctx context.Context, inv *inventory.Inventory, server *link.
} }
inv.UseChain(c) inv.UseChain(c)
inv.RequireLayout() inv.RequireLayout()
// And the builds laid out under their modules, once, before they are read (issue 1121). // And the builds laid out under their modules, once, before they are read (issue 1121) — **before this returns**, so
go layBuildsOutSoon(ctx, inv) // nothing the start goes on to read meets a chain not laid out yet: on 2026-10-18 the layout ran beside the start, a
// read of the start was refused, and the controller exited and was started again every five seconds. The layout of
// keys laid out before skips them, so this takes seconds; one that fails is tried again beside the serving, loudly.
if err := inv.LayBuildsOut(ctx, func(format string, args ...any) { fmt.Printf("chain: "+format+"\n", args...) }); err != nil {
fmt.Printf("chain: the builds are not laid out under their modules yet, and none is read until they are: %v\n", err)
go layBuildsOutSoon(ctx, inv)
}
// And the requests the move of delivery 1 found never handed over, asked again once (askAgain), once it is serving. // And the requests the move of delivery 1 found never handed over, asked again once (askAgain), once it is serving.
go askAgainSoon(ctx, inv) go askAgainSoon(ctx, inv)
// And what delivery 1 recorded of what each machine was sent, copied onto the keys of the assignments (CopySentBuilds). // And what delivery 1 recorded of what each machine was sent, copied onto the keys of the assignments (CopySentBuilds).
+17 -1
View File
@@ -41,6 +41,16 @@ func (i *Inventory) RequireLayout() {
i.state.mu.Unlock() i.state.mu.Unlock()
} }
// layingOut lets one read at a time lay the builds out; the others wait for it and then find the marker.
var layingOut sync.Mutex
// layOutOnce lays the builds out under their modules unless another read did so meanwhile.
func (i *Inventory) layOutOnce(ctx context.Context) error {
layingOut.Lock()
defer layingOut.Unlock()
return i.LayBuildsOut(ctx, func(format string, args ...any) { fmt.Printf("chain: "+format+"\n", args...) })
}
func (i *Inventory) markLaidOut() { func (i *Inventory) markLaidOut() {
i.state.mu.Lock() i.state.mu.Lock()
i.state.laid = true i.state.laid = true
@@ -107,7 +117,13 @@ func (i *Inventory) theChain(ctx context.Context) (*chain.Chain, error) {
if _, found, err := c.Builds.GetRaw(ctx, chain.LayoutMarkerKey); err != nil { if _, found, err := c.Builds.GetRaw(ctx, chain.LayoutMarkerKey); err != nil {
return nil, err return nil, err
} else if !found { } else if !found {
return nil, ErrNotLaidOut // **The first read lays them out** (issue 1121): on 2026-10-18 a read of the controller's start came before
// the layout and was refused, and the controller exited and was started again every five seconds. A
// process that may write (the lease holder) lays them out here, once; one that may not is refused, loudly.
if err := i.layOutOnce(ctx); err != nil {
return nil, fmt.Errorf("%w (laying them out now failed: %v)", ErrNotLaidOut, err)
}
return c, nil
} }
i.markLaidOut() i.markLaidOut()
} }
@@ -3,7 +3,6 @@ package inventory
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"reflect" "reflect"
"sort" "sort"
@@ -398,9 +397,10 @@ func TestAnOlderBuildFinishingLastLeavesThePointersOnTheNewer(t *testing.T) {
} }
} }
// Nothing reads the chain on the bus until the controller's start has laid the builds out and written the marker; the layout // Nothing reads the chain on the bus before the builds are laid out and the marker written: the first read of a process that
// is done once. // may write lays them out itself (issue 1121: a refused read of the start made the controller exit), and the layout is done
func TestTheChainIsRefusedUntilTheBuildsAreLaidOutAndTheLayoutIsDoneOnce(t *testing.T) { // once.
func TestTheFirstReadLaysTheBuildsOutAndTheLayoutIsDoneOnce(t *testing.T) {
mem := chain.NewMemory() mem := chain.NewMemory()
inv, c := layoutChain(mem) inv, c := layoutChain(mem)
inv.RequireLayout() inv.RequireLayout()
@@ -409,11 +409,11 @@ func TestTheChainIsRefusedUntilTheBuildsAreLaidOutAndTheLayoutIsDoneOnce(t *test
if _, err := c.Builds.Create(ctx, old.ID, old); err != nil { if _, err := c.Builds.Create(ctx, old.ID, old); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if _, err := inv.theChain(ctx); !errors.Is(err, ErrNotLaidOut) { if _, err := inv.theChain(ctx); err != nil {
t.Fatalf("a read before the layout answered %v", err) t.Fatalf("the first read was refused, not laid out: %v", err)
} }
if err := inv.LayBuildsOut(ctx, nil); err != nil { if !containsKey(keysOf(t, mem), chain.BuildKey("alpha", old.ID)) {
t.Fatal(err) t.Fatalf("the first read did not lay %s out under its module: %v", old.ID, keysOf(t, mem))
} }
if _, err := inv.theChain(ctx); err != nil { if _, err := inv.theChain(ctx); err != nil {
t.Fatalf("a read after the layout answered %v", err) t.Fatalf("a read after the layout answered %v", err)
@@ -0,0 +1,25 @@
package inventory
import (
"context"
"testing"
"github.com/novox/mesh-controller/internal/chain"
)
// The first read of a chain that requires the layout lays the builds out itself when it may write, instead of refusing
// the controller's own start (issue 1121, 2026-10-18: the controller exited every five seconds).
func TestTheFirstReadOfAChainNotLaidOutLaysItOut(t *testing.T) {
ctx := context.Background()
inv, c := anInventoryOnTheChain(t)
inv.RequireLayout()
if _, found, _ := c.Builds.GetRaw(ctx, chain.LayoutMarkerKey); found {
t.Fatal("a fresh chain holds the marker already; the test proves nothing")
}
if _, err := inv.theChain(ctx); err != nil {
t.Fatalf("the first read was refused: %v", err)
}
if _, found, err := c.Builds.GetRaw(ctx, chain.LayoutMarkerKey); err != nil || !found {
t.Fatalf("the read did not lay the builds out: found %v, %v", found, err)
}
}