carlos / platform Public

sim: hostlife: claim rev 6.4 stamps, sequential level-0 uploads, the legacy location's children (rev 7.6)

Claim rev 6.4 (697f0f9f) answered rev 7.6's notes: retire-artifacts(S)
moves no stamp, so retireArtifacts is a named no-op on the record and
install (activation) retires the old active stamp before setting the new
one; the pass's stamp is captured at work selection, so fence-stamp and
its witness stand; a child prefix that is not an incarnation id belongs
to the host prefix's own location, so locationOf no longer reads g-<op>/
under a legacy host as an incarnation (the first rev 7.6 baseline swept a
live generation that way, 2 seeds of 3,000).

Uploads are sequential, as litestream's are: each sync writes its level-0
file to a local queue and ship uploads the queue in TXID order, never
sending the next file before the one ahead completed, retrying the same
bytes after eight steps. The second rev 7.6 baseline shipped the next file
while the first was still in flight and a cold restore met the gap
(9 seeds of 30,000, 19 of 100,000).

fence-stamp is caught alone by the sweep now (a pass that selected before
the record existed carries no stamp): expected-outcomes.txt drops it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev 50b1e51de0120d43ea48027b2efcda2385e89019 parent 29245f6
10 files changed, +136 −79
  • internal/sim/hostlife/background_test.go +3 −3
  • internal/sim/hostlife/console_test.go +2 −2
  • internal/sim/hostlife/expected-outcomes.txt +0 −1
  • internal/sim/hostlife/ltx_test.go +48 −12
  • internal/sim/hostlife/moves_test.go +25 −15
  • internal/sim/hostlife/protocol_test.go +5 −5
  • internal/sim/hostlife/rules_test.go +3 −3
  • internal/sim/hostlife/sim_test.go +1 −1
  • internal/sim/hostlife/witness_test.go +2 −2
  • internal/sim/hostlife/world_test.go +47 −35
diff --git a/internal/sim/hostlife/background_test.go b/internal/sim/hostlife/background_test.go
index 7988c317a..a5d37f7b7 100644
--- a/internal/sim/hostlife/background_test.go
+++ b/internal/sim/hostlife/background_test.go
@@ -150,7 +150,7 @@ func (w *world) adopt() bool {
if eb == nil {
return false
}
- n := &claim{Ver: 1, Holder: &holder{Inc: w.rec.Inc, App: w.rec.App, Legacy: true, Location: locationFor(w.rec.Inc, true), Stamp: 1}, // adoption writes holder.stamp (claim rev 6.3 §2)
+ n := &claim{Ver: 1, Holder: &holder{Inc: w.rec.Inc, App: w.rec.App, Legacy: true, Location: locationFor(w.rec.Inc, true), Stamp: 1}, // adoption writes holder.stamp (claim rev 6.4 §2)
Op: opState{Kind: "serving", Exec: eb.ID, Parts: []int{eb.ID}}, Regions: []int{w.rec.Region}, Boxes: []int{eb.ID}, Complete: false}
if on("legacy-discovery") {
// every other region parks whatever it holds: it cannot be the
@@ -197,7 +197,7 @@ func (w *world) reconcileClaimed(b *box, c *claim) {
}
if b.Row == nil && c.Op.Kind == "serving" {
b.Row = &row{Inc: h.Inc, App: h.App, Status: "ready"}
- w.initRecord(b, c) // the local record from holder.stamp, on this fresh read (claim rev 6.3 §6.4)
+ w.initRecord(b, c) // the local record from holder.stamp, on this fresh read (claim rev 6.4 §6.4)
}
} else if b.Row != nil && !(on("participant-rows") && w.ownsLocalObligation(c, b)) {
w.retireRow(b)
@@ -255,7 +255,7 @@ func (w *world) passExec(b *box) {
return // the selected bundle is no longer the held one
}
}
- // the local record (claim rev 6.3 §6.4): the writer carries the stamp
+ // the local record (claim rev 6.4 §6.4): the writer carries the stamp
// it selected under, admitted only if it is still the active one
if !b.admitsStamp(p.Inc, p.Stamp) {
return
diff --git a/internal/sim/hostlife/console_test.go b/internal/sim/hostlife/console_test.go
index 3b3244b7a..86b796fce 100644
--- a/internal/sim/hostlife/console_test.go
+++ b/internal/sim/hostlife/console_test.go
@@ -315,7 +315,7 @@ func (w *world) createSteps(p *consolePlan) bool {
return false
}
n.Op = opState{Kind: "serving", Exec: b.ID, Parts: []int{b.ID}}
- n.Holder.Stamp = w.nextGen() // create step 4: holder.stamp is this CAS's gen (claim rev 6.3 §2)
+ n.Holder.Stamp = w.nextGen() // create step 4: holder.stamp is this CAS's gen (claim rev 6.4 §2)
return true
}) {
delete(ad.Reg, p.OpID)
@@ -570,7 +570,7 @@ func (w *world) orphanSteps(p *consolePlan) bool {
// deleteSteps: record DeleteIfMatch on the version the intent names, then
// parking with obligations, then free once every obligation is
-// acknowledged (claim §5.2). Under a purge (claim rev 6.3 §5.3 step 4,
+// acknowledged (claim §5.2). Under a purge (claim rev 6.4 §5.3 step 4,
// R4-4) the same steps run under the purge's own claim: the record version
// is recorded in op.intent first, the delete is conditional on it, and
// purging → parking is legal only with the record authoritatively gone
diff --git a/internal/sim/hostlife/expected-outcomes.txt b/internal/sim/hostlife/expected-outcomes.txt
index c81903152..6cc69ce95 100644
--- a/internal/sim/hostlife/expected-outcomes.txt
+++ b/internal/sim/hostlife/expected-outcomes.txt
@@ -17,4 +17,3 @@ touched-incomplete missed caught with legacy-discovery
door-reread missed subsumed by C3a: caught with location-per-incarnation
door-by-version missed subsumed by C3a: caught with location-per-incarnation (witness), and with door-reread as well (sweep)
registration-check missed a cheap extra: subsumed by purge-claim-fence and fence-etag together
-fence-stamp missed caught with hostguard (witness): alone, the host guard's fresh re-validation refuses the stale writer's bundle; the stamp is what refuses it when that check is gone
diff --git a/internal/sim/hostlife/ltx_test.go b/internal/sim/hostlife/ltx_test.go
index 54d97a9c8..fa9bff3d8 100644
--- a/internal/sim/hostlife/ltx_test.go
+++ b/internal/sim/hostlife/ltx_test.go
@@ -240,29 +240,65 @@ func apply(state map[string]int, f *blob) {
}
// replicate is the serving process's litestream replication (claim C3's
-// exception: plain PUTs). It ships the pending writes as one level-0 file
-// at Pos+1 (an upload that times out is retried with the same bytes, so
-// the position advances at once), then runs compaction, snapshot
-// rotation and retention as the store's monitors would.
+// exception: plain PUTs). A sync writes the pending writes as one local
+// level-0 file at Pos+1; ship uploads the local files in order; then
+// compaction, snapshot rotation and retention run as the store's
+// monitors would.
func (w *world) replicate(b *box) {
if !w.agentUp(b) || !hasMain(b) || b.DB == nil {
return
}
db := b.DB
- inc := db.Inc
if len(db.Pending) > 0 {
- bl := w.deltaOf(db, inc)
- key := ltxKey(db.Loc, db.Gen, 0, bl.Min, bl.Max)
- w.issue(b.ID, &request{kind: rPut, region: b.Region, key: key, b: bl})
- // the file is this TXID's whether the upload has completed or is
- // still in flight: litestream retries the same bytes, and later
- // writes go to the next TXID. A retry that lands after the
- // original is an identical version of the same key
+ // the sync writes this TXID's file locally; the position advances
+ // at once, and later writes go to the next TXID
+ bl := w.deltaOf(db, db.Inc)
+ db.Queue = append(db.Queue, bl)
db.Pos, db.Pending = bl.Max, nil
}
+ w.ship(b)
w.compact(b)
}
+// uploadTimeout is the client's patience with one level-0 upload, in
+// steps, before litestream retries it.
+const uploadTimeout = 8
+
+// ship is the replica's upload loop: the local level-0 files go up in
+// TXID order, and the next is never sent before the one ahead of it has
+// completed (litestream does not advance past a failed upload, moves spec
+// §5.1 step 6). An upload the client has waited uploadTimeout steps for is
+// retried with the same bytes to the same key; the original may still land
+// later, as an identical version. So a gap in a generation's current
+// listing is always a file that has not landed yet, never a file that was
+// skipped.
+func (w *world) ship(b *box) {
+ db := b.DB
+ for len(db.Queue) > 0 {
+ if r := db.InFlight; r != nil {
+ if !r.done {
+ if w.now-db.InFlightAt < uploadTimeout {
+ return
+ }
+ w.stats["ltx upload retries"]++
+ } else {
+ db.InFlight = nil
+ db.Queue = db.Queue[1:]
+ continue
+ }
+ }
+ bl := db.Queue[0]
+ r := &request{kind: rPut, region: b.Region, key: ltxKey(db.Loc, db.Gen, 0, bl.Min, bl.Max), b: bl}
+ if v := w.issue(b.ID, r); v > 0 {
+ db.InFlight = nil
+ db.Queue = db.Queue[1:]
+ continue
+ }
+ db.InFlight, db.InFlightAt = r, w.now
+ return
+ }
+}
+
// currentLTX lists the current LTX files of a generation at one level, by
// range.
func (w *world) currentLTX(bk *bucket, prefix string, level int) []*version {
diff --git a/internal/sim/hostlife/moves_test.go b/internal/sim/hostlife/moves_test.go
index b2799cd0f..b24af297b 100644
--- a/internal/sim/hostlife/moves_test.go
+++ b/internal/sim/hostlife/moves_test.go
@@ -48,7 +48,7 @@ func (w *world) workerMode(b *box, obl bool) {
w.role(b, c)
}
-// initRecord is claim rev 6.3 §6.4's initialization (R5-4): a box creates
+// initRecord is claim rev 6.4 §6.4's initialization (R5-4): a box creates
// or refreshes its local stamp record only from a fresh claim read, and
// only when that claim names it as op.exec or as the landing participant;
// it sets active_stamp = holder.stamp. A create's first wake, adoption on
@@ -203,14 +203,16 @@ func (w *world) snapshotBlobOf(b *box, c *claim, gen string) (*blob, *blob) {
// woken) the replica restored as it stands
var db *blob
if b.DB != nil {
- // the final checkpoint ships the pending writes as the next level-0
- // file and waits for the upload (litestream SyncAndWait); the
- // snapshot is then at that position
+ // the final checkpoint writes the pending writes as the next local
+ // level-0 file and uploads what it can (litestream SyncAndWait);
+ // the snapshot is then at that position, and carries every page
+ // whether or not the upload has landed
if len(b.DB.Pending) > 0 {
bl := w.deltaOf(b.DB, b.DB.Inc)
- w.issue(b.ID, &request{kind: rPut, region: b.Region, key: ltxKey(b.DB.Loc, b.DB.Gen, 0, bl.Min, bl.Max), b: bl})
+ b.DB.Queue = append(b.DB.Queue, bl)
b.DB.Pos, b.DB.Pending = bl.Max, nil
}
+ w.ship(b)
db = snapshotOf(b.DB, b.DB.Inc)
} else if r := w.restore(b, c.Holder.Inc, c.Holder.Location, gen); r != nil {
db = snapshotOf(r, c.Holder.Inc)
@@ -690,7 +692,7 @@ func (w *world) followRecord(c *claim) bool {
return false
}
n.Op.Kind, n.Op.Exec, n.Op.LandPhase, n.Op.FlipAt = "landing", op.Tgt, "checking", w.now
- n.Holder.Stamp = w.nextGen() // holder.stamp: the gen this CAS produces, the landing's installation stamp (claim rev 6.3 §2)
+ n.Holder.Stamp = w.nextGen() // holder.stamp: the gen this CAS produces, the landing's installation stamp (claim rev 6.4 §2)
n.Op.FlipGen = n.Holder.Stamp
return true
})
@@ -749,7 +751,7 @@ func (w *world) landTarget(b *box, c *claim) {
// the new installation sets this box's active stamp to its own
// captured stamp, the claim gen of the CAS that made the landing
// current, under the guard, on a fresh read showing its operation
- // current and this box the landing participant (claim rev 6.3
+ // current and this box the landing participant (claim rev 6.4
// §6.4, R3-3, R4-2): a move back to a region the instance left is
// admitted at its own stamp while the retired installation's
// writers stay refused
@@ -924,7 +926,7 @@ func (w *world) installLocal(b *box, c *claim, gen string, seal ref, _ string, n
}
if b.Files == nil || b.Files.Gen != gen {
// fresh extraction: park whatever is there first, as
- // retire-artifacts of the predecessor's stamp (claim rev 6.3
+ // retire-artifacts of the predecessor's stamp (claim rev 6.4
// §6.4): the new installation's active stamp is untouched
if b.DB != nil {
b.retireArtifacts(c.Holder.Inc, b.DB.Stamp)
@@ -1198,7 +1200,7 @@ func (w *world) recoverSource(b *box, c *claim) {
w.cas(c, func(n *claim) bool {
w.newGen(n.Holder.Location, n.Holder.Gen, op.RollbackID)
n.Holder.Gen = op.RollbackID
- n.Holder.Stamp = w.nextGen() // a dirty rollback installs a new generation: a new stamp, never the predecessor's (claim rev 6.3 §6.4)
+ n.Holder.Stamp = w.nextGen() // a dirty rollback installs a new generation: a new stamp, never the predecessor's (claim rev 6.4 §6.4)
n.Obl = append(n.Obl, w.newObl(n, "park-staging", op.SrcRegion, -1, fmt.Sprintf("moves/%s/%s/", host, op.RollbackID), "", op.ReturnQuiet))
return sameOp(n.Op.ID, op.ID)
})
@@ -1300,7 +1302,7 @@ func (w *world) pushStep(b *box, c *claim) {
n.Op.Phase = "probed"
w.newGen(n.Holder.Location, n.Holder.Gen, op.ID)
n.Holder.Gen = op.ID
- n.Holder.Stamp = w.nextGen() // a push's commit installs a new generation: a new stamp (claim rev 6.3 §2)
+ n.Holder.Stamp = w.nextGen() // a push's commit installs a new generation: a new stamp (claim rev 6.4 §2)
q := op.StopAt + horizonH
if q > n.Quiet[b.Region] {
n.Quiet[b.Region] = q
@@ -1407,10 +1409,18 @@ func locationOf(k string) string {
rest := strings.TrimPrefix(k, p)
if i := strings.Index(rest, "/"); i > 0 {
child := rest[:i]
- // a level directory (0000 to 0009, litestream's layout) directly
- // under the host is the legacy location; anything else is an
- // incarnation location (claim rev 6 §6.2a)
- if !isLevelDir(child) {
+ // an incarnation id as a child prefix (the claim's 64 hex
+ // characters; the model's decimal ids) is an incarnation location.
+ // A level directory (0000 to 0009, litestream's layout) or a
+ // generation prefix (g-<op>/) directly under the host belongs to
+ // the legacy location, the host prefix itself (claim rev 6.4 §6.2a)
+ digits := len(child) > 0 && !isLevelDir(child)
+ for _, r := range child {
+ if r < '0' || r > '9' {
+ digits = false
+ }
+ }
+ if digits {
return p + child + "/"
}
}
@@ -1419,7 +1429,7 @@ func locationOf(k string) string {
// deadGeneration: within holder.location, everything but the live set.
// The live set is {authoritative region, holder.location,
-// holder.generation} (claim rev 6.3 §6.2a, R4-3): the generation's
+// holder.generation} (claim rev 6.4 §6.2a, R4-3): the generation's
// objects are live only in the authoritative region's bucket, the region
// of the holder's current installation; a matching key in any other
// region is residue. The mutation of live-set-region exempts matching keys
diff --git a/internal/sim/hostlife/protocol_test.go b/internal/sim/hostlife/protocol_test.go
index daefde62f..e156fd0cf 100644
--- a/internal/sim/hostlife/protocol_test.go
+++ b/internal/sim/hostlife/protocol_test.go
@@ -23,7 +23,7 @@ func (w *world) readClaim(b *box) (*claim, bool) {
}
c := w.claim.clone()
if c != nil && c.Holder != nil {
- c.readStamp = b.stampOf(c.Holder.Inc) // the installation stamp this read carries (claim rev 6.3 §6.4)
+ c.readStamp = b.stampOf(c.Holder.Inc) // the installation stamp this read carries (claim rev 6.4 §6.4)
}
b.snap = c.clone()
return c, true
@@ -290,7 +290,7 @@ func (w *world) restore(b *box, inc int, loc, gen string) *localDB {
return db
}
-// stampOK is admission's local check (claim rev 6.3 §6.4): the worker's
+// stampOK is admission's local check (claim rev 6.4 §6.4): the worker's
// read of the claim carries the installation stamp active on this box at
// that read; it is admitted only if that is still the active stamp. A
// landing probe refused here by its own installation is the fault R3-3
@@ -444,7 +444,7 @@ func (w *world) admits(b *box, c *claim, probe string) bool {
return false
}
if !w.stampOK(b, c, probe) {
- return false // a retired installation's worker (claim rev 6.3 §6.4)
+ return false // a retired installation's worker (claim rev 6.4 §6.4)
}
if w.isParticipant(c, b) && b.ExecUntil > 0 && w.localNow(b) > b.ExecUntil {
return false // self-fenced until the lease is renewed
@@ -591,13 +591,13 @@ func (w *world) parkDepth(b *box, prefix, dest string, quiet int, keep func(stri
// guard: a retired marker first, then each artifact renamed aside, one per
// call, so a crash can fall between any two (claim §6.3). It reports
// whether nothing is left.
-// localPark is claim rev 6.3's retire-installation (§6.3): the active
+// localPark is claim rev 6.4's retire-installation (§6.3): the active
// stamp moves into retired_stamps and becomes none, then every artifact is
// parked. It serves a delete, a move away and the quarantine; a landing
// parks its predecessor's leftovers with retire-artifacts instead.
func (w *world) localPark(b *box, inc int) bool {
if on("hostguard") && b.stampOf(inc) != 0 {
- b.retireStamp(inc) // retire-installation: active moves into retired_stamps (claim rev 6.3 §6.4)
+ b.retireStamp(inc) // retire-installation: active moves into retired_stamps (claim rev 6.4 §6.4)
return false
}
switch {
diff --git a/internal/sim/hostlife/rules_test.go b/internal/sim/hostlife/rules_test.go
index 8fc006790..384b6772f 100644
--- a/internal/sim/hostlife/rules_test.go
+++ b/internal/sim/hostlife/rules_test.go
@@ -54,8 +54,8 @@ var allRules = []string{
"park-skip-lock", // §6.2 step 2/J-N6
"retirement-ids", // §6.2/J-D5/J-N7
"hostguard", // §6.4/J-N8
- "fence-by-installation", // claim rev 6.3 §6.4 (R3-3): a new installation of a retired incarnation sets its own active stamp; the mutation is rev 5.2's marker, retired for good
- "fence-stamp", // claim rev 6.3 §6.4 (R4-2): a writer is admitted only if its stamp equals the active stamp; the mutation admits any writer for the incarnation while one is active
+ "fence-by-installation", // claim rev 6.4 §6.4 (R3-3): a new installation of a retired incarnation sets its own active stamp; the mutation is rev 5.2's marker, retired for good
+ "fence-stamp", // claim rev 6.4 §6.4 (R4-2): a writer is admitted only if its stamp equals the active stamp; the mutation admits any writer for the incarnation while one is active
"purge-reserve-first", // §5.3/J-N9
"purge-adopts", // this model's finding: purge claims unclaimed legacy records first
"purge-journal", // this model's finding: the reservation is the purge's journal
@@ -88,7 +88,7 @@ var allRules = []string{
"straggler-sweep", // claim rev 5 §6.2a (CC15): the at-rest sweep
"sweep-staging", // CC20: the sweep also covers moves/<host>/
"sweep-namespace", // claim rev 6 §6.2a (R3-4): the sweep discovers locations from the namespace; the mutation reads the capped retired_incarnations
- "live-set-region", // claim rev 6.3 §6.2a (R4-3): the live set is {authoritative region, location, generation}; the mutation exempts matching keys in every region
+ "live-set-region", // claim rev 6.4 §6.2a (R4-3): the live set is {authoritative region, location, generation}; the mutation exempts matching keys in every region
// moves spec
"seal-by-version", // J-N2: landing copies sealed versions
"staging-quiet", // J-N2: staging retired after its writers' horizon
diff --git a/internal/sim/hostlife/sim_test.go b/internal/sim/hostlife/sim_test.go
index a544e3019..bf9b191ac 100644
--- a/internal/sim/hostlife/sim_test.go
+++ b/internal/sim/hostlife/sim_test.go
@@ -48,7 +48,7 @@ func newWorld(seed int64, verbose bool) *world {
}
// retentionStart: the host has been recreated more times than
-// retired_incarnations remembers (claim rev 6.3 §2 caps it at 32), and a
+// retired_incarnations remembers (claim rev 6.4 §2 caps it at 32), and a
// replication object of an incarnation the list forgot is still in a
// bucket (rev 7.5, R4 §3: the retention-boundary fixture). Only a sweep
// that discovers locations from the namespace parks it.
diff --git a/internal/sim/hostlife/witness_test.go b/internal/sim/hostlife/witness_test.go
index f90054a65..279702341 100644
--- a/internal/sim/hostlife/witness_test.go
+++ b/internal/sim/hostlife/witness_test.go
@@ -1010,7 +1010,7 @@ func (w *world) roundTrip(t *testing.T, uk, us *box) {
}, w.act(uk, us), w.recoveryPass, w.operator)
}
-// wStaleWriter (claim rev 6.3 §6.4, R4-2): a config-sync pass selects the
+// wStaleWriter (claim rev 6.4 §6.4, R4-2): a config-sync pass selects the
// row under the first installation and pauses; the host moves away and
// back; a new bundle is published; the pass takes the guard.
func wStaleWriter(t *testing.T, w *world) {
@@ -1032,7 +1032,7 @@ func wStaleWriter(t *testing.T, w *world) {
w.finish(t)
}
-// wLiveSetRegion (claim rev 6.3 §6.2a, R4-3): a legacy host serves in UK
+// wLiveSetRegion (claim rev 6.4 §6.2a, R4-3): a legacy host serves in UK
// (box 0) with a pre-release replica in US (box 1). Adoption's discovery
// parks US. A replication PUT the old US executor issued before the
// retirement completes after it.
diff --git a/internal/sim/hostlife/world_test.go b/internal/sim/hostlife/world_test.go
index 023196568..ea559bddd 100644
--- a/internal/sim/hostlife/world_test.go
+++ b/internal/sim/hostlife/world_test.go
@@ -1,6 +1,6 @@
// Package hostlife is a deterministic, randomized model of the host
// lifecycle claim (docs/superpowers/specs/2026-10-02-host-lifecycle-claim-design.md,
-// rev 6.3, 8faa89a4) and Phase 1 of move-any-app
+// rev 6.4, 697f0f9f) and Phase 1 of move-any-app
// (docs/superpowers/specs/2026-10-02-move-any-app-design.md, rev 7.6).
//
// Since joint round 2 the model encodes what the specs say and nothing
@@ -225,6 +225,7 @@ type request struct {
ifVer int
hold bool // the biased schedule holds it until its worst moment (rev 7.5: a generation marker)
by string
+ done bool // completed (or refused) on the server; the client may have given up long before
// result, for an immediate completion
resultVer int
}
@@ -237,7 +238,7 @@ type filesDir struct {
Gen string // .files-gen marker
Env int // admin token identity in <host>.env
Done bool // markers written last
- Stamp int // the installation stamp that produced it (claim rev 6.3 §6.3)
+ Stamp int // the installation stamp that produced it (claim rev 6.4 §6.3)
}
func (f *filesDir) clone() *filesDir {
@@ -257,9 +258,15 @@ type localDB struct {
TX int // the lineage's transaction id: the oracle's order of acknowledged writes
Pos int // the replication position: litestream's TXID, the count of files shipped, which every LTX range is numbered in (R5-1)
Pending []int // writes since Pos (what the next LTX file ships)
- Log []int // every write applied here, for the oracle
- Stamp int // the installation stamp that produced this artifact (claim rev 6.3 §6.3)
- Seq int
+ // the local level-0 files not yet confirmed uploaded, oldest first:
+ // litestream writes each sync's file locally and the replica uploads
+ // them in TXID order, never past one that has not completed (rev 7.6)
+ Queue []*blob
+ InFlight *request // the head's upload, as the client sees it
+ InFlightAt int
+ Log []int // every write applied here, for the oracle
+ Stamp int // the installation stamp that produced this artifact (claim rev 6.4 §6.3)
+ Seq int
}
func (d *localDB) clone() *localDB {
@@ -269,6 +276,7 @@ func (d *localDB) clone() *localDB {
c := *d
c.State = cloneState(d.State)
c.Pending = append([]int(nil), d.Pending...)
+ c.Queue = append([]*blob(nil), d.Queue...)
c.Log = append([]int(nil), d.Log...)
return &c
}
@@ -317,7 +325,7 @@ type box struct {
History []int // delivered history (disposable)
Attempt int // runner bookkeeping
Procs []*proc
- // Stamps is the local record per host incarnation (claim rev 6.3 §6.4,
+ // Stamps is the local record per host incarnation (claim rev 6.4 §6.4,
// R4-2): {active_stamp | none, retired_stamps}. A stamp is the claim
// gen captured at the CAS that made an installation current here. A
// writer is admitted only if its stamp equals the active one.
@@ -349,7 +357,7 @@ type plan struct {
Ver int // the config bundle version it selected
At int // it takes the guard no earlier than this
// Stamp is the installation stamp the pass read when it selected the
- // row (claim rev 6.3 §6.4, R2-3): the host guard admits the write only
+ // row (claim rev 6.4 §6.4, R2-3): the host guard admits the write only
// if it is still the active stamp.
Stamp int
}
@@ -369,11 +377,13 @@ func (b *box) stampOf(inc int) int {
return 0
}
-// install sets the active stamp for inc to holder.stamp as the installing
-// CAS wrote it (claim rev 6.3 §6.4: a create's serving CAS, a landing's
-// flip, an adoption, a reinstall, a dirty rollback's install, a push
-// commit), or as initRecord read it fresh under the guard. Under the
-// guard.
+// install is activation (claim rev 6.4 §6.4): the box's record for inc
+// takes holder.stamp as the installing CAS wrote it (a create's serving
+// CAS, a landing's flip, an adoption, a reinstall, a dirty rollback's
+// probe CAS, a push's commit), or as initRecord read it fresh under the
+// guard. Activation is one of the two moves of a stamp: the old active
+// stamp, if any, goes into retired_stamps first, then the new one is set.
+// Under the guard.
func (b *box) install(inc, gen int) {
if b.Stamps == nil {
b.Stamps = map[int]*stamps{}
@@ -381,29 +391,27 @@ func (b *box) install(inc, gen int) {
if b.Stamps[inc] == nil {
b.Stamps[inc] = &stamps{}
}
- b.Stamps[inc].Active = gen
+ s := b.Stamps[inc]
+ if s.Active != 0 && s.Active != gen && !contains(s.Retired, s.Active) {
+ s.Retired = append(s.Retired, s.Active)
+ }
+ s.Active = gen
}
-// retireArtifacts is claim rev 6.3's retire-artifacts(S): the artifacts
-// tagged S are parked (the caller does that) and S joins retired_stamps;
-// active_stamp is untouched. A landing uses it on its predecessor's
-// leftovers.
+// retireArtifacts is claim rev 6.4's retire-artifacts(S): the artifacts
+// tagged S are parked (the caller does that) and the record is untouched:
+// neither active_stamp nor retired_stamps moves. Only activation and
+// retire-installation move stamps. A landing uses it on its predecessor's
+// leftovers; a dirty rollback parks its pre-move database with S still
+// active, and S retires when the probe CAS's new stamp activates. The
+// helper exists so that every call names which retirement it is (claim
+// rev 6.4 §6.4: nothing calls retire-installation on a predecessor).
func (b *box) retireArtifacts(inc, stamp int) {
- if stamp == 0 {
- return
- }
- if b.Stamps == nil {
- b.Stamps = map[int]*stamps{}
- }
- if b.Stamps[inc] == nil {
- b.Stamps[inc] = &stamps{}
- }
- if !contains(b.Stamps[inc].Retired, stamp) && stamp != b.Stamps[inc].Active {
- b.Stamps[inc].Retired = append(b.Stamps[inc].Retired, stamp)
- }
+ _ = inc
+ _ = stamp
}
-// retireStamp is claim rev 6.3's retire-installation: the active stamp
+// retireStamp is claim rev 6.4's retire-installation: the active stamp
// moves into retired_stamps and becomes none; the caller parks every
// artifact. Used at a delete, a move away, and the quarantine.
func (b *box) retireStamp(inc int) {
@@ -419,7 +427,7 @@ func (b *box) retireStamp(inc int) {
b.Stamps[inc].Active = 0
}
-// admitsStamp is the local record's admission (claim rev 6.3 §6.4): the
+// admitsStamp is the local record's admission (claim rev 6.4 §6.4): the
// writer's stamp must equal the active stamp; none active refuses all,
// and so does no record at all (R5-4: a writer is never admitted before
// initialization). The mutation of fence-stamp admits any writer for the
@@ -450,6 +458,9 @@ func (b *box) forget() {
b.mem = map[string]bool{}
b.evidAt = map[string]int{}
b.pendingTimers = nil
+ if b.DB != nil {
+ b.DB.InFlight = nil // the upload's client is gone; the restarted replica uploads the same file again
+ }
}
// ---------- control plane ----------
@@ -467,7 +478,7 @@ type holder struct {
// current (moves rev 6 §5.1): "" is origin; a move, a push and a dirty
// rollback each install a generation named by their op id.
Gen string
- // Stamp is holder.stamp (claim rev 6.3 §2, R5-4): the gen produced by
+ // Stamp is holder.stamp (claim rev 6.4 §2, R5-4): the gen produced by
// the CAS that made the current installation current, written by that
// same CAS: a create's serving CAS, adoption, a flip, a reinstall, a
// dirty rollback's install, a push's commit. The claim is where a box
@@ -530,7 +541,7 @@ type opState struct {
BaseKey string
BaseTX int
RecVer int // a delete's or purge's intent: the record version it will DeleteIfMatch (claim §5.2 step 1, §5.3 step 4)
- FlipGen int // the holder.stamp the flip CAS wrote: the landing's installation stamp (claim rev 6.3 §6.4)
+ FlipGen int // the holder.stamp the flip CAS wrote: the landing's installation stamp (claim rev 6.4 §6.4)
Committed bool
Opened bool
ParkLocalNow bool
@@ -581,7 +592,7 @@ type claim struct {
Cancelled []cancelledCreate
// readStamp is model bookkeeping on a box's read of the claim: the
// installation stamp active on that box at the read, which a worker
- // carries with the claim it validated under (claim rev 6.3 §6.4)
+ // carries with the claim it validated under (claim rev 6.4 §6.4)
readStamp int
}
@@ -737,7 +748,7 @@ func (w *world) newVer() int {
}
// nextGen is the gen the claim CAS in progress will carry: what a writer
-// captures as its installation stamp (claim rev 6.3 §6.4).
+// captures as its installation stamp (claim rev 6.4 §6.4).
func (w *world) nextGen() int {
if w.claim == nil {
return 1
@@ -889,6 +900,7 @@ func (w *world) issue(actorBox int, r *request) int {
}
func (w *world) complete(r *request) int {
+ r.done = true
bk := w.buckets[r.region]
switch r.kind {
case rPut: