carlos / platform Public

sim: hostlife on claim rev 5, with a biased delete-and-recreate schedule

Claim rev 5 (4a458a9c) re-encoded: the at-rest sweep as claim §6.2a
states it, under a park-sweep obligation the sweeping box owns (CC15);
registrations renewed by their console and drained only when expired and
unclaimed (CC16); orphan records at any time, removed by the pass or by a
create that finds its key occupied (CC17); holder.generation (CC18);
signatures taken at the deadline check, with an SDK retry that would
re-sign (CC19).

Two rules the strict model needed beyond rev 5: sweep-staging (CC20: the
sweep must also cover moves/<host>/) and registration-check (CC21: a
create checks its registration right before its claim CAS).

The biased schedule (half the seeds): a host is recreated at once where it
last served; the old executor's last replication PUT completes at its
worst moment, after the recreate; door listings stall until the location's
object belongs to another incarnation. location-per-incarnation (C3a) is
now caught alone, and both door rules with it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev d5a6c9b170fb3da05fe39f189a2f941e5ffba4ff parent 7ac666f
8 files changed, +241 −106
  • internal/sim/hostlife/background_test.go +3 −0
  • internal/sim/hostlife/console_test.go +87 −25
  • internal/sim/hostlife/expected-outcomes.txt +2 −3
  • internal/sim/hostlife/moves_test.go +72 −38
  • internal/sim/hostlife/protocol_test.go +9 −1
  • internal/sim/hostlife/rules_test.go +4 −1
  • internal/sim/hostlife/sim_test.go +3 −2
  • internal/sim/hostlife/world_test.go +61 −36
diff --git a/internal/sim/hostlife/background_test.go b/internal/sim/hostlife/background_test.go
index e3c28c213..40fed13a1 100644
--- a/internal/sim/hostlife/background_test.go
+++ b/internal/sim/hostlife/background_test.go
@@ -306,6 +306,9 @@ func (w *world) fault() {
if len(w.pending) > 0 && w.rng.Intn(3) == 0 {
i := w.rng.Intn(len(w.pending))
r := w.pending[i]
+ if r.beyond && w.fail["recreate"] {
+ return // the biased schedule keeps it for its worst moment
+ }
w.pending = append(w.pending[:i:i], w.pending[i+1:]...)
w.complete(r)
return
diff --git a/internal/sim/hostlife/console_test.go b/internal/sim/hostlife/console_test.go
index 1a403c781..5a37078bc 100644
--- a/internal/sim/hostlife/console_test.go
+++ b/internal/sim/hostlife/console_test.go
@@ -28,7 +28,8 @@ type doorPlan struct {
Key string
Ver int
Signed bool
- Until int // a stalled step may resume until then (one in four stall long)
+ Adv bool // the biased schedule stalls this one until its worst moment
+ Until int // a stalled step may resume until then (one in four stall long)
}
func (w *world) execBox(c *claim) *box {
@@ -114,6 +115,11 @@ func (w *world) createSteps(p *consolePlan) bool {
delete(ad.Reg, p.OpID)
return true
}
+ if e, ok := ad.Reg[p.OpID]; ok && p.Step > 0 {
+ // the creating console renews its registration lease (CC16)
+ e.Until = w.now + 60
+ ad.Reg[p.OpID] = e
+ }
switch p.Step {
case 0:
// app admission (claim rev 4 §5.1 step 0): register while open.
@@ -132,8 +138,8 @@ func (w *world) createSteps(p *consolePlan) bool {
p.Step = 1
return false
case 1:
- if e, ok := ad.Reg[p.OpID]; on("admission-registration") && on("registration-recovery") && (!ok || w.now > e.Until-maxPause) {
- return abandon() // the registration lease ended before the claim CAS
+ if _, ok := ad.Reg[p.OpID]; on("admission-registration") && on("registration-check") && !ok {
+ return abandon() // CC21: the registration is gone; the claim CAS must not follow
}
if p.Host == "h2" {
if w.h2.Kind != "" {
@@ -145,7 +151,21 @@ func (w *world) createSteps(p *consolePlan) bool {
return false
}
c := w.claim
- if c != nil && (c.Holder != nil || (c.Op.Kind != "" && c.Op.Kind != "serving")) || w.rec != nil || w.recPend != nil {
+ if c != nil && (c.Holder != nil || (c.Op.Kind != "" && c.Op.Kind != "serving")) || w.recPend != nil {
+ return abandon()
+ }
+ if c != nil && len(c.Obl) > 0 {
+ return false // a pending chore, such as a sweep, finishes first (CC11)
+ }
+ if w.rec != nil {
+ // the record key is occupied: an orphan is removed with
+ // DeleteIfMatch and the create retries (CC17); anything else
+ // refuses the create
+ if c != nil && w.orphan(c) && on("cancelled-creates") {
+ w.logf("create %s: removing an orphan record inc %d", p.OpID, w.rec.Inc)
+ w.rec = nil
+ return false
+ }
return abandon()
}
b := w.regionBox(p.Region)
@@ -245,6 +265,19 @@ func (w *world) userDelete() {
return
}
id := w.newID("del")
+ if e := w.execBox(c); e != nil {
+ w.lastRegion = e.Region
+ }
+ if w.fail["recreate"] && c.Op.Kind == "serving" {
+ w.doorAuthorize() // a door listing that will race the delete and a recreate
+ if e := w.execBox(c); e != nil && e.DB != nil && hasMain(e) && w.rng.Intn(2) == 0 {
+ // the replicator's last PUT before the delete's stop evidence:
+ // accepted, then completing after the host is recreated
+ bl := &blob{Kind: "ltx", Inc: e.DB.Inc, Gen: e.DB.Gen, Writes: append([]int(nil), e.DB.Writes...), SealKey: e.DB.Key}
+ w.pending = append(w.pending, &request{kind: rPut, region: e.Region, key: dataKey(e.DB.Loc, e.DB.Gen, "ltx"), b: bl,
+ doneAt: w.now + 40*horizonH, beyond: true, by: fmt.Sprintf("box:%d", e.ID)})
+ }
+ }
if w.cas(c, func(n *claim) bool {
n.Op = opState{Kind: "deleting", ID: id, Exec: -1, ReqAt: w.now}
return true
@@ -438,6 +471,12 @@ func (w *world) free(c *claim) {
w.violate("INV-10: the host was freed with residue %v", r)
}
w.stats["freed"]++
+ if w.fail["recreate"] && w.rng.Intn(2) == 0 && !w.calm {
+ // the biased schedule: recreate the host at once, where it last
+ // served, while the old incarnation's requests are in flight
+ w.nextInc++
+ w.console = append(w.console, &consolePlan{Kind: "create", Host: "h", App: app, OpID: w.newID("cr"), Inc: w.nextInc, Region: w.lastRegion})
+ }
if w.purgeRes[app] && !on("purge-journal") {
w.purgeRes[app] = false // freeing the host drops the reservation
}
@@ -596,8 +635,15 @@ func (w *world) doorAuthorize() {
if b == nil {
return
}
- w.doorReads = append(w.doorReads, &doorPlan{Region: b.Region, Loc: c.Holder.Location, Gen: c.Holder.Gen, Inc: c.Holder.Inc, OpID: c.Op.ID,
- Key: dataKey(c.Holder.Location, c.Holder.Gen, "ltx"), Until: w.now + 1 + w.pauseLen()})
+ d := &doorPlan{Region: b.Region, Loc: c.Holder.Location, Gen: c.Holder.Gen, Inc: c.Holder.Inc, OpID: c.Op.ID,
+ Key: dataKey(c.Holder.Location, c.Holder.Gen, "ltx"), Until: w.now + 1 + w.pauseLen()}
+ if w.fail["recreate"] && w.rng.Intn(2) == 0 {
+ d.Adv = true
+ if w.rng.Intn(2) == 0 {
+ d.Until = w.now + 40*horizonH // stalls before selecting
+ }
+ }
+ w.doorReads = append(w.doorReads, d)
}
// doorOpen advances one stalled listing or file request. A stalled step
@@ -610,7 +656,9 @@ func (w *world) doorOpen() {
i := w.rng.Intn(len(w.doorReads))
worst := -1
for j, d := range w.doorReads {
- if c := w.claim; c == nil || c.Holder == nil || c.Holder.Inc != d.Inc {
+ // its worst moment: the object its key names now belongs to
+ // another incarnation
+ if cur := w.buckets[d.Region].current(d.Key); cur != nil && !cur.Marker && cur.B.Inc != 0 && cur.B.Inc != d.Inc {
worst = j
break
}
@@ -639,6 +687,9 @@ func (w *world) doorOpen() {
return
}
d.Signed, d.Until = true, w.now+1+w.pauseLen()
+ if d.Adv {
+ d.Until = w.now + 40*horizonH // stalls between signing and the open
+ }
return
}
w.doorReads = append(w.doorReads[:i:i], w.doorReads[i+1:]...)
@@ -677,24 +728,10 @@ func (w *world) recoveryPass() {
// a late record PUT from a cancelled create: an orphan, never admitted
// (C1); DeleteIfMatch when cancelled_creates names its incarnation
// (claim rev 4 §5.1, J2-R2-7)
- if w.rec != nil && (c.Holder == nil || c.Holder.Inc != w.rec.Inc) && c.Op.Kind != "creating" && on("cancelled-creates") {
- orphan := false
- for _, x := range c.Cancelled {
- if x.Inc == w.rec.Inc && w.now <= x.Until {
- orphan = true
- }
- }
- // CC17: a record PUT has no completion bound either (C3a's
- // lesson), so `until` cannot close the window. A record whose
- // incarnation is retired is an orphan whenever it lands.
- if on("orphan-no-deadline") && contains(c.Retired, w.rec.Inc) {
- orphan = true
- }
- if orphan {
- w.logf("recovery: removing a cancelled create's late record inc %d", w.rec.Inc)
- w.rec = nil
- return
- }
+ if w.rec != nil && on("cancelled-creates") && w.orphan(c) {
+ w.logf("recovery: removing an orphan record inc %d", w.rec.Inc)
+ w.rec = nil
+ return
}
switch c.Op.Kind {
case "creating":
@@ -757,6 +794,31 @@ func (w *world) recoveryPass() {
}
}
+// orphan: claim rev 5 §5.1 (CC17). A record whose incarnation is retired,
+// or is not the claim's holder, is an orphan at any time; rev 4's
+// cancelled_creates window is the mutation.
+func (w *world) orphan(c *claim) bool {
+ r := w.rec
+ if r == nil || c == nil {
+ return false
+ }
+ if c.Holder != nil && c.Holder.Inc == r.Inc {
+ return false
+ }
+ if c.Op.Kind == "creating" && c.Op.NewInc == r.Inc {
+ return false
+ }
+ if !on("orphan-no-deadline") {
+ for _, x := range c.Cancelled {
+ if x.Inc == r.Inc && w.now <= x.Until {
+ return true
+ }
+ }
+ return false
+ }
+ return true
+}
+
// ---------- the operator (calm phase only) ----------
func (w *world) operator() {
diff --git a/internal/sim/hostlife/expected-outcomes.txt b/internal/sim/hostlife/expected-outcomes.txt
index 0b01f17df..cfed5cf08 100644
--- a/internal/sim/hostlife/expected-outcomes.txt
+++ b/internal/sim/hostlife/expected-outcomes.txt
@@ -15,6 +15,5 @@ start-check-claim-gen missed one of three admission fences; caught with exec-adm
start-check missed caught with exec-admission and hold-op-open
strict-spawn missed caught with evidence-whole-tree
touched-incomplete missed caught with legacy-discovery
-location-per-incarnation missed unexercised: guards completions later than the model's delete-to-recreate window
-door-by-version missed unexercised: subsumed by C3a, and not caught with it dropped either
-door-reread missed unexercised: subsumed by C3a, and not caught with it dropped either
+door-reread missed subsumed by C3a: caught with location-per-incarnation
+door-by-version missed subsumed by C3a: caught with door-reread and location-per-incarnation
diff --git a/internal/sim/hostlife/moves_test.go b/internal/sim/hostlife/moves_test.go
index 48e305ea5..c127debfa 100644
--- a/internal/sim/hostlife/moves_test.go
+++ b/internal/sim/hostlife/moves_test.go
@@ -462,6 +462,7 @@ type stalledSign struct {
until int
checked bool // passed its SafeUntil check and signed, not yet sent
checkedAt int
+ retry bool // a retry of a send that failed transiently
}
// runStalled lets paused goroutines resume: deferred registry writes and
@@ -483,10 +484,18 @@ func (w *world) runStalled() {
// timestamp is the check's, and S3 refuses it once older than
// the signature-age limit (model 18); signed at send time, it
// is accepted however late
- if on("sign-at-check") && w.now > s.checkedAt+sigAge {
+ resigned := s.retry && !on("sdk-no-resign") // the SDK's retry refreshed the signature
+ if on("sign-at-check") && !resigned && w.now > s.checkedAt+sigAge {
w.stats["stale signature refused"]++
continue
}
+ if !s.retry && w.rng.Intn(3) == 0 {
+ // a transient error: the SDK retries the same request later,
+ // after a pause a frozen box can stretch (CC19)
+ s.retry, s.signAt = true, w.now+1+w.pauseLen()
+ keep = append(keep, s)
+ continue
+ }
w.issue(b.ID, s.req)
continue
}
@@ -1189,57 +1198,82 @@ func sameStep(a, b *claim) bool {
x.Opened == y.Opened && len(a.Obl) == len(b.Obl) && (a.Holder == nil) == (b.Holder == nil)
}
-// sweep is the straggler sweep (moves rev 6 §6.4, CC15): a request accepted
-// before S3's signature limit can complete after it (claim C3a), so a
-// retirement's horizon listing can miss it. While the host is at rest (the
-// claim free, or serving with no operation and no obligation), each region's
-// box lists the host namespace in its own bucket and parks, as a retirement
-// event of its own, everything outside the live set: the claim's location
-// and current generation in the executor's region, and the parkers' locks.
-// It re-reads the claim on every visit and abandons the event the moment
-// the host is no longer at rest; every delete names a snapshot version, so
-// nothing written later is touched.
+// sweep is claim rev 5's at-rest sweep (§6.2a, CC15): for a host with no
+// operation running, each region's box lists the locations of the host's
+// retired incarnations and every generation within holder.location other
+// than holder.generation, and parks whatever it finds as a further
+// retirement event, under a park-sweep obligation it owns. The claim's
+// wording leaves out `moves/<host>/`; `sweep-staging` (CC20) adds it.
func (w *world) sweep(b *box, c *claim) {
- if !on("straggler-sweep") {
- return
- }
- rest := c == nil || c.Holder == nil && c.Op.Kind == "" && len(c.Obl) == 0 ||
- c.Holder != nil && c.Op.Kind == "serving" && len(c.Obl) == 0
- if !rest {
- b.sweepID = ""
+ if !on("straggler-sweep") || c == nil || !(c.Op.Kind == "" || c.Op.Kind == "serving") {
return
}
- live := map[string]bool{}
- if c != nil && c.Holder != nil && c.Op.Exec == b.ID {
- loc, gen := c.Holder.Location, c.Holder.Gen
- live[dataKey(loc, gen, "base")], live[dataKey(loc, gen, "ltx")], live[genMarkerKey(loc)] = true, true, true
+ for _, o := range c.Obl {
+ if o.Kind == "park-sweep" && o.Region == b.Region {
+ return // its own sweep is still running
+ }
}
- keep := func(k string) bool { return live[k] }
bk := w.buckets[b.Region]
- any := false
+ var prefixes []string
for _, p := range []string{"instances/" + host + "/", "moves/" + host + "/"} {
for _, k := range bk.keys(p) {
- if !strings.HasSuffix(k, "lock.json") && !live[k] && hasNonMarker(bk.objs[k]) {
- any = true
+ if hasNonMarker(bk.objs[k]) && w.sweepable(c, b.Region, k) {
+ prefixes = append(prefixes, p)
+ break
}
}
}
- if !any && b.sweepID == "" {
+ if len(prefixes) == 0 {
return
}
- if b.sweepID == "" {
- b.sweepID = w.newID("sw")
+ w.cas(c, func(n *claim) bool {
+ if !(n.Op.Kind == "" || n.Op.Kind == "serving") {
+ return false
+ }
+ for _, p := range prefixes {
+ n.Obl = append(n.Obl, obligation{ID: w.newID("ob"), Op: "sweep", Kind: "park-sweep", Region: b.Region, Box: -1, Prefix: p, Ret: "sweep"})
+ }
+ return true
+ })
+}
+
+// sweepable: is k, in region r, one of the things the at-rest sweep parks?
+func (w *world) sweepable(c *claim, r int, k string) bool {
+ if strings.HasSuffix(k, "lock.json") {
+ return false
+ }
+ if strings.HasPrefix(k, "moves/") {
+ return on("sweep-staging") // CC20
}
- dest := fmt.Sprintf("parked/sweep/%s/%s/", host, b.sweepID)
- done := true
- for _, p := range []string{"instances/" + host + "/", "moves/" + host + "/"} {
- if !w.parkStep(b, p, dest+strings.TrimSuffix(strings.TrimPrefix(p, ""), "/")+"/", 0, keep) {
- done = false
+ h := c.Holder
+ if h != nil && strings.HasPrefix(k, h.Location) {
+ // a generation within the live location other than holder.generation
+ if k == dataKey(h.Location, h.Gen, "base") || k == dataKey(h.Location, h.Gen, "ltx") {
+ return false
}
+ if k == genMarkerKey(h.Location) {
+ v := w.buckets[r].current(k)
+ e := w.execBox(c)
+ return v != nil && (v.B.Gen != h.Gen || e == nil || e.Region != r)
+ }
+ return isDataKey(k)
}
- if done {
- w.logf("box %d swept stragglers (%s)", b.ID, b.sweepID)
- w.stats["swept"]++
- b.sweepID = ""
+ for _, inc := range c.Retired {
+ if strings.HasPrefix(k, locationFor(inc, false)) {
+ return true
+ }
}
+ // a retired legacy incarnation's location is the bare namespace
+ return w.legacyRetired(c)
+}
+
+// legacyRetired: the host once held a legacy incarnation, now retired, so
+// the bare namespace outside holder.location is a retired location.
+func (w *world) legacyRetired(c *claim) bool {
+ for _, inc := range c.Retired {
+ if w.legacyIncs[inc] {
+ return true
+ }
+ }
+ return false
}
diff --git a/internal/sim/hostlife/protocol_test.go b/internal/sim/hostlife/protocol_test.go
index 620a24b5c..3ce546929 100644
--- a/internal/sim/hostlife/protocol_test.go
+++ b/internal/sim/hostlife/protocol_test.go
@@ -583,7 +583,12 @@ func (w *world) doObligation(b *box, c *claim, o obligation) {
quiet = b.evidAt[o.ID] + horizonH
}
}
- if !w.parkStep(b, o.Prefix, o.Dest(), quiet, nil) {
+ var keep func(string) bool
+ if o.Kind == "park-sweep" {
+ keep = func(k string) bool { return !w.sweepable(w.claim, b.Region, k) }
+ quiet = 0
+ }
+ if !w.parkStep(b, o.Prefix, o.Dest(), quiet, keep) {
return
}
}
@@ -610,6 +615,9 @@ func (w *world) doObligation(b *box, c *claim, o obligation) {
}
func (o obligation) Dest() string {
+ if o.Kind == "park-sweep" {
+ return fmt.Sprintf("parked/sweep/%s/%s/", host, o.ID)
+ }
if o.Kind == "park-replica" {
return fmt.Sprintf("parked/%s/%s/%s/", o.Ret, host, o.ID)
}
diff --git a/internal/sim/hostlife/rules_test.go b/internal/sim/hostlife/rules_test.go
index 07baf07d6..2cd9dbd42 100644
--- a/internal/sim/hostlife/rules_test.go
+++ b/internal/sim/hostlife/rules_test.go
@@ -29,6 +29,7 @@ var allRules = []string{
"revalidate-before-effect", // C3: a worker re-reads under its lease before acting
"sign-before-safeuntil", // C3/§3.1: a paused round signs only before its lease's SafeUntil
"sign-at-check", // model 18: signed at the check, so S3's age limit bounds any later pause
+ "sdk-no-resign", // claim rev 5 C3 (CC19): the SDK\'s retry resends the original signature
"location-per-incarnation", // claim rev 4 C3a (J2-R2-1): no location shared between incarnations
"fresh-admission", // C4: admission reads the claim fresh
"exec-admission", // C4/K1: op.exec names this box (and the role column, which is op.exec here)
@@ -47,6 +48,7 @@ var allRules = []string{
"purge-journal", // this model's finding: the reservation is the purge's journal
"admission-registration", // claim rev 4 §5.1 step 0, §5.3 step 1b (J2-R2-2)
"registration-recovery", // CC16: a registration is a lease, so a dead create cannot block purge
+ "registration-check", // CC21: a create checks its registration immediately before its claim CAS
"cancelled-creates", // claim rev 4 §5.1 (J2-R2-7): a late record PUT is removed
"orphan-no-deadline", // CC17: ... whenever it lands, not only before `until`
"door-by-version", // §6.5/J-N10
@@ -62,7 +64,8 @@ var allRules = []string{
"stale-hold-release", // this model's finding: release a hold whose op ended
"restore-by-generation", // C3a + moves rev 6 §5.1: restore replays the claim's generation only
"quiet-before-ack", // this model's finding (late writes after a park)
- "straggler-sweep", // CC15 + moves rev 6 §6.4: stragglers past H are parked at rest
+ "straggler-sweep", // claim rev 5 §6.2a (CC15): the at-rest sweep
+ "sweep-staging", // CC20: the sweep also covers moves/<host>/
// 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 0d46d5ef3..2df214dd8 100644
--- a/internal/sim/hostlife/sim_test.go
+++ b/internal/sim/hostlife/sim_test.go
@@ -17,12 +17,12 @@ const (
func newWorld(seed int64, verbose bool) *world {
w := &world{seed: seed, rng: rand.New(rand.NewSource(seed)), verbose: verbose,
stores: map[string]bool{app0: true, app1: true}, cfgVer: map[string]int{app0: 1, app1: 1}, purgeRes: map[string]bool{}, purgeStep: map[string]int{},
- control: newBucket(), delivered: map[int]bool{}, durable: map[string]map[int]bool{}, writeInc: map[int]int{}, genParent: map[string]string{}, execLease: map[int]execLease{}, fail: map[string]bool{}, stats: map[string]int{}}
+ control: newBucket(), delivered: map[int]bool{}, durable: map[string]map[int]bool{}, writeInc: map[int]int{}, legacyIncs: map[int]bool{}, genParent: map[string]string{}, execLease: map[int]execLease{}, fail: map[string]bool{}, stats: map[string]int{}}
for r := 0; r < nRegions; r++ {
w.buckets[r] = newBucket()
}
w.admit = map[string]*admission{app0: {Reg: map[string]regEntry{}}, app1: {Reg: map[string]regEntry{}}}
- for _, f := range []string{"partition", "freeze", "pause", "destroy", "disk"} {
+ for _, f := range []string{"partition", "freeze", "pause", "destroy", "disk", "recreate"} {
w.fail[f] = w.rng.Intn(2) == 0
}
for r := 0; r < nRegions; r++ {
@@ -54,6 +54,7 @@ func (w *world) legacyStart() {
w.nextInc++
inc := w.nextInc
w.rec = &record{Inc: inc, App: app0, Region: r}
+ w.legacyIncs[inc] = true
key := 7
var writes []int
for i := 0; i < 3; i++ {
diff --git a/internal/sim/hostlife/world_test.go b/internal/sim/hostlife/world_test.go
index 082999935..ff8db6b52 100644
--- a/internal/sim/hostlife/world_test.go
+++ b/internal/sim/hostlife/world_test.go
@@ -1,7 +1,7 @@
// Package hostlife is a deterministic, randomized model of the host
// lifecycle claim (docs/superpowers/specs/2026-10-02-host-lifecycle-claim-design.md,
-// rev 4, a3c86fa5) and Phase 1 of move-any-app
-// (docs/superpowers/specs/2026-10-02-move-any-app-design.md, rev 6).
+// rev 5, 4a458a9c) and Phase 1 of move-any-app
+// (docs/superpowers/specs/2026-10-02-move-any-app-design.md, rev 7).
//
// Since joint round 2 the model encodes what the specs say and nothing
// stronger: restore reads the holder's location and the generation the
@@ -296,7 +296,6 @@ type box struct {
stopped map[string]bool
mem map[string]bool
evidAt map[string]int
- sweepID string
pendingTimers []int
}
@@ -319,7 +318,6 @@ func (b *box) forget() {
b.snap, b.stopped, b.stale = nil, nil, nil
b.mem = map[string]bool{}
b.evidAt = map[string]int{}
- b.sweepID = ""
b.pendingTimers = nil
}
@@ -495,37 +493,39 @@ type world struct {
ver int
verbose bool
- claim *claim // nil = absent
- rec *record
- stores map[string]bool
- cfgVer map[string]int // each app's current config bundle version
- purgeRes map[string]bool
- purgeStep map[string]int // the reservation's durable phase (purge journal)
- admit map[string]*admission
- h2 liteHost
- recPend *pendingRec // a record PUT whose response was lost
- floor bool
- buckets [nRegions]*bucket
- control *bucket
- boxes []*box
- pending []*request
- acks []ack
- writeInc map[int]int // which incarnation acknowledged each write
- durable map[string]map[int]bool // per generation prefix: writes a completed replica object holds
- genParent map[string]string // generation prefix -> the one it superseded
- nextW int
- nextInc int
- serial int
- nextID int
- delivered map[int]bool
- doorReads []*doorPlan
- execLease map[int]execLease
- staging [nRegions]stagingLease
- console []*consolePlan
- fail map[string]bool // which faults this seed exercises
- stats map[string]int
- stalled []*stalledSign
- ccache *claim // the console's short-lived claim cache
+ claim *claim // nil = absent
+ rec *record
+ stores map[string]bool
+ cfgVer map[string]int // each app's current config bundle version
+ purgeRes map[string]bool
+ purgeStep map[string]int // the reservation's durable phase (purge journal)
+ admit map[string]*admission
+ h2 liteHost
+ recPend *pendingRec // a record PUT whose response was lost
+ lastRegion int // where the host served when it was last deleted
+ legacyIncs map[int]bool // incarnations that were adopted legacy hosts
+ floor bool
+ buckets [nRegions]*bucket
+ control *bucket
+ boxes []*box
+ pending []*request
+ acks []ack
+ writeInc map[int]int // which incarnation acknowledged each write
+ durable map[string]map[int]bool // per generation prefix: writes a completed replica object holds
+ genParent map[string]string // generation prefix -> the one it superseded
+ nextW int
+ nextInc int
+ serial int
+ nextID int
+ delivered map[int]bool
+ doorReads []*doorPlan
+ execLease map[int]execLease
+ staging [nRegions]stagingLease
+ console []*consolePlan
+ fail map[string]bool // which faults this seed exercises
+ stats map[string]int
+ stalled []*stalledSign
+ ccache *claim // the console's short-lived claim cache
violations []string
opsDone int
@@ -680,6 +680,11 @@ func (w *world) issue(actorBox int, r *request) int {
// bounds acceptance, not completion (claim C3a, J2-R2-1)
r.doneAt = w.now + horizonH + 1 + w.rng.Intn(10*horizonH)
r.beyond = true
+ if w.fail["recreate"] && w.rng.Intn(2) == 0 {
+ // the biased schedule: no bound at all, so it can outlive a
+ // delete and a recreate; it lands at its worst moment
+ r.doneAt = w.now + 40*horizonH
+ }
}
}
r.by = fmt.Sprintf("box:%d", actorBox)
@@ -757,10 +762,30 @@ func (w *world) newGen(loc, old, gen string) {
}
}
+// worstMoment: the biased schedule completes a pending write of an earlier
+// incarnation's replica data the moment another incarnation holds the host
+// with an executor in that region, inside its own window or past H, which
+// is the interleaving C3a exists for (a recreate while the previous
+// incarnation's requests are still in flight).
+func (w *world) worstMoment(r *request) bool {
+ if !w.fail["recreate"] || r.kind != rPut || r.b == nil || r.b.Inc == 0 || !isDataKey(r.key) {
+ return false
+ }
+ c := w.claim
+ if c == nil || c.Holder == nil || c.Holder.Inc == r.b.Inc {
+ return false
+ }
+ if c.Op.Kind == "creating" && c.Op.NewRegion == r.region {
+ return w.rng.Intn(2) == 0
+ }
+ e := w.execBox(c)
+ return e != nil && e.Region == r.region && w.rng.Intn(2) == 0
+}
+
func (w *world) completeDue(force bool) {
var keep []*request
for _, r := range w.pending {
- if force || r.doneAt <= w.now {
+ if force || r.doneAt <= w.now || w.worstMoment(r) {
w.complete(r)
if r.beyond {
w.stats["completed beyond H"]++