From 7f074d9c94c6583468991f337fac4ac67a72493e Mon Sep 17 00:00:00 2001 From: jochen Date: Sun, 18 Oct 2026 13:41:25 +0200 Subject: [PATCH] A layout run again sets no pointer back and skips records already moved (issue 1121) The live start recorded its layout in chain_moved, so the marker is missing and the layout runs once more over the old keys. It wrote newest, worked, held, commit and source over the pointers of every build made since. Pointers are now made only where missing, and a record already under its new key is not read again. --- internal/inventory/chain_builds.go | 24 +++++++++++++- .../inventory/chain_builds_layout_test.go | 33 +++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/internal/inventory/chain_builds.go b/internal/inventory/chain_builds.go index 459ec109..c6db6b75 100644 --- a/internal/inventory/chain_builds.go +++ b/internal/inventory/chain_builds.go @@ -1206,6 +1206,28 @@ func (i *Inventory) layoutBuilds(ctx context.Context, c *chain.Chain, say func(s if r.Records%1000 == 0 { say("laying the builds out: %d of %d records, %d keys written so far", r.Records, len(ids), r.Written) } + // A record already under its new key is moved: a run again (the start before this one laid it out) neither reads its + // manifest nor writes it, and **no pointer is set back**: they are only made where missing. + own := chain.BuildKey(rec.Module, rec.ID) + if rec.State == chain.BuildAsked { + own = chain.RequestKey(rec.ID) + } + if _, there, err := c.Builds.GetRaw(ctx, own); err != nil { + return r, err + } else if there { + r.Present++ + if rec.State != chain.BuildAsked { + if wrote, err := i.pointTo(ctx, c, chain.IDKey(rec.ID), idPointer(rec.Module), false); err != nil { + return r, err + } else if wrote { + r.Written++ + } + } + if rec.State != chain.BuildAsked && rec.Module != "" { + byModule[rec.Module] = append(byModule[rec.Module], rec) + } + continue + } // A manifest already written in this run is not written again. digest := "" if body := manifestBytes([]byte(rec.Manifest)); body != nil && c.Kept { @@ -1265,7 +1287,7 @@ func (i *Inventory) layoutBuilds(ctx context.Context, c *chain.Chain, say func(s return r, err } for _, p := range points { - wrote, err := i.pointTo(ctx, c, p.key, p.value, true) + wrote, err := i.pointTo(ctx, c, p.key, p.value, false) if err != nil { return r, err } diff --git a/internal/inventory/chain_builds_layout_test.go b/internal/inventory/chain_builds_layout_test.go index b1442c0e..26e30f3a 100644 --- a/internal/inventory/chain_builds_layout_test.go +++ b/internal/inventory/chain_builds_layout_test.go @@ -426,3 +426,36 @@ func TestTheChainIsRefusedUntilTheBuildsAreLaidOutAndTheLayoutIsDoneOnce(t *test t.Fatalf("the second layout changed the bucket: %v", err) } } + +// A layout run again — the live start recorded the first in chain_moved, not in the marker — never sets a pointer back to an +// older build, and does not read a manifest of a record already under its new key. +func TestALayoutRunAgainLeavesThePointersOfTheBuildsMadeSince(t *testing.T) { + counting := &readCounting{Backend: chain.NewMemory()} + inv, c := layoutChain(counting) + ctx := context.Background() + for n := 1; n <= 2; n++ { + old := legacyBuild(n, "alpha", fmt.Sprintf("c%d", n)) + if _, err := c.Builds.Create(ctx, old.ID, old); err != nil { + t.Fatal(err) + } + } + if _, err := inv.layoutBuilds(ctx, c, nil); err != nil { + t.Fatal(err) + } + if err := inv.chainRecordBuild(ctx, c, layoutBuild("alpha", 3, "c3", "s3")); err != nil { + t.Fatal(err) + } + counting.gets.Store(0) + r, err := inv.layoutBuilds(ctx, c, nil) + if err != nil || r.Written != 0 { + t.Fatalf("the second layout %+v: %v", r, err) + } + for _, key := range []string{"alpha.newest", "alpha.worked", "alpha.held"} { + if e, _, _ := counting.Get(ctx, key); string(e.Value) != "build-3" { + t.Errorf("%s = %q after the second layout; want build-3", key, e.Value) + } + } + if e, _, _ := counting.Get(ctx, "alpha.commit.c3"); pointedID(e.Value) != "build-3" { + t.Errorf("commit c3 = %s", e.Value) + } +}