carlos / platform Public

sim: hostlife: the recovery build takes stop evidence first and reuses a current snapshot on replay

Seed 86799 of 100,000: a descendant that outlived the main process wrote
to the warm database between the recovery's snapshot and the generation
moving, so the write was acknowledged, uploaded into the old generation
and absent from the new one. The build now requires stop evidence before
it takes its baseline, as every other operation does. A replayed build
that finds a snapshot current in the new generation reuses it as the
baseline and reuses a current anchor, never rebuilding from another
baseline under the same key.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev 15f2fa100ce3a013fb631f24ee5f3c8be5206b09 parent 34ddfa4
1 file changed, +26 −5
  • internal/sim/hostlife/moves_test.go +26 −5
diff --git a/internal/sim/hostlife/moves_test.go b/internal/sim/hostlife/moves_test.go
index 6782bf8d7..ab2339697 100644
--- a/internal/sim/hostlife/moves_test.go
+++ b/internal/sim/hostlife/moves_test.go
@@ -1625,6 +1625,9 @@ func (w *world) anchorAcked(b *box, loc, gen, snapKey string, snapVer, tx int) b
if sv == nil {
return false
}
+ if w.buckets[b.Region].current(ltxKey(loc, gen, 0, tx, tx)) != nil {
+ return true // a replay finds its anchor current and reuses it
+ }
db := &localDB{Inc: sv.B.Inc, Loc: loc, Gen: gen, Key: sv.B.SealKey, Pos: tx, State: cloneState(sv.B.Pages), Log: append([]int(nil), sv.B.TXs...)}
acked := w.ackedPut(b, ltxKey(loc, gen, 0, tx, tx), anchorOf(db, sv.B.Inc), false)
w.stats["restore anchors"]++
@@ -1655,11 +1658,29 @@ func (w *world) recoverStep(b *box, c *claim) {
loc := h.Location
switch op.Phase {
case "build":
+ // nothing of the host may run while the baseline is taken: a
+ // descendant that outlived the main process would write to the
+ // database after the snapshot and before the generation moved
+ // (seed 86799: a write acknowledged during the build was lost)
+ if !w.evidence(b, op.ID) {
+ return
+ }
var base *localDB
- if b.DB != nil && b.DB.Inc == h.Inc && b.DB.Gen == op.BaseGen {
+ reused := false
+ bk := w.buckets[b.Region]
+ if snaps := w.currentLTX(bk, dataKey(loc, op.ID, ""), 9); len(snaps) > 0 {
+ // a replayed build (a crash after the snapshot landed, with or
+ // without the disk): the snapshot already current in the new
+ // generation is reused, never rebuilt from another baseline,
+ // which could carry other bytes under the same key (R7-4, R6-5;
+ // seed 86799: the first build's snapshot held a write the
+ // rebuilt one from the old generation did not)
+ sv := snaps[len(snaps)-1].B
+ base = &localDB{Inc: h.Inc, Loc: loc, Gen: op.ID, Key: sv.SealKey, Pos: sv.Max, State: cloneState(sv.Pages), Log: txsOf(sv), TX: w.lineageTX(sv)}
+ reused = true
+ w.stats["cold recovery: snapshot reused on replay"]++
+ } else if b.DB != nil && b.DB.Inc == h.Inc && b.DB.Gen == op.BaseGen {
base = b.DB
- } else if b.DB != nil && b.DB.Inc == h.Inc && b.DB.Gen == op.ID {
- base = b.DB // a replayed build after a crash: the restored file is there
} else {
base = w.restoreCalc(b.Region, h.Inc, loc, op.BaseGen)
if base == nil {
@@ -1674,10 +1695,10 @@ func (w *world) recoverStep(b *box, c *claim) {
}
n := base.Pos
key := snapKey(loc, op.ID, n)
- if !w.ackedPut(b, key, snap, false) {
+ if !reused && !w.ackedPut(b, key, snap, false) {
return
}
- sv := w.buckets[b.Region].current(key)
+ sv := bk.current(key)
if sv == nil {
return
}