carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package hostlife
2
3import (
4 "fmt"
5 "strings"
6)
7
8// worker is one box agent's lifecycle worker: exec-lease renewal, the
9// obligations it owns, then its role in the current operation. One call
10// performs at most one protocol step.
11func (w *world) worker(b *box) { w.workerMode(b, false) }
12
13// obligationRunner is a separate goroutine from the role worker: a box
14// runs its chores and its operation role concurrently (CC11 is what keeps
15// them from meeting in one namespace).
16func (w *world) obligationRunner(b *box) { w.workerMode(b, true) }
17
18func (w *world) workerMode(b *box, obl bool) {
19 if b.Old {
20 return // a pre-floor executable knows nothing of claims
21 }
22 c, ok := w.readClaim(b)
23 if !ok {
24 if obl && w.claim != nil && len(w.claim.Obl) > 0 && w.calm {
25 w.logf("box %d: obligation run skipped, the agent cannot read the claim (down=%v paused=%v frozen=%v partitioned=%v destroyed=%v)", b.ID, b.AgentDown, b.AgentPaused, b.Frozen, b.Partitioned, b.Destroyed)
26 }
27 return
28 }
29 if obl {
30 if c == nil || (contains(c.Lost, b.ID) && on("lost-quarantine")) {
31 if c != nil && w.calm {
32 w.logf("box %d: obligation run refused, the box is declared lost", b.ID)
33 }
34 return
35 }
36 for _, o := range c.Obl {
37 if w.ownsObligation(o, b) {
38 w.doObligation(b, c, o)
39 return
40 }
41 }
42 w.sweep(b, c)
43 return
44 }
45 if b.stale != nil && w.rng.Intn(6) == 0 {
46 // a stalled worker of this box resumes with the claim it read long ago
47 old := b.stale
48 b.stale = nil
49 w.resumeStale(b, old)
50 return
51 } else if w.rng.Intn(10) == 0 {
52 b.stale = c.clone()
53 }
54 changed, pending := w.fenceStep(b, c)
55 if changed {
56 return // the regional worker's termination fence (region HA §5.9), before its role
57 }
58 // a relocation participant's fence this worker could not consume yet
59 // (a dead target with no spare before its deadline): Worker.Step
60 // returns w.replace(...) before the phase switch, so the regional
61 // transitions wait; the participant work that does not go through
62 // Worker.Step still runs (rev 8, Astra round 5)
63 b.fencePending = pending
64 w.role(b, c)
65 b.fencePending = false
66}
67
68// initRecord is claim rev 6.5 §6.4's initialization (R5-4): a box creates
69// or refreshes its local stamp record only from a fresh claim read, and
70// only when that claim names it as op.exec or as the landing participant;
71// it sets active_stamp = holder.stamp. A create's first wake, adoption on
72// the serving box, and a lost record on restart all go through here. A
73// stamp this box has retired is never made active again.
74func (w *world) initRecord(b *box, c *claim) {
75 if c == nil || c.Holder == nil || c.Holder.Stamp == 0 || c.Ver != w.claim.Ver {
76 return
77 }
78 inc := c.Holder.Inc
79 if !(c.Op.Exec == b.ID || c.Op.Kind == "landing" && c.Op.Tgt == b.ID) {
80 return
81 }
82 if b.stampOf(inc) == c.Holder.Stamp {
83 return
84 }
85 if s := b.Stamps[inc]; s != nil && contains(s.Retired, c.Holder.Stamp) {
86 return
87 }
88 w.activate(b, inc, c.Holder.Stamp, c.Holder.Gen)
89 c.readStamp = c.Holder.Stamp
90 w.logf("box %d: local record for inc %d initialized from holder.stamp %d", b.ID, inc, c.Holder.Stamp)
91}
92
93// activate is activation on box b (claim rev 6.5 §6.4): the old active
94// stamp retires, the new one is set, and the artifacts of the generation
95// the claim names, installed under the predecessor's stamp, are tagged
96// with the new one (R6-4: a dirty rollback's return database is installed
97// under S and tagged R when R activates). The oracle refuses a stamp the
98// claim issued for another box's installation (INV-6).
99func (w *world) activate(b *box, inc, stamp int, gen string) {
100 if bx, ok := w.stampBox[stamp]; ok && bx != b.ID {
101 w.violate("INV-6: box %d activates stamp %d, issued for box %d's installation (claim rev 6.5: holder.stamp names the installation op.exec should run)", b.ID, stamp, bx)
102 }
103 b.install(inc, stamp)
104 if b.DB != nil && b.DB.Inc == inc && b.DB.Gen == gen {
105 b.DB.Stamp = stamp
106 }
107 if b.Files != nil && b.Files.Inc == inc && b.Files.Gen == gen {
108 b.Files.Stamp = stamp
109 }
110}
111
112// issueStamp is the stamp an installing CAS writes into holder.stamp: the
113// gen the CAS produces, recorded as box's (claim rev 6.5 §2).
114func (w *world) issueStamp(n *claim, box int) int {
115 s := w.nextGen()
116 if w.stampBox == nil {
117 w.stampBox = map[int]int{}
118 }
119 w.stampBox[s] = box
120 return s
121}
122
123func dbStamp(d *localDB) int {
124 if d == nil {
125 return 0
126 }
127 return d.Stamp
128}
129
130func filesStamp(f *filesDir) int {
131 if f == nil {
132 return 0
133 }
134 return f.Stamp
135}
136
137// resumeStale is a stalled worker goroutine of b resuming with a claim it
138// read long ago (rev 4's actor). With claim C3's revalidation it re-reads
139// under its lease, sees the world moved, and stops; the mutation acts on
140// the old read. A witness calls this directly (witness_test.go).
141func (w *world) resumeStale(b *box, old *claim) {
142 c, ok := w.readClaim(b)
143 if !ok {
144 return
145 }
146 if on("revalidate-before-effect") && !sameStep(old, c) {
147 return
148 }
149 // the revalidation re-reads the box's own record as well: the stamp the
150 // resumed step carries is the one active now (claim rev 6.5 §6.4; seed
151 // 67671 / 100,000: a stalled probe step carried the stamp active at its
152 // read, before a cold admission's installing CAS, and the box's record
153 // refused its own probe)
154 old = old.clone()
155 old.readStamp = c.readStamp
156 w.role(b, old)
157}
158
159// role is the worker's exec-lease renewal, quarantine and operation role,
160// acting on the claim c it read.
161func (w *world) role(b *box, c *claim) {
162 w.initRecord(b, c)
163 w.finishParticipation(b, c) // a relocation controller whose participation ended (rev 4)
164 // exec lease (claim §7.1)
165 if w.isParticipant(c, b) && !contains(c.Lost, b.ID) {
166 if b.ExecUntil > 0 && w.localNow(b) > b.ExecUntil {
167 w.scopedCleanup(b, c) // the relocation controller's lease was lost (1b-next rev 14 finding 1)
168 }
169 if b.ExecUntil == 0 || b.ExecUntil-w.localNow(b) < execRenew {
170 b.ExecUntil = w.localNow(b) + execTTL
171 w.execLease[b.ID] = execLease{Op: c.Op.ID, Expires: w.now + execTTL}
172 }
173 if c.Op.Kind == "relocating" {
174 w.recordAck(b, c) // exec_ack.<box>, durable (rev 4)
175 if cur := w.claim; cur != nil && cur.Ver != c.Ver {
176 nc := cur.clone()
177 nc.readStamp = c.readStamp
178 c = nc
179 }
180 }
181 } else if c == nil || !w.isParticipant(c, b) {
182 b.ExecUntil = 0
183 }
184 if !b.Old && b.DB != nil && b.DB.Epoch != b.Epoch && len(b.Procs) > 0 {
185 // the agent restarted under a running app whose session's
186 // replicator died with the old agent (claim rev 6.10: a new agent
187 // process is a new session): the app is stopped, and the next wake
188 // is a new admission that declares what it acknowledged meanwhile
189 b.Procs = nil
190 w.stats["app stopped: its session's agent is gone"]++
191 w.logf("box %d: the agent restarted under a running app of a dead session; the app is stopped", b.ID)
192 }
193 if c == nil {
194 return
195 }
196 if contains(c.Lost, b.ID) && on("lost-quarantine") {
197 // a quarantined box retires whatever it kept before anything else
198 if b.Row != nil || b.DB != nil || b.Files != nil || len(b.Procs) > 0 {
199 if w.evidence(b, "quarantine") {
200 inc := 0
201 if b.DB != nil {
202 inc = b.DB.Inc
203 }
204 if w.localPark(b, inc) { // the quarantine retires the installation
205 b.Row = nil
206 b.owedDischarge = "" // a lost box's participation binds nothing (ruling 77)
207 }
208 }
209 }
210 return
211 }
212 if c.Holder == nil {
213 return
214 }
215 if b.open != nil {
216 // an admitted session finishing its start (rev 7.11 §7.14); the
217 // operation's own step runs once the app answered readiness
218 w.resumeStart(b, c)
219 if !hasMain(b) && !b.hostFailed {
220 return // the operation's own step runs once the app answered readiness, or once the host is marked failed
221 }
222 }
223 switch c.Op.Kind {
224 case "moving":
225 if b.ID == c.Op.Src {
226 w.moveSource(b, c)
227 } else if b.ID == c.Op.Tgt {
228 w.moveReport(b, c)
229 }
230 case "reverting":
231 if b.ID == c.Op.Src {
232 w.revertSource(b, c)
233 }
234 case "landing":
235 if b.ID == c.Op.Tgt {
236 w.landTarget(b, c)
237 } else if b.ID == c.Op.Src {
238 w.holdSourceRow(b, c)
239 }
240 case "restoring":
241 if b.ID == c.Op.Tgt {
242 w.releaseTarget(b, c)
243 } else if b.ID == c.Op.Src {
244 w.recoverSource(b, c)
245 }
246 case "pushing":
247 if b.ID == c.Op.Src {
248 w.pushStep(b, c)
249 }
250 case "relocating":
251 w.relocate(b, c)
252 case "serving":
253 w.completionPass(b, c) // a relocation completed on this box and not yet marked (ruling 80)
254 }
255}
256
257// convertToRelocating is the termination fence's conversion of a pre-flip
258// move (moves rev 7.20 §7.10; claim rev 6.28 §4): one CAS records the
259// fence (fence.<box>), sets exec none, keeps holder.stamp and
260// holder.generation, starts relocating at pinning in the source's region,
261// touches no existing obligation, and queues the staging lane's park for
262// the target region when the move had not queued it yet (as an abort
263// would). The fenced source is on lost and leaves the participants.
264func (w *world) convertToRelocating(c *claim, src *box) {
265 op := c.Op
266 if w.cas(c, func(n *claim) bool {
267 if !sameOp(n.Op.ID, op.ID) || n.Op.Kind != "moving" || n.Op.Committed {
268 return false
269 }
270 n.Op.Kind, n.Op.Phase, n.Op.Exec, n.Op.FenceBox = "relocating", "pinning", -1, src.ID
271 n.Op.RelocReason, n.Op.FenceAt, n.Op.Tgt, n.Op.Src = "failover", w.now, -1, src.ID
272 n.Op.Pin, n.Op.Prepared, n.Op.Receipt, n.Op.Release = nil, "", nil, nil
273 // the files the relocation installs: region HA §5.6's files
274 // publisher kept the source's files directory in S3 as it served
275 // (L17); this model has no publisher, so the fence takes the
276 // source's directory as published (a stand-in, moves §14.2.6)
277 if src.Files != nil {
278 n.Op.StopFiles = src.Files.clone()
279 }
280 var parts []int
281 for _, p := range n.Op.Parts {
282 if p != src.ID {
283 parts = append(parts, p)
284 }
285 }
286 n.Op.Parts = parts
287 n.Lost = addUniq(n.Lost, src.ID)
288 if c.oblFor(op.ID) == 0 {
289 n.Obl = append(n.Obl, w.scoped(w.newObl(n, "park-staging", op.TgtRegion, -1, fmt.Sprintf("moves/%s/%s/", host, op.ID), "", w.now+stagingTTL+horizonH), w.now+stagingTTL, c.Holder.Inc))
290 }
291 return true
292 }) {
293 w.stats["relocation: source fenced before the flip"]++
294 w.logf("move %s: source box %d terminated before the flip; converted to relocating at pinning in region %d", op.ID, src.ID, op.SrcRegion)
295 }
296}
297
298// relocate is the relocation's worker on a box of the operation's region
299// (region HA §5.8's phase table; claim rev 6.30 §4): every live, enrolled
300// box of the region that is not on lost may act, and each phase's CAS is
301// conditional on the operation, the phase and the ETag, so two workers
302// racing make the same CAS and one fails. The phases live in
303// regionha_test.go. A stuck operation waits for the operator.
304func (w *world) relocate(b *box, c *claim) {
305 op := c.Op
306 if b.Region != op.SrcRegion || b.Destroyed || contains(c.Lost, b.ID) || !b.Enrolled || op.Stuck != "" || w.haOff {
307 return // with region HA off no relocation pass runs, only the completion pass (ruling 82)
308 }
309 if b.fencePending && on("reloc-fence-pending-blocks") {
310 // Worker.Step returned w.replace(...) before its phase switch (rev
311 // 8, Astra round 5): no regional transition while a participant's
312 // fence is pending. A live executor's own release sequence is the
313 // participant's (1b-next Task 7), not Worker.Step's, so it runs.
314 // The mutation goes on into the phase switch, as rev 7 did
315 w.stats["relocation: phase held, a participant's fence is pending"]++
316 if (op.Phase == "releasing" || op.Phase == "probe-failed") && b.ID == op.Exec && (op.Release == nil || op.Release.Box != op.Exec) {
317 w.relocJournalRelease(b, c, true)
318 }
319 return
320 }
321 switch op.Phase {
322 case "starting":
323 w.relocStarting(b, c)
324 case "releasing":
325 w.relocReleasing(b, c)
326 case "pinning":
327 w.relocPinning(b, c)
328 case "installing":
329 w.relocInstalling(b, c)
330 case "installed":
331 w.relocInstalled(b, c)
332 case "probing":
333 w.relocProbing(b, c)
334 case "probe-failed":
335 w.relocProbeFailed(b, c)
336 case "probed":
337 w.relocSettle(b, c)
338 }
339}
340
341type execLease struct {
342 Op string
343 Expires int
344}
345
346func (w *world) leasesSeen(c *claim) bool {
347 for _, id := range c.Op.Parts {
348 l, ok := w.execLease[id]
349 if !ok || l.Op != c.Op.ID || l.Expires < w.now {
350 return false
351 }
352 }
353 return true
354}
355
356// stagingLease: the shared staging-work lease (claim C3), per destination
357// region: the target's for a move's staging, the source's for a dirty
358// rollback's return lane.
359func (w *world) takeStaging(c *claim, b *box) bool {
360 return w.takeStagingFor(c.Op.TgtRegion, c.Op.ID, b)
361}
362
363func (w *world) takeStagingFor(region int, opID string, b *box) bool {
364 l := w.staging[region]
365 if l.Op != "" && l.Op != opID && l.Until >= w.now {
366 return false
367 }
368 w.staging[region] = stagingLease{Op: opID, Box: b.ID, Until: w.now + stagingTTL}
369 return true
370}
371
372type stagingLease struct {
373 Op string
374 Box int
375 Until int
376}
377
378// snapshotBlob is a staging round: the source's final checkpoint over its
379// replica, which is what the mirror copies (§7.3). With no local database
380// (a lost disk, or never woken) it is the replica alone.
381func (w *world) snapshotBlob(b *box, c *claim) (*blob, *blob) {
382 return w.snapshotBlobOf(b, c, c.Holder.Gen)
383}
384
385func (w *world) snapshotBlobOf(b *box, c *claim, gen string) (*blob, *blob) {
386 // the sealed transfer is a verified final snapshot (rev 7.5 §5.1, R4-1):
387 // the local database after its final checkpoint, every page, TXIDs 1
388 // to its current one; with no local database (a lost disk, or never
389 // woken) the replica restored as it stands
390 var db *blob
391 if b.DB != nil {
392 // the final checkpoint writes the pending writes as the next local
393 // level-0 file and uploads what it can (litestream SyncAndWait);
394 // the snapshot is then at that position, and carries every page
395 // whether or not the upload has landed. While the admitted session's
396 // start has not opened replication there is no replicator to sync:
397 // the snapshot carries the pages as they stand and nothing is
398 // uploaded into the live generation (rev 7.11; seed 17056 / 30,000:
399 // a round's forced sync wrote the first upload's key with other
400 // bytes while the start's own PUT was still in flight)
401 if b.open == nil && (b.Old || b.DB.Epoch == b.Epoch) {
402 if len(b.DB.Pending) > 0 {
403 bl := w.deltaOf(b.DB, b.DB.Inc)
404 b.DB.Queue = append(b.DB.Queue, bl)
405 b.DB.Pos, b.DB.Pending = bl.Max, nil
406 }
407 w.ship(b)
408 }
409 db = snapshotOf(b.DB, b.DB.Inc)
410 } else if sv := w.sealVersion(c.Op.SealDB); sv != nil && c.Op.Kind == "restoring" {
411 // a target that never restored a database returns the seal it was
412 // given, as staged, pinned by version (rev 7.11 §7.8)
413 cp := *sv.B
414 db = &cp
415 } else {
416 // no local database (a lost disk): the remote as the next admission
417 // would restore it, a lagging marker read through the journal (rev
418 // 7.11; seeds 3987 and 14322 / 30,000 sealed an empty database over
419 // a generation whose marker had not landed, and the push lost the
420 // legacy host's writes)
421 var r *localDB
422 if gen == c.Holder.Gen {
423 r = w.restoreAsNext(b, c)
424 } else {
425 r = w.restoreCalc(b.Region, c.Holder.Inc, c.Holder.Location, gen)
426 }
427 if r != nil {
428 db = snapshotOf(r, c.Holder.Inc)
429 } else if !w.hasFiles(b, c.Holder.Location, gen) && !w.hasFiles(b, c.Holder.Location, c.Holder.Gen) {
430 db = &blob{Kind: "snap", Level: 9, Min: 1, Inc: c.Holder.Inc, Pages: map[string]int{}} // nothing was ever written
431 } else {
432 w.stats["seal refused: nothing to restore"]++
433 return nil, nil // the remote cannot be read: no seal
434 }
435 }
436 files := &blob{Kind: "files", Inc: c.Holder.Inc, Files: b.Files.clone(), Timers: append([]int(nil), b.Timers...)}
437 if b.Files == nil {
438 if fv := w.sealVersion(c.Op.SealFiles); fv != nil && c.Op.Kind == "restoring" {
439 cp := *fv.B
440 files = &cp
441 }
442 }
443 return db, files
444}
445
446// sealVersion is the staged seal object a journal ref names, or nil.
447func (w *world) sealVersion(r ref) *version {
448 if r.Ver == 0 {
449 return nil
450 }
451 for _, v := range w.buckets[r.Region].objs[r.Key] {
452 if v.ID == r.Ver {
453 return v
454 }
455 }
456 return nil
457}
458
459func (w *world) moveSource(b *box, c *claim) {
460 op := c.Op
461 switch op.Phase {
462 case "leases":
463 if w.rec != nil && w.rec.Inc == c.Holder.Inc && w.rec.MoveID == "" {
464 // the record half of the request, after the claim's intent (C2):
465 // a crash between is finished here or by the recovery pass
466 w.rec.MoveID, w.rec.MoveState = op.ID, ""
467 return
468 }
469 if w.leasesSeen(c) {
470 w.cas(c, func(n *claim) bool { n.Op.Phase = "preparing"; return sameOp(n.Op.ID, op.ID) })
471 }
472 case "preparing", "ready":
473 if !contains(c.Regions, op.TgtRegion) {
474 w.cas(c, func(n *claim) bool { n.Regions = addUniq(n.Regions, op.TgtRegion); return true })
475 return
476 }
477 srcReady := !on("source-readiness") || (b.Config == c.Holder.Inc && b.Live == c.Holder.App && b.LiveInc == c.Holder.Inc && b.HasKey)
478 if op.Phase == "ready" && op.ReportOK && w.now-op.ReportAt < 60 && op.Auto && srcReady {
479 // auto-cut: admission is atomic with abort eligibility
480 if w.rec == nil || w.rec.MoveID != op.ID {
481 return
482 }
483 deadline := w.now + cutBudget
484 if !on("cut-deadline") {
485 deadline = 1 << 30
486 }
487 w.cas(c, func(n *claim) bool {
488 if !sameOp(n.Op.ID, op.ID) || n.Op.CutAt != 0 || n.Op.Kind != "moving" {
489 return false
490 }
491 n.Op.CutAt = w.now
492 n.Op.CutDeadline = deadline
493 n.Op.PinVer = w.cfgVer[c.Holder.App] // move.inputs, pinned at admission
494 n.Op.Phase = "stopping"
495 return true
496 })
497 return
498 }
499 if w.now-op.ReqAt > readyWait {
500 // readiness never came: abort on its own (no human)
501 w.logf("abort %s: phase=%s report=%v@%d srcReady=%v auto=%v", op.ID, op.Phase, op.ReportOK, op.ReportAt, srcReady, op.Auto)
502 w.abortMove(c, b)
503 return
504 }
505 if !w.takeStaging(c, b) {
506 return
507 }
508 db, files := w.snapshotBlob(b, c)
509 r1 := &request{kind: rPut, region: op.TgtRegion, key: fmt.Sprintf("moves/%s/%s/db", host, op.ID), b: db}
510 r2 := &request{kind: rPut, region: op.TgtRegion, key: fmt.Sprintf("moves/%s/%s/files", host, op.ID), b: files}
511 if w.rng.Intn(3) == 0 {
512 // this round revalidated under the staging-work lease just now,
513 // and its goroutine pauses before signing (J-N2): it may sign
514 // after the final round and the seal, but only before its lease's
515 // SafeUntil (claim C3)
516 until := w.staging[op.TgtRegion].Until
517 for _, r := range []*request{r1, r2} {
518 w.stalled = append(w.stalled, &stalledSign{box: b.ID, epoch: b.Epoch, req: r, signAt: w.now + w.pauseSign(), until: until})
519 }
520 return
521 }
522 v1 := w.issue(b.ID, r1)
523 v2 := w.issue(b.ID, r2)
524 if v1 > 0 && v2 > 0 && op.Phase == "preparing" {
525 w.cas(c, func(n *claim) bool { n.Op.Phase = "ready"; return sameOp(n.Op.ID, op.ID) })
526 }
527 case "aborting":
528 if c.oblFor(op.ID) == 0 {
529 w.closeMove(c, op.Src)
530 }
531 case "stopping":
532 if b.Row != nil {
533 b.Row.Status, b.Row.HoldOp = "moving", op.ID
534 }
535 if w.now > op.CutDeadline {
536 w.startRevert(c)
537 return
538 }
539 if !w.evidence(b, op.ID) {
540 return
541 }
542 d := w.digestOf(b)
543 files := b.Files.clone()
544 repl, next := "", 0
545 if b.Row != nil {
546 repl, next = b.Row.Repl, b.Row.Next
547 }
548 w.cas(c, func(n *claim) bool {
549 if !sameOp(n.Op.ID, op.ID) || n.Op.Phase != "stopping" {
550 return false
551 }
552 n.Op.Phase, n.Op.StopAt, n.Op.StopDigest, n.Op.StopFiles = "stopped", w.now, d, files
553 n.Op.CutSession = n.Holder.Session // the session src_covered speaks for (rev 7.12)
554 n.Op.Repl, n.Op.Next = repl, next
555 n.Op.Exec = -1 // K3: the source gives up execution, citing its evidence
556 return true
557 })
558 case "stopped":
559 if w.now > op.CutDeadline || b.FailArchive {
560 w.startRevert(c)
561 return
562 }
563 if !w.takeStaging(c, b) {
564 return
565 }
566 db, files := w.snapshotBlob(b, c)
567 if db == nil {
568 w.logf("%s: the source has no database and its remote cannot be restored; reverting", op.ID)
569 w.startRevert(c)
570 return
571 }
572 if op.SealDB.Ver == 0 {
573 r := &request{kind: rPut, region: op.TgtRegion, key: fmt.Sprintf("moves/%s/%s/db", host, op.ID), b: db}
574 w.maybeStallDuplicate(b, op, r)
575 v := w.issue(b.ID, r)
576 if v > 0 {
577 w.cas(c, func(n *claim) bool {
578 n.Op.SealDB = ref{Region: op.TgtRegion, Key: fmt.Sprintf("moves/%s/%s/db", host, op.ID), Ver: v}
579 return sameOp(n.Op.ID, op.ID)
580 })
581 }
582 return
583 }
584 r := &request{kind: rPut, region: op.TgtRegion, key: fmt.Sprintf("moves/%s/%s/files", host, op.ID), b: files}
585 w.maybeStallDuplicate(b, op, r)
586 v := w.issue(b.ID, r)
587 if v > 0 {
588 // does the source's own replica cover the stopped database? The
589 // final checkpoint's upload may still be in flight or lost; the
590 // seal holds every page regardless. A clean rollback restores the
591 // source from its replica only when it does (rev 7.11, INV-15;
592 // seed 17056 / 30,000)
593 covered := b.DB == nil || b.open == nil && len(b.DB.Pending) == 0 && w.remoteMax(w.buckets[b.Region], dataKey(c.Holder.Location, c.Holder.Gen, "")) >= b.DB.Pos
594 if !covered {
595 w.stats["seal: source replica short of the stop"]++
596 }
597 w.cas(c, func(n *claim) bool {
598 if !sameOp(n.Op.ID, op.ID) {
599 return false
600 }
601 n.Op.SrcCovered = covered
602 n.Op.SealFromDB = b.DB != nil
603 n.Op.SealFiles = ref{Region: op.TgtRegion, Key: fmt.Sprintf("moves/%s/%s/files", host, op.ID), Ver: v}
604 n.Op.SealQuiet = w.now + stagingTTL + horizonH
605 if !on("staging-quiet") {
606 n.Op.SealQuiet = w.now
607 }
608 n.Op.Phase = "sealed"
609 return true
610 })
611 }
612 case "sealed":
613 // the flip, record first (§7.5): one CAS on the record, then the
614 // claim follows on a later step. A crash between leaves nobody
615 // with execution, which is safe; whoever next sees the record
616 // completes the claim (rev 6 §19 K4).
617 if w.rec != nil && w.rec.Inc == c.Holder.Inc && w.rec.MoveID == op.ID && w.rec.MoveState == "landing" {
618 w.followRecord(c)
619 return
620 }
621 if w.rec != nil && w.rec.Inc == c.Holder.Inc && w.rec.MoveID == op.ID && w.rec.MoveState == "" && op.StopAt != 0 {
622 w.rec.Region, w.rec.MoveState = op.TgtRegion, "landing"
623 w.rec.Ver = w.newVer() // every record write has a fresh ETag
624 w.logf("flip %s: record names region %d", op.ID, op.TgtRegion)
625 }
626 }
627}
628
629func (w *world) moveReport(b *box, c *claim) {
630 op := c.Op
631 if op.Phase != "preparing" && op.Phase != "ready" {
632 return
633 }
634 if w.now-op.ReportAt < 20 {
635 return
636 }
637 ok := b.HasKey || !on("readiness-key")
638 if on("readiness-quiet") && w.now < c.Quiet[op.TgtRegion] {
639 ok = false // T12: the landing would wait out quiet_after inside the cut window
640 }
641 w.cas(c, func(n *claim) bool {
642 if !sameOp(n.Op.ID, op.ID) {
643 return false
644 }
645 n.Op.ReportAt, n.Op.ReportOK = w.now, ok
646 return true
647 })
648}
649
650func (w *world) abortMove(c *claim, by *box) {
651 op := c.Op
652 w.cas(c, func(n *claim) bool {
653 if !sameOp(n.Op.ID, op.ID) || n.Op.Kind != "moving" || n.Op.CutAt != 0 {
654 return false
655 }
656 n.Op.Phase = "aborting"
657 if on("abort-target-owner") {
658 n.Obl = append(n.Obl, w.scoped(w.newObl(n, "park-staging", op.TgtRegion, -1, fmt.Sprintf("moves/%s/%s/", host, op.ID), "", w.now+stagingTTL+horizonH), w.now+stagingTTL, c.Holder.Inc))
659 }
660 return true
661 })
662 if !on("abort-target-owner") && by != nil {
663 // the source tries to retire remote staging itself
664 w.issue(by.ID, &request{kind: rDelVer, region: op.TgtRegion, key: fmt.Sprintf("moves/%s/%s/db", host, op.ID), ver: 1})
665 }
666}
667
668func (w *world) startRevert(c *claim) {
669 op := c.Op
670 w.cas(c, func(n *claim) bool {
671 if !sameOp(n.Op.ID, op.ID) || n.Op.Kind != "moving" {
672 return false
673 }
674 n.Op.Kind, n.Op.Exec = "reverting", op.Src
675 if op.StopAt == 0 {
676 n.Op.RevPhase = "evidence"
677 } else {
678 n.Op.RevPhase = "verify"
679 }
680 n.Obl = append(n.Obl, w.scoped(w.newObl(n, "park-staging", op.TgtRegion, -1, fmt.Sprintf("moves/%s/%s/", host, op.ID), "", w.now+stagingTTL+horizonH), w.now+stagingTTL, c.Holder.Inc))
681 return true
682 })
683}
684
685func (w *world) holdRow(b *box, status, holdOp string, c *claim) {
686 if b.Row == nil {
687 b.Row = &row{Inc: c.Holder.Inc, App: c.Holder.App}
688 }
689 b.Row.Status, b.Row.HoldOp = status, holdOp
690 if w.rng.Intn(25) == 0 {
691 // a paused goroutine's duplicate of this write, executed later
692 b.deferred = append(b.deferred, &localWrite{kind: "hold", status: status, holdOp: holdOp, epoch: b.Epoch, at: w.now + 1 + w.pauseLen()})
693 }
694}
695
696// openRow is the conditional open (moves §5.2).
697func (w *world) openRow(b *box, holdOp string) bool {
698 if w.rng.Intn(6) == 0 {
699 d := &localWrite{kind: "open", holdOp: holdOp, epoch: b.Epoch, at: w.now + 1 + w.pauseLen()}
700 if w.rng.Intn(2) == 0 {
701 // the adversary keeps this one paused until its worst moment,
702 // or a long stall's end
703 d.worst, d.at = true, w.now+4000
704 }
705 b.deferred = append(b.deferred, d)
706 }
707 return w.openNow(b, holdOp)
708}
709
710func (w *world) openNow(b *box, holdOp string) bool {
711 if b.Row == nil {
712 return false
713 }
714 if on("hold-op-open") && b.Row.HoldOp != holdOp {
715 return false
716 }
717 b.Row.Status, b.Row.HoldOp = "ready", ""
718 return true
719}
720
721// localWrite is a host-specific registry write whose goroutine passed its
722// revalidation and then paused: it lands later, and only the conditions
723// on the write itself (hold_op) stand between it and a newer operation.
724type localWrite struct {
725 kind string // "open" or "hold"
726 status string
727 holdOp string
728 epoch int
729 at int // the latest it lands
730 worst bool // the scheduler wakes it at the worst moment before at
731}
732
733// pauseLen is how long a paused goroutine sleeps before its registry
734// write lands: mostly short, sometimes as long as a whole operation. A
735// crash or reboot (b.Epoch) kills it first.
736func (w *world) pauseLen() int {
737 if w.rng.Intn(4) == 0 {
738 return w.rng.Intn(2000)
739 }
740 return w.rng.Intn(150)
741}
742
743// pauseSign is how long a staging goroutine pauses between revalidating
744// under the staging-work lease and signing: it may outlast the lease, and
745// then SafeUntil (claim C3) is all that stops the signature.
746func (w *world) pauseSign() int { return w.rng.Intn(2*stagingTTL + horizonH + 1) }
747
748// maybeStallDuplicate models a lost-response retry in the cut's own
749// staging round: the first attempt's goroutine paused before signing and
750// the agent retried. The paused attempt may sign after the seal.
751func (w *world) maybeStallDuplicate(b *box, op opState, r *request) {
752 w.maybeStallDuplicateFor(b, op.TgtRegion, r)
753}
754
755func (w *world) maybeStallDuplicateFor(b *box, region int, r *request) {
756 if w.rng.Intn(3) != 0 {
757 return
758 }
759 dup := *r
760 w.stalled = append(w.stalled, &stalledSign{box: b.ID, epoch: b.Epoch, req: &dup, signAt: w.now + w.pauseSign(), until: w.staging[region].Until})
761}
762
763type stalledSign struct {
764 box int
765 epoch int
766 req *request
767 signAt int
768 until int
769 checked bool // passed its SafeUntil check and signed, not yet sent
770 checkedAt int
771 retry bool // a retry of a send that failed transiently
772}
773
774// runStalled lets paused goroutines resume: deferred registry writes and
775// staging rounds that pause between revalidation and signing.
776func (w *world) runStalled() {
777 var keep []*stalledSign
778 for _, s := range w.stalled {
779 b := w.boxByID(s.box)
780 if b == nil || b.Destroyed || b.Epoch != s.epoch {
781 continue // the goroutine died with its agent
782 }
783 if s.signAt > w.now || b.Frozen || b.AgentPaused {
784 keep = append(keep, s)
785 continue
786 }
787 if s.checked {
788 // the request is sent now, after a pause that a whole-VM
789 // freeze can stretch without bound. Signed at the check, its
790 // timestamp is the check's, and S3 refuses it once older than
791 // the signature-age limit (model 18); signed at send time, it
792 // is accepted however late
793 resigned := s.retry && !on("sdk-no-resign") // the SDK's retry refreshed the signature
794 if on("sign-at-check") && !resigned && w.now > s.checkedAt+sigAge {
795 w.stats["stale signature refused"]++
796 continue
797 }
798 if !s.retry && w.rng.Intn(3) == 0 {
799 // a transient error: the SDK retries the same request later,
800 // after a pause a frozen box can stretch (CC19)
801 s.retry, s.signAt = true, w.now+1+w.pauseLen()
802 keep = append(keep, s)
803 continue
804 }
805 w.issue(b.ID, s.req)
806 continue
807 }
808 margin := maxSkew + maxPause
809 if on("sign-at-check") {
810 margin = maxSkew + sigAge // SafeUntil leaves room for the signature's whole valid life
811 }
812 if on("sign-before-safeuntil") && w.localNow(b) > s.until-margin {
813 continue // checked SafeUntil immediately before signing: too late
814 }
815 // the check passed and the request is signed; the goroutine may
816 // still pause before sending it
817 s.checked, s.checkedAt, s.signAt = true, w.now, w.now+w.rng.Intn(maxPause+1)
818 keep = append(keep, s)
819 }
820 w.stalled = keep
821 for _, b := range w.boxes {
822 if b.Destroyed || b.Frozen || b.AgentPaused || b.AgentDown {
823 continue
824 }
825 var rest []*localWrite
826 for _, d := range b.deferred {
827 if d.epoch != b.Epoch {
828 continue
829 }
830 // an adversarial wake-up: a stale open resumes while a newer
831 // operation holds the row, at its worst moment: after that
832 // operation committed and stopped re-asserting the hold
833 worst := d.worst && d.kind == "open" && b.Row != nil && b.Row.HoldOp != "" && b.Row.HoldOp != d.holdOp &&
834 w.claim != nil && w.claim.Op.Committed && w.rng.Intn(3) == 0
835 if d.at > w.now && !worst {
836 rest = append(rest, d)
837 continue
838 }
839 switch d.kind {
840 case "open":
841 w.openNow(b, d.holdOp)
842 case "hold":
843 if b.Row != nil && (!on("hold-op-open") || b.Row.HoldOp == "" || b.Row.HoldOp == d.holdOp) {
844 b.Row.Status, b.Row.HoldOp = d.status, d.holdOp
845 }
846 }
847 }
848 b.deferred = rest
849 }
850}
851
852func (w *world) revertSource(b *box, c *claim) {
853 op := c.Op
854 if !on("revert-journal") && !b.mem[op.ID+"rev"] && (op.RevPhase == "probing" || op.RevPhase == "probed") {
855 // the journal is not trusted after a restart: verify again
856 w.cas(c, func(n *claim) bool { n.Op.RevPhase = "verify"; return sameOp(n.Op.ID, op.ID) })
857 return
858 }
859 if op.RevPhase != "probed" && op.RevPhase != "opened" {
860 w.holdRow(b, "restoring", op.ID, c)
861 }
862 switch op.RevPhase {
863 case "evidence":
864 if w.evidence(b, op.ID) {
865 w.cas(c, func(n *claim) bool { n.Op.RevPhase = "probing"; return sameOp(n.Op.ID, op.ID) })
866 }
867 case "verify":
868 if !w.evidence(b, op.ID) {
869 return
870 }
871 ph := "probing"
872 if b.DB == nil {
873 // the source lost its disk since it stopped (R8-2): nothing to
874 // verify; its writer is admitted like any lost replication
875 // state, a warm continuation of the intact generation or a cold
876 // admission (claim rev 6.8 §6.4a)
877 w.logf("revert: box %d's database is gone; the probe restores it", b.ID)
878 w.stats["revert: source database lost"]++
879 } else if w.digestOf(b) != op.StopDigest {
880 ph = "stuck"
881 w.logf("revert: digest mismatch %+v vs %+v", w.digestOf(b), op.StopDigest)
882 }
883 w.cas(c, func(n *claim) bool { n.Op.RevPhase = ph; return sameOp(n.Op.ID, op.ID) })
884 case "probing":
885 b.mem[op.ID+"rev"] = true
886 if !w.admits(b, c, op.ID) {
887 return
888 }
889 if b.HasKey { // RenderHeld + MaterializeHeld before every probe
890 b.Config, b.CfgVer, b.Live, b.LiveInc = c.Holder.Inc, w.cfgVer[c.Holder.App], c.Holder.App, c.Holder.Inc
891 }
892 if hasMain(b) || w.spawn(b, c, c.Holder.Gen, true) {
893 w.probeAccepted(b, c)
894 w.cas(c, func(n *claim) bool { n.Op.RevPhase = "probed"; return sameOp(n.Op.ID, op.ID) })
895 return
896 }
897 if b.hostFailed && !b.failCounted {
898 b.failCounted = true // a host the retry policy marked failed: one failed try for the operation
899 } else if b.open != nil || b.hostFailed {
900 return // admitted, its start in progress, or a failed host awaiting the operator: not a failed try
901 }
902 w.cas(c, func(n *claim) bool {
903 n.Op.ProbeTries++
904 if n.Op.ProbeTries >= probeTries*3 {
905 n.Op.RevPhase = "stuck"
906 }
907 return sameOp(n.Op.ID, op.ID)
908 })
909 case "probed":
910 if b.open != nil {
911 return // the source's session has not opened replication yet (rev 7.11 §7.14)
912 }
913 if !hasMain(b) {
914 w.stats["probe restarted"]++ // the probe died: restarted, admitted like any other (rev 7.11)
915 w.cas(c, func(n *claim) bool { n.Op.RevPhase = "probing"; return sameOp(n.Op.ID, op.ID) })
916 return
917 }
918 if w.openRow(b, op.ID) {
919 w.cas(c, func(n *claim) bool { n.Op.RevPhase = "opened"; return sameOp(n.Op.ID, op.ID) })
920 }
921 case "opened":
922 if c.oblFor(op.ID) == 0 {
923 w.closeMove(c, op.Src)
924 }
925 }
926}
927
928func (w *world) holdSourceRow(b *box, c *claim) {
929 if b.Row != nil && !c.Op.Committed {
930 b.Row.Status, b.Row.HoldOp = "moving", c.Op.ID
931 }
932}
933
934// rollback is record first (§7.8): one CAS on the record, landing →
935// rolling_back, guarded by move id. The commit is a CAS on the same record
936// field, so exactly one of them wins. The claim follows on a later step.
937func (w *world) rollback(c *claim, why string) {
938 op := c.Op
939 r := w.rec
940 if r == nil || r.Inc != c.Holder.Inc || r.MoveID != op.ID || r.MoveState != "landing" {
941 return
942 }
943 r.MoveState, r.Region, r.RollbackID, r.Reason = "rolling_back", op.SrcRegion, w.newID("rb"), why
944 r.Ver = w.newVer()
945 w.logf("rollback %s: %s", op.ID, why)
946 w.stats["why: "+why+" @"+op.LandPhase]++
947}
948
949// followRecord completes the claim half of a record transition the claim
950// has not caught up with: the flip, the commit, a rollback. Any actor that
951// reads both may do it (rev 6 §19 K4): the CAS is fully determined by the
952// record and is conditional on the op id and the claim's prior state.
953func (w *world) followRecord(c *claim) bool {
954 r := w.rec
955 if c == nil || c.Holder == nil || r == nil || r.Inc != c.Holder.Inc || r.MoveID == "" || r.MoveID != c.Op.ID {
956 return false
957 }
958 op := c.Op
959 switch {
960 case r.MoveState == "landing" && op.Kind == "moving" && op.Phase == "sealed":
961 return w.cas(c, func(n *claim) bool {
962 if !sameOp(n.Op.ID, op.ID) || n.Op.Kind != "moving" || n.Op.Phase != "sealed" {
963 return false
964 }
965 n.Op.Kind, n.Op.Exec, n.Op.LandPhase, n.Op.FlipAt = "landing", op.Tgt, "checking", w.now
966 n.Op.SrcStamp = n.Holder.Stamp // the source installation's stamp, kept in the journal for the return (claim rev 6.5)
967 n.Op.SrcGen = n.Holder.Gen // the source's generation at the cut, which a clean rollback's return names again (rev 7.10)
968 n.Holder.Stamp = w.issueStamp(n, op.Tgt) // holder.stamp: the gen this CAS produces, the landing's installation stamp (claim rev 6.5 §2)
969 n.Op.FlipGen = n.Holder.Stamp
970 return true
971 })
972 case r.MoveState == "committed" && op.Kind == "landing" && !op.Committed:
973 return w.cas(c, func(n *claim) bool {
974 if !sameOp(n.Op.ID, op.ID) || n.Op.Kind != "landing" || n.Op.Committed {
975 return false
976 }
977 n.Op.Committed = true // holder.generation moved at the probe's cold admission (claim rev 6.9)
978 q := op.StopAt + horizonH
979 if !on("quiet-before-ack") {
980 q = 0
981 }
982 n.Obl = append(n.Obl,
983 w.scoped(w.newObl(n, "park-superseded", op.SrcRegion, -1, c.Holder.Location, "", q), op.StopAt, c.Holder.Inc),
984 w.newObl(n, "park-local", -1, op.Src, "", "", 0),
985 w.scoped(w.newObl(n, "park-staging", op.TgtRegion, -1, fmt.Sprintf("moves/%s/%s/", host, op.ID), "", op.SealQuiet), stagingBound(op.SealQuiet), c.Holder.Inc))
986 if q > n.Quiet[op.SrcRegion] {
987 n.Quiet[op.SrcRegion] = q
988 }
989 // the source region's whole location is parked: its uncovered
990 // predecessors are covered here, in the same CAS, with no chore
991 // (claim rev 6.14 §6.4a item 6, CoverRegionPredecessors)
992 w.coverRegionPredecessors(n, op.SrcRegion, "commit")
993 return true
994 })
995 case r.MoveState == "rolling_back" && op.Kind == "landing":
996 return w.cas(c, func(n *claim) bool {
997 if !sameOp(n.Op.ID, op.ID) || n.Op.Kind != "landing" || n.Op.Committed {
998 return false
999 }
1000 n.Op.Kind, n.Op.RollbackID, n.Op.SrcPhase, n.Op.Phase, n.Op.Reason = "restoring", r.RollbackID, "waiting", "releasing", r.Reason
1001 return true
1002 })
1003 }
1004 return false
1005}
1006
1007func (w *world) landTarget(b *box, c *claim) {
1008 op := c.Op
1009 if c.Op.Exec != b.ID {
1010 return
1011 }
1012 loc := c.Holder.Location
1013 g := installGen(&op) // the generation this landing prepares; the probe's cold admission makes it holder.generation (claim rev 6.9)
1014 if !op.Committed {
1015 if w.rec != nil && w.rec.MoveID == op.ID && w.rec.MoveState != "landing" {
1016 w.followRecord(c) // a commit or rollback the claim has not caught up with
1017 return
1018 }
1019 w.holdRow(b, "landing", op.ID, c)
1020 if w.now > op.CutDeadline {
1021 w.rollback(c, "cut deadline") // N15: every phase before the commit
1022 return
1023 }
1024 }
1025 switch op.LandPhase {
1026 case "checking":
1027 // the new installation sets this box's active stamp to its own
1028 // captured stamp, the claim gen of the CAS that made the landing
1029 // current, under the guard, on a fresh read showing its operation
1030 // current and this box the landing participant (claim rev 6.5
1031 // §6.4, R3-3, R4-2): a move back to a region the instance left is
1032 // admitted at its own stamp while the retired installation's
1033 // writers stay refused
1034 if fc, ok := w.readClaim(b); ok && fc.Op.ID == op.ID && fc.Op.Kind == "landing" && fc.Op.Tgt == b.ID {
1035 retiredHere := b.Stamps[c.Holder.Inc] != nil && len(b.Stamps[c.Holder.Inc].Retired) > 0
1036 if on("fence-by-installation") || !retiredHere {
1037 b.install(c.Holder.Inc, fc.Holder.Stamp)
1038 w.logf("box %d: installation stamp %d for inc %d (%s), from holder.stamp", b.ID, fc.Holder.Stamp, c.Holder.Inc, op.ID)
1039 }
1040 }
1041 w.cas(c, func(n *claim) bool {
1042 n.Boxes = addUniq(n.Boxes, b.ID)
1043 n.Op.LandPhase = "install-park"
1044 return sameOp(n.Op.ID, op.ID)
1045 })
1046 case "install-park":
1047 if !w.evidence(b, op.ID) {
1048 return
1049 }
1050 if w.now < c.Quiet[b.Region] && !b.mem[op.ID+"quietwait"] {
1051 // INV-9 (T12): an admitted cut never waits out quiet_after; the
1052 // readiness report said the region was quiet
1053 b.mem[op.ID+"quietwait"] = true
1054 w.violate("INV-9: the cut of %s waits on quiet_after[%d] until %d", op.ID, b.Region, c.Quiet[b.Region])
1055 }
1056 // an earlier stay of this incarnation, in its location here
1057 if w.parkStep(b, loc, fmt.Sprintf("superseded/%s/%s/install/", op.ID, host), c.Quiet[b.Region], w.opScope(c, loc), w.keepFilesLive(b.Region, confine(loc))) {
1058 w.cas(c, func(n *claim) bool { n.Op.LandPhase = "install-copy"; return sameOp(n.Op.ID, op.ID) })
1059 }
1060 case "install-copy":
1061 src := op.SealDB
1062 if cur := w.buckets[b.Region].current(src.Key); cur != nil && cur.ID != src.Ver {
1063 w.stats["diag: staging newer than seal at install"]++
1064 if sv := w.buckets[b.Region].byID(src.Key, src.Ver); sv != nil && cur.B.Max < sv.B.Max {
1065 w.stats["diag: ...and older content"]++
1066 }
1067 }
1068 if !on("seal-by-version") {
1069 cur := w.buckets[b.Region].current(src.Key)
1070 if cur == nil {
1071 w.rollback(c, "staging missing")
1072 return
1073 }
1074 src.Ver = cur.ID
1075 }
1076 // into this move's own generation, as its snapshot at the sealed
1077 // TXID: a copy that completes late can only ever add an object to
1078 // this generation
1079 sv := w.buckets[b.Region].byID(src.Key, src.Ver)
1080 if sv == nil {
1081 w.rollback(c, "staging missing")
1082 return
1083 }
1084 if sv.B.Max == 0 && on("valid-ltx") {
1085 // an empty source (R8-3): no LTX file can carry TXID 0, so the
1086 // generation is installed empty, its marker naming no snapshot
1087 w.cas(c, func(n *claim) bool {
1088 n.Op.LandPhase, n.Op.BaseVer, n.Op.BaseKey, n.Op.BaseTX = "published", 0, "", 0
1089 return sameOp(n.Op.ID, op.ID)
1090 })
1091 return
1092 }
1093 key := snapKey(loc, g, sv.B.Max)
1094 v := w.issue(b.ID, &request{kind: rCopy, region: b.Region, key: key, srcRegion: b.Region, srcKey: src.Key, srcVer: src.Ver})
1095 if v > 0 || !on("build-completion") {
1096 // the build journal records the snapshot's key, version and TXID
1097 // (R3-1, R4-1); the anchor follows, then completion (R7-4). The
1098 // mutation publishes from the intent, before the artifacts landed
1099 next := "install-anchor"
1100 if !on("build-completion") {
1101 next = "published"
1102 }
1103 w.cas(c, func(n *claim) bool {
1104 n.Op.LandPhase, n.Op.BaseVer, n.Op.BaseKey, n.Op.BaseTX = next, v, key, sv.B.Max
1105 return sameOp(n.Op.ID, op.ID)
1106 })
1107 }
1108 case "install-anchor":
1109 if w.anchorAcked(b, loc, g, op.BaseKey, op.BaseVer, op.BaseTX) {
1110 w.cas(c, func(n *claim) bool { n.Op.LandPhase = "published"; return sameOp(n.Op.ID, op.ID) })
1111 }
1112 case "published":
1113 // the build is complete; generation.json is published by the probe's
1114 // cold admission, after readiness and before its first upload (claim
1115 // rev 6.9 §6.4a)
1116 w.cas(c, func(n *claim) bool { n.Op.LandPhase = "local"; return sameOp(n.Op.ID, op.ID) })
1117 case "local":
1118 w.installLocal(b, c, g, op.SealFiles, op.LandPhase, func(n *claim) { n.Op.LandPhase = "config" })
1119 case "config":
1120 if !b.HasKey && on("config-before-probe") {
1121 w.rollback(c, "configuration did not render")
1122 return
1123 }
1124 if b.HasKey {
1125 b.Config, b.CfgVer = c.Holder.Inc, w.cfgVer[c.Holder.App]
1126 if b.CfgVer != op.PinVer {
1127 // `configured`: the bundle changed after admission (§7.6)
1128 w.rollback(c, "configuration changed during the move")
1129 return
1130 }
1131 }
1132 b.Live, b.LiveInc = c.Holder.App, c.Holder.Inc
1133 w.cas(c, func(n *claim) bool { n.Op.LandPhase = "restore"; return sameOp(n.Op.ID, op.ID) })
1134 case "restore":
1135 // the installed digest, for the settle check's dirty verdict, from a
1136 // restore of the prepared generation computed and not kept: the
1137 // probe's admission restores the database it starts on (claim rev
1138 // 6.9: a box never reuses a local database)
1139 tmp := *b
1140 tmp.DB = w.restorePinned(b.Region, c.Holder.Inc, loc, g, op.BaseKey, op.BaseVer, op.BaseTX)
1141 if tmp.DB == nil {
1142 w.rollback(c, "restore failed")
1143 return
1144 }
1145 d := w.digestOf(&tmp)
1146 w.cas(c, func(n *claim) bool { n.Op.Installed, n.Op.LandPhase = d, "probing"; return sameOp(n.Op.ID, op.ID) })
1147 case "probing":
1148 if w.now > op.CutDeadline {
1149 w.rollback(c, "cut deadline")
1150 return
1151 }
1152 if !w.admits(b, c, op.ID) {
1153 return
1154 }
1155 if hasMain(b) || w.spawn(b, c, g, true) {
1156 w.probeAccepted(b, c)
1157 if !on("commit-before-open") {
1158 w.openRow(b, op.ID)
1159 }
1160 w.cas(c, func(n *claim) bool { n.Op.LandPhase, n.Op.ProbedAt = "probed", w.now; return sameOp(n.Op.ID, op.ID) })
1161 return
1162 }
1163 if b.hostFailed {
1164 w.rollback(c, "target would not start") // the retry policy marked the host failed (rev 7.11 §7.14)
1165 return
1166 }
1167 if b.open != nil {
1168 return // admitted, its start in progress or in backoff: not a failed try
1169 }
1170 tries := op.ProbeTries + 1
1171 w.cas(c, func(n *claim) bool { n.Op.ProbeTries = tries; return sameOp(n.Op.ID, op.ID) })
1172 if tries >= probeTries {
1173 w.rollback(c, "target would not start")
1174 }
1175 case "probed":
1176 if !op.Committed {
1177 if w.now > op.CutDeadline {
1178 w.rollback(c, "cut deadline")
1179 return
1180 }
1181 if !hasMain(b) {
1182 // the probe died in its settle window (a crash, an agent
1183 // restart): the landing restarts only the probe (§7.14), a
1184 // writer admitted like any other (claim rev 6.9 §6.4a, R8-2):
1185 // cold, into a fresh generation restored from the dead probe's
1186 if w.spawn(b, c, c.Holder.Gen, true) {
1187 w.stats["probe restarted"]++
1188 w.cas(c, func(n *claim) bool { n.Op.ProbedAt = w.now; return sameOp(n.Op.ID, op.ID) })
1189 return
1190 }
1191 if b.hostFailed {
1192 w.rollback(c, "probe died")
1193 return
1194 }
1195 if b.open != nil {
1196 return // admitted, its start in progress: not a failed try
1197 }
1198 tries := op.ProbeTries + 1
1199 w.cas(c, func(n *claim) bool { n.Op.ProbeTries = tries; return sameOp(n.Op.ID, op.ID) })
1200 if tries >= probeTries {
1201 w.rollback(c, "probe died")
1202 }
1203 return
1204 }
1205 if b.open != nil {
1206 return // the probe's session has not opened replication yet (rev 7.11 §7.14): the settle window waits
1207 }
1208 if w.now < op.ProbedAt+15 {
1209 if w.fail["lostreturn"] && w.rng.Intn(2) == 0 {
1210 w.traffic(b) // the biased schedule: the app does real work in the settle window
1211 }
1212 return // the settle window between the two readiness checks
1213 }
1214 // the settle window (§7.7): an app that answered once, did real
1215 // work, and then fails its second readiness check. The biased
1216 // schedule (model 27) makes every target such an app, so the
1217 // rollback is dirty and a return lane follows
1218 if (b.FailSettle || w.fail["lostreturn"]) && hasMain(b) && w.digestOf(b) != op.Installed && w.rng.Intn(2) == 0 {
1219 w.logf("box %d failed the settle check for %s", b.ID, op.ID)
1220 w.rollback(c, "settle check failed")
1221 return
1222 }
1223 // the commit, record first (§7.9): a CAS on the same record
1224 // field the rollback uses, conditional on the deadline
1225 r := w.rec
1226 if r != nil && r.MoveID == op.ID && r.MoveState == "landing" && w.now <= op.CutDeadline {
1227 r.MoveState = "committed"
1228 r.Ver = w.newVer()
1229 w.logf("commit %s: record committed", op.ID)
1230 if w.now > op.CutAt+cutBudget {
1231 w.violate("INV-9: %s committed at %d, cut at %d (budget %d)", op.ID, w.now, op.CutAt, cutBudget)
1232 }
1233 }
1234 return
1235 }
1236 if !op.Opened {
1237 if w.openRow(b, op.ID) || b.Row != nil && b.Row.Status == "ready" {
1238 w.cas(c, func(n *claim) bool { n.Op.Opened = true; return sameOp(n.Op.ID, op.ID) })
1239 }
1240 return
1241 }
1242 if c.oblFor(op.ID) == 0 {
1243 w.closeMove(c, op.Tgt)
1244 }
1245 }
1246}
1247
1248// installLocal is the local install, journaled so the files marker is
1249// written last (R2-6). seal names the files archive version.
1250func (w *world) installLocal(b *box, c *claim, gen string, seal ref, _ string, next func(n *claim)) {
1251 if b.Files != nil && b.Files.Gen == gen && b.Files.Done && b.Files.Inc == c.Holder.Inc {
1252 if w.perRegion > 1 && !w.preparedManifestHere(b, c, gen) {
1253 return // moves §7.6 (L17, R18-1) in the two-box shape: the generation's prepared manifest first
1254 }
1255 // the marker vouches for everything before it
1256 w.cas(c, func(n *claim) bool { next(n); return true })
1257 return
1258 }
1259 if b.Files == nil || b.Files.Gen != gen {
1260 // fresh extraction: park whatever is there first, as
1261 // retire-artifacts of the predecessor's stamp (claim rev 6.5
1262 // §6.4): the new installation's active stamp is untouched. A
1263 // landing parks an earlier stay, never its own installation; a
1264 // dirty rollback's install on the source parks the pre-move
1265 // database while its stamp is still the active one (R6-4)
1266 for _, st := range []int{dbStamp(b.DB), filesStamp(b.Files)} {
1267 if st == 0 {
1268 continue
1269 }
1270 if c.Op.Kind == "restoring" && st != b.stampOf(c.Holder.Inc) && !b.stampRetired(c.Holder.Inc, st) {
1271 // a retired stamp's leftover is fair game: a cold admission that
1272 // issued a stamp and then failed its purge leaves the old
1273 // installation's database under a newer active stamp (rev
1274 // 7.11, the CAS before the purge; seed 65090 / 100,000)
1275 w.violate("INV-6: the dirty install on box %d parks an artifact tagged %d while the active stamp is %d (claim rev 6.5 §6.4)", b.ID, st, b.stampOf(c.Holder.Inc))
1276 }
1277 if c.Op.Kind == "landing" && st == b.stampOf(c.Holder.Inc) {
1278 w.violate("INV-6: landing on box %d would park an artifact tagged with its own active stamp %d", b.ID, st)
1279 }
1280 w.retireArtifacts(b, c.Holder.Inc, st)
1281 }
1282 b.DB = nil
1283 b.Files = nil
1284 src := w.buckets[b.Region].byID(seal.Key, seal.Ver)
1285 if !on("seal-by-version") {
1286 src = w.buckets[b.Region].current(seal.Key)
1287 }
1288 if src == nil || src.B.Files == nil {
1289 return
1290 }
1291 if !on("files-carried") {
1292 // the kpmg outage: nothing carries the files directory, and the
1293 // landing carries on regardless
1294 b.pendingTimers = append([]int(nil), src.B.Timers...)
1295 b.Timers = append([]int(nil), b.pendingTimers...)
1296 w.cas(c, func(n *claim) bool { next(n); return true })
1297 return
1298 }
1299 f := src.B.Files.clone()
1300 f.Gen, f.Done, f.Stamp = gen, false, b.stampOf(c.Holder.Inc)
1301 if !on("markers-last") {
1302 f.Done = true
1303 }
1304 b.Files = f
1305 b.pendingTimers = append([]int(nil), src.B.Timers...)
1306 return
1307 }
1308 // files extracted, marker not yet written: env, timers, columns, marker
1309 b.Timers = append([]int(nil), b.pendingTimers...)
1310 if src := w.buckets[b.Region].byID(seal.Key, seal.Ver); src != nil {
1311 b.Timers = append([]int(nil), src.B.Timers...)
1312 }
1313 b.Attempt = 0
1314 b.History = nil
1315 if b.Row != nil {
1316 b.Row.Repl, b.Row.Next = c.Op.Repl, c.Op.Next
1317 }
1318 b.Files.Done = true
1319 if w.perRegion > 1 && !w.preparedManifestHere(b, c, gen) {
1320 return // the first branch finishes once the prepared manifest is acknowledged
1321 }
1322 w.cas(c, func(n *claim) bool { next(n); return true })
1323}
1324
1325// moveClose reports whether op is a move's close: a committed landing, an
1326// opened rollback or revert, or an abort. Pushes and relocations release on
1327// their own terms.
1328func moveClose(op opState) bool {
1329 switch op.Kind {
1330 case "landing", "restoring", "reverting":
1331 return true
1332 case "moving":
1333 return op.Phase == "aborting"
1334 }
1335 return false
1336}
1337
1338// closeExec is the box a move's close releases to: the target after a
1339// landing, the source otherwise (§19 K8).
1340func closeExec(op opState) int {
1341 if op.Kind == "landing" {
1342 return op.Tgt
1343 }
1344 return op.Src
1345}
1346
1347// closeMove is a move's close by the box that runs it, record first (moves
1348// rev 7.23 §7.5 and §19 K8, Paul's ruling M4). With nothing owed under the
1349// op, the record's CAS clears move into last_move and names the executor's
1350// region; only then does the claim release. The box may stop between the
1351// halves (every third step here, a deterministic schedule that draws
1352// nothing from the seed's stream). It never comes back to a record without a
1353// move, so a closed record whose claim still holds the op is the recovery
1354// pass's to release, from last_move (fixRecord).
1355func (w *world) closeMove(c *claim, execBox int) {
1356 op := c.Op
1357 if c.oblFor(op.ID) != 0 {
1358 return
1359 }
1360 if !on("record-first-close") {
1361 // rev 7.22: the claim first; the recovery pass wrote the record
1362 // half after the release
1363 w.release(c, execBox)
1364 return
1365 }
1366 if !w.closeRecord(c, execBox) {
1367 return // closed already (last_move): not the box's any more
1368 }
1369 if w.now%3 == 0 {
1370 w.stats["close: stopped between the record and the release"]++
1371 return
1372 }
1373 w.release(c, execBox)
1374}
1375
1376// closeRecord is the close's record half: the CAS that clears move into
1377// last_move and names the executor's region, conditional on the record still
1378// carrying this move for the claim's incarnation.
1379func (w *world) closeRecord(c *claim, execBox int) bool {
1380 r, op := w.rec, c.Op
1381 if r == nil || c.Holder == nil || r.Inc != c.Holder.Inc || r.MoveID != op.ID {
1382 return false
1383 }
1384 if b := w.boxByID(execBox); b != nil {
1385 r.Region = b.Region
1386 }
1387 r.MoveID, r.MoveState, r.RollbackID, r.LastMove = "", "", "", op.ID
1388 r.Ver = w.newVer()
1389 w.logf("close %s: record closed (last_move)", op.ID)
1390 return true
1391}
1392
1393func (w *world) release(c *claim, execBox int) {
1394 op := c.Op
1395 if w.cas(c, func(n *claim) bool {
1396 if !sameOp(n.Op.ID, op.ID) {
1397 return false
1398 }
1399 if op.Kind == "relocating" && (n.Op.Kind != "relocating" || n.Op.Phase != op.Phase || n.Op.Exec != execBox) {
1400 return false // CompleteRelocation's requireRelocation (L18 finding 2)
1401 }
1402 // holder.admissions survives the release by living in the holder
1403 // (claim rev 6.12): an admission whose start is unfinished at the
1404 // release stays explained (seed 1052 / 30,000)
1405 if op.Kind == "relocating" {
1406 w.relocationLeftovers(n, op) // the abandoned installations' park-local chores (claim §4)
1407 }
1408 n.Op = opState{Kind: "serving", Exec: execBox, Parts: []int{execBox}}
1409 if op.Kind == "relocating" {
1410 // completed_relocation (ruling 56): the box and the probe's
1411 // session, unmarked until the completion pass discharges it
1412 n.Op.Completed = &completedEntry{Op: op.ID, Box: execBox, Session: n.Holder.Session}
1413 }
1414 return true
1415 }) {
1416 w.logf("released %s to serving on box %d", op.ID, execBox)
1417 if b := w.boxByID(execBox); b != nil && op.Kind == "relocating" {
1418 b.owedDischarge = op.ID // ground truth for INV-31
1419 }
1420 if moveClose(op) && (w.rec == nil || w.rec.LastMove != op.ID || w.rec.MoveID == op.ID) {
1421 // K8 as rev 7.23 amends it: never a serving claim over a live
1422 // move. Judged only for a release CAS that succeeded: one the
1423 // op-id guard refused released nothing (fix round 2)
1424 w.violate("K8: %s released the claim before its record closed (record first, rev 7.23)", op.ID)
1425 }
1426 w.opsDone++
1427 // a move's record closed before this (closeMove, rev 7.23); a
1428 // push's or a relocation's record half follows on a later step
1429 w.checkDurable(w.claim, "at the release of "+op.ID)
1430 if r := w.residueAtEnd(); len(r) > 0 {
1431 w.violate("INV-10: %s ended with residue %v", op.ID, r)
1432 }
1433 switch {
1434 case op.Kind == "landing":
1435 w.stats["move-done"]++
1436 case op.Kind == "restoring" && op.LostBox != 0:
1437 w.stats["declared-lost"]++
1438 case op.Kind == "restoring" && op.Dirty:
1439 w.stats["rollback-dirty"]++
1440 case op.Kind == "restoring":
1441 w.stats["rollback-clean"]++
1442 case op.Kind == "reverting":
1443 w.stats["revert"]++
1444 case op.Kind == "moving":
1445 w.stats["abort"]++
1446 case op.Kind == "pushing":
1447 w.stats["push"]++
1448 case op.Kind == "relocating":
1449 w.stats["relocation"]++
1450 }
1451 }
1452}
1453
1454func (w *world) releaseTarget(b *box, c *claim) {
1455 op := c.Op
1456 if op.Exec == b.ID {
1457 w.holdRow(b, "releasing", op.RollbackID, c)
1458 if !w.evidence(b, op.RollbackID) {
1459 return
1460 }
1461 dirty := false
1462 if (op.LandPhase == "probing" || op.LandPhase == "probed") && b.DB != nil {
1463 // a target without a database never opened: nothing to return.
1464 // Since rev 7.10 the probe's admission restores the database, so
1465 // a target whose probe never started has none (seed 62 / 300:
1466 // a spurious dirty return sealed an empty database and the
1467 // source restored it)
1468 dirty = w.digestOf(b) != op.Installed
1469 }
1470 if !on("dirty-return") {
1471 dirty = false
1472 }
1473 if !op.SrcCovered && on("seal-return") {
1474 // the source's replica stops short of its stopped database: only
1475 // the seal holds every acknowledged write, so the return carries
1476 // it whether or not the target wrote (rev 7.11 §7.8)
1477 dirty = true
1478 w.stats["rollback: return carries the seal"]++
1479 }
1480 w.cas(c, func(n *claim) bool {
1481 if !sameOp(n.Op.ID, op.ID) || n.Op.Exec != b.ID {
1482 return false
1483 }
1484 n.Op.Exec, n.Op.DirtyKnown, n.Op.Dirty, n.Op.Phase, n.Op.TgtStopAt = -1, true, dirty, "released", w.now
1485 q := w.now + horizonH
1486 if on("target-park") {
1487 n.Obl = append(n.Obl, w.scoped(w.newObl(n, "park-superseded", op.TgtRegion, -1, c.Holder.Location, "", q), w.now, c.Holder.Inc))
1488 w.coverRegionPredecessors(n, op.TgtRegion, "rollback") // the target region's whole location is parked (claim rev 6.14 item 6)
1489 }
1490 n.Obl = append(n.Obl, w.scoped(w.newObl(n, "park-staging", op.TgtRegion, -1, fmt.Sprintf("moves/%s/%s/", host, op.ID), "", op.SealQuiet), stagingBound(op.SealQuiet), c.Holder.Inc))
1491 if !dirty {
1492 n.Obl = append(n.Obl, w.newObl(n, "park-local", -1, b.ID, "", "", 0))
1493 }
1494 if q > n.Quiet[op.TgtRegion] {
1495 n.Quiet[op.TgtRegion] = q
1496 }
1497 return true
1498 })
1499 return
1500 }
1501 if op.Dirty && op.Phase == "released" && op.Exec == -1 {
1502 // return the target's writes over the approved lane (M17), under
1503 // the staging-work lease of the source's region, with the same
1504 // paused signers and SafeUntil rule as the forward path
1505 if !w.takeStagingFor(op.SrcRegion, op.RollbackID, b) {
1506 return
1507 }
1508 db, files := w.snapshotBlobOf(b, c, op.ID)
1509 if db == nil {
1510 return // nothing to return yet: the remote could not be read
1511 }
1512 if op.ReturnDB.Ver == 0 {
1513 r := &request{kind: rPut, region: op.SrcRegion, key: fmt.Sprintf("moves/%s/%s/db", host, op.RollbackID), b: db}
1514 w.maybeStallDuplicateFor(b, op.SrcRegion, r)
1515 v := w.issue(b.ID, r)
1516 if v > 0 {
1517 fromDB := b.DB != nil
1518 w.cas(c, func(n *claim) bool {
1519 n.Op.ReturnDB = ref{Region: op.SrcRegion, Key: fmt.Sprintf("moves/%s/%s/db", host, op.RollbackID), Ver: v}
1520 n.Op.ReturnFromDB = fromDB
1521 return sameOp(n.Op.ID, op.ID)
1522 })
1523 }
1524 w.loseReturning(b) // the first PUT of the lane is out, accepted or in flight
1525 return
1526 }
1527 r := &request{kind: rPut, region: op.SrcRegion, key: fmt.Sprintf("moves/%s/%s/files", host, op.RollbackID), b: files}
1528 w.maybeStallDuplicateFor(b, op.SrcRegion, r)
1529 v := w.issue(b.ID, r)
1530 if v > 0 {
1531 w.cas(c, func(n *claim) bool {
1532 if !sameOp(n.Op.ID, op.ID) {
1533 return false
1534 }
1535 n.Op.ReturnFiles = ref{Region: op.SrcRegion, Key: fmt.Sprintf("moves/%s/%s/files", host, op.RollbackID), Ver: v}
1536 n.Op.ReturnQuiet = w.now + stagingTTL + horizonH // the forward path's staging horizon (§7.3)
1537 if !on("staging-quiet") {
1538 n.Op.ReturnQuiet = w.now
1539 }
1540 n.Op.Phase = "returned"
1541 n.Obl = append(n.Obl, w.newObl(n, "park-local", -1, b.ID, "", "", 0))
1542 return true
1543 })
1544 return
1545 }
1546 w.loseReturning(b) // the second PUT is in flight and the return is not yet journaled
1547 }
1548}
1549
1550// loseReturning is the biased schedule for model 27 (rev 7.2). In half the
1551// seeds the cloud stops the target the moment it has begun its dirty
1552// return and before the return is complete: a return PUT is accepted, or
1553// still in flight, in the source's region, and the box that would finish
1554// the lane is gone. Declare-lost then meets a partial return lane, which
1555// the uniform schedule reached once in 100,000 seeds on rev 7 and never on
1556// rev 7.1. A stopped box is what the operator's compute fencing verifies
1557// (§7.10), so it is declared lost on the operator's next call.
1558func (w *world) loseReturning(b *box) {
1559 if !w.fail["lostreturn"] || w.rng.Intn(2) != 0 {
1560 return
1561 }
1562 b.Destroyed, b.Procs = true, nil
1563 w.stats["biased: target stopped mid-return"]++
1564 w.logf("box %d stopped mid-return (biased schedule)", b.ID)
1565}
1566
1567func (w *world) recoverSource(b *box, c *claim) {
1568 op := c.Op
1569 if !on("rollback-journal") && !b.mem[op.RollbackID] && op.SrcPhase != "waiting" && op.SrcPhase != "opened" && op.SrcPhase != "stuck" {
1570 // without a durable journal a restarted source starts over
1571 w.cas(c, func(n *claim) bool { n.Op.SrcPhase = "claimed"; return sameOp(n.Op.ID, op.ID) })
1572 b.mem[op.RollbackID] = true
1573 return
1574 }
1575 b.mem[op.RollbackID] = true
1576 switch op.SrcPhase {
1577 case "waiting":
1578 if b.Row != nil && !op.Committed {
1579 b.Row.Status, b.Row.HoldOp = "restoring", op.RollbackID
1580 }
1581 if op.Exec == -1 && op.DirtyKnown && (!op.Dirty || op.Phase == "returned") {
1582 w.cas(c, func(n *claim) bool {
1583 if !sameOp(n.Op.ID, op.ID) || n.Op.Exec != -1 {
1584 return false
1585 }
1586 n.Op.Exec, n.Op.SrcPhase = op.Src, "claimed"
1587 // return to source (claim rev 6.5): the CAS that makes the
1588 // source's existing installation current again writes its
1589 // stamp back, from the journal; never the target's F
1590 if on("return-stamp") {
1591 n.Holder.Stamp = op.SrcStamp
1592 }
1593 // and, for a clean rollback, the source's generation (rev
1594 // 7.10 §7.14): the target's probe moved holder.generation to
1595 // its own; the target wrote nothing, and the source's probe
1596 // restores from the predecessor generation's remote in its
1597 // own region
1598 if !op.Dirty && n.Holder.Gen != op.SrcGen {
1599 n.Holder.Gen = op.SrcGen // "" is the origin generation
1600 }
1601 return true
1602 })
1603 }
1604 case "claimed":
1605 w.holdRow(b, "restoring", op.RollbackID, c)
1606 next := "verify"
1607 if op.Dirty {
1608 next = "install-park"
1609 }
1610 w.cas(c, func(n *claim) bool { n.Op.SrcPhase = next; return sameOp(n.Op.ID, op.ID) })
1611 case "verify":
1612 if !w.evidence(b, op.RollbackID) {
1613 return
1614 }
1615 ph := "config"
1616 if b.DB == nil {
1617 // the source lost its disk since it stopped (R8-2): see the
1618 // revert's verify above
1619 w.logf("recover: box %d's database is gone; the probe restores it", b.ID)
1620 w.stats["rollback: source database lost"]++
1621 } else if w.digestOf(b) != op.StopDigest {
1622 ph = "stuck"
1623 w.logf("recover: digest mismatch %+v vs %+v", w.digestOf(b), op.StopDigest)
1624 }
1625 w.cas(c, func(n *claim) bool { n.Op.SrcPhase = ph; return sameOp(n.Op.ID, op.ID) })
1626 case "install-park":
1627 if !w.evidence(b, op.RollbackID) {
1628 return
1629 }
1630 if on("dirty-return-needs-admission") && !targetAdmitted(c) {
1631 // movesinstall.go's parking (fix rounds 3 and 4): the target never
1632 // admitted this move, so holder.generation is still the pre-move
1633 // one, the source's live state; there is no residue, and nothing
1634 // is parked. The mutation parks as if the target had admitted
1635 w.stats["rollback: dirty return parks nothing, the target never admitted"]++
1636 w.cas(c, func(n *claim) bool { n.Op.SrcPhase = "install-copy"; return sameOp(n.Op.ID, op.ID) })
1637 return
1638 }
1639 if w.parkStep(b, c.Holder.Location, fmt.Sprintf("superseded/%s/%s/return/", op.ID, host), op.StopAt+horizonH, w.opScope(c, c.Holder.Location), w.keepFilesLive(b.Region, confine(c.Holder.Location))) {
1640 w.cas(c, func(n *claim) bool { n.Op.SrcPhase = "install-copy"; return sameOp(n.Op.ID, op.ID) })
1641 }
1642 case "install-copy":
1643 sv := w.buckets[b.Region].byID(op.ReturnDB.Key, op.ReturnDB.Ver)
1644 if sv == nil {
1645 return // the lane is gone: wait (stuck, in time)
1646 }
1647 g := installGen(&op)
1648 if sv.B.Max == 0 && on("valid-ltx") {
1649 w.cas(c, func(n *claim) bool {
1650 n.Op.SrcPhase, n.Op.BaseVer, n.Op.BaseKey, n.Op.BaseTX = "published", 0, "", 0
1651 return sameOp(n.Op.ID, op.ID)
1652 })
1653 return
1654 }
1655 key := snapKey(c.Holder.Location, g, sv.B.Max)
1656 v := w.issue(b.ID, &request{kind: rCopy, region: b.Region, key: key, srcRegion: b.Region, srcKey: op.ReturnDB.Key, srcVer: op.ReturnDB.Ver})
1657 if v > 0 || !on("build-completion") {
1658 next := "install-anchor"
1659 if !on("build-completion") {
1660 next = "published"
1661 }
1662 w.cas(c, func(n *claim) bool {
1663 n.Op.SrcPhase, n.Op.BaseVer, n.Op.BaseKey, n.Op.BaseTX = next, v, key, sv.B.Max
1664 return sameOp(n.Op.ID, op.ID)
1665 })
1666 }
1667 case "install-anchor":
1668 if w.anchorAcked(b, c.Holder.Location, installGen(&op), op.BaseKey, op.BaseVer, op.BaseTX) {
1669 w.cas(c, func(n *claim) bool { n.Op.SrcPhase = "published"; return sameOp(n.Op.ID, op.ID) })
1670 }
1671 case "published":
1672 w.cas(c, func(n *claim) bool { n.Op.SrcPhase = "local"; return sameOp(n.Op.ID, op.ID) }) // the marker is the probe's admission's (claim rev 6.9)
1673 case "local":
1674 w.installLocal(b, c, installGen(&op), op.ReturnFiles, "", func(n *claim) { n.Op.SrcPhase = "config" })
1675 case "config":
1676 b.Config, b.CfgVer, b.Live, b.LiveInc = c.Holder.Inc, w.cfgVer[c.Holder.App], c.Holder.App, c.Holder.Inc
1677 w.cas(c, func(n *claim) bool { n.Op.SrcPhase = "probing"; return sameOp(n.Op.ID, op.ID) })
1678 case "probing":
1679 if !w.admits(b, c, op.RollbackID) {
1680 return
1681 }
1682 want := c.Holder.Gen
1683 if op.Dirty {
1684 want = installGen(&op) // the return generation prepared from the target's sealed copy
1685 }
1686 if hasMain(b) || w.spawn(b, c, want, true) {
1687 w.probeAccepted(b, c)
1688 w.cas(c, func(n *claim) bool { n.Op.SrcPhase = "probed"; return sameOp(n.Op.ID, op.ID) })
1689 return
1690 }
1691 if b.hostFailed && !b.failCounted {
1692 b.failCounted = true // a host the retry policy marked failed: one failed try for the operation
1693 } else if b.open != nil || b.hostFailed {
1694 return // admitted, its start in progress, or a failed host awaiting the operator: not a failed try
1695 }
1696 w.logf("box %d: the rollback's probe did not start (try %d, generation %q, db=%v, host failed %v)", b.ID, op.ProbeTries+1, want, b.DB != nil, b.hostFailed)
1697 w.cas(c, func(n *claim) bool {
1698 n.Op.ProbeTries++
1699 if n.Op.ProbeTries >= probeTries*3 {
1700 n.Op.SrcPhase = "stuck"
1701 }
1702 return sameOp(n.Op.ID, op.ID)
1703 })
1704 case "probed":
1705 if b.open != nil {
1706 return // the source's session has not opened replication yet (rev 7.11 §7.14)
1707 }
1708 if !hasMain(b) {
1709 w.stats["probe restarted"]++ // the probe died: restarted, admitted like any other (rev 7.11)
1710 w.cas(c, func(n *claim) bool { n.Op.SrcPhase = "probing"; return sameOp(n.Op.ID, op.ID) })
1711 return
1712 }
1713 if op.Dirty && !op.ReturnParked {
1714 // holder.generation and holder.stamp moved at the probe's cold
1715 // admission (claim rev 6.9); the return lane's park is owed
1716 w.cas(c, func(n *claim) bool {
1717 n.Op.ReturnParked = true
1718 n.Obl = append(n.Obl, w.scoped(w.newObl(n, "park-staging", op.SrcRegion, -1, fmt.Sprintf("moves/%s/%s/", host, op.RollbackID), "", op.ReturnQuiet), stagingBound(op.ReturnQuiet), c.Holder.Inc))
1719 return sameOp(n.Op.ID, op.ID)
1720 })
1721 return
1722 }
1723 if w.openRow(b, op.RollbackID) {
1724 w.cas(c, func(n *claim) bool { n.Op.SrcPhase = "opened"; return sameOp(n.Op.ID, op.ID) })
1725 }
1726 case "opened":
1727 if c.oblFor(op.ID) == 0 {
1728 w.closeMove(c, op.Src)
1729 }
1730 }
1731}
1732
1733func (w *world) pushStep(b *box, c *claim) {
1734 op := c.Op
1735 loc := c.Holder.Location
1736 g := installGen(&op) // the generation this push prepares; its probe's cold admission makes it holder.generation (claim rev 6.9)
1737 switch op.Phase {
1738 case "stopping":
1739 w.holdRow(b, "moving", op.ID, c)
1740 if !w.evidence(b, op.ID) {
1741 return
1742 }
1743 // the push's snapshot: the replica under the final checkpoint, plus
1744 // the push's own mark, one more acknowledged transaction
1745 snap, _ := w.snapshotBlobOf(b, c, c.Holder.Gen)
1746 if snap == nil {
1747 // nothing to seal: the push is abandoned before it changed
1748 // anything (rev 7.11)
1749 w.stats["push abandoned: nothing to seal"]++
1750 w.logf("push %s abandoned: the box has no database and its remote cannot be restored", op.ID)
1751 w.release(c, b.ID)
1752 return
1753 }
1754 snap.Gen = g
1755 w.nextW++
1756 mark := w.nextW
1757 w.acks = append(w.acks, ack{W: mark, Inc: c.Holder.Inc, Box: b.ID, Session: c.Holder.Session, Gen: c.Holder.Gen, At: w.now, Kind: "write"})
1758 w.writeInc[mark] = c.Holder.Inc
1759 snap.Max++
1760 snap.Pages[fmt.Sprintf("k%d", mark)] = mark
1761 snap.TXs = append(snap.TXs, mark)
1762 w.txs[mark] = tx{W: mark, TX: snap.Max, Key: fmt.Sprintf("k%d", mark), Val: mark}
1763 w.cas(c, func(n *claim) bool {
1764 if !sameOp(n.Op.ID, op.ID) || n.Op.Phase != "stopping" {
1765 return false
1766 }
1767 n.Op.Phase, n.Op.StopAt, n.Op.PushSnap, n.Op.SealFromDB = "park", w.now, snap, b.DB != nil
1768 return true
1769 })
1770 case "park":
1771 if w.parkStep(b, loc, fmt.Sprintf("superseded/%s/%s/push/", op.ID, host), op.StopAt+horizonH, w.opScope(c, loc), w.keepFilesLive(b.Region, confine(loc))) {
1772 w.cas(c, func(n *claim) bool { n.Op.Phase = "install"; return sameOp(n.Op.ID, op.ID) })
1773 }
1774 case "install":
1775 if on("quiet-before-ack") && w.now < c.Quiet[b.Region] {
1776 return // installs wait out quiet_after (claim §6.2a)
1777 }
1778 snap := op.PushSnap
1779 if b.Files != nil {
1780 snap.SealKey = b.Files.Key
1781 }
1782 key := snapKey(loc, g, snap.Max)
1783 v := w.issue(b.ID, &request{kind: rPut, region: b.Region, key: key, b: snap})
1784 if v > 0 || !on("build-completion") {
1785 next := "install-anchor"
1786 if !on("build-completion") {
1787 next = "published"
1788 }
1789 w.cas(c, func(n *claim) bool {
1790 n.Op.Phase, n.Op.BaseVer, n.Op.BaseKey, n.Op.BaseTX = next, v, key, snap.Max
1791 return sameOp(n.Op.ID, op.ID)
1792 })
1793 }
1794 case "install-anchor":
1795 if w.anchorAcked(b, loc, g, op.BaseKey, op.BaseVer, op.BaseTX) {
1796 w.cas(c, func(n *claim) bool { n.Op.Phase = "published"; return sameOp(n.Op.ID, op.ID) })
1797 }
1798 case "published":
1799 w.cas(c, func(n *claim) bool { n.Op.Phase = "restore"; return sameOp(n.Op.ID, op.ID) }) // the marker is the probe's admission's (claim rev 6.9)
1800 case "restore":
1801 // the probe's admission restores the database (claim rev 6.9); the
1802 // files are unchanged and carried (F7)
1803 // every probe renders config and materializes the release for the
1804 // held row first (the passes skip held rows): RenderHeld, MaterializeHeld
1805 if !b.HasKey {
1806 return
1807 }
1808 b.Config, b.CfgVer, b.Live, b.LiveInc = c.Holder.Inc, w.cfgVer[c.Holder.App], c.Holder.App, c.Holder.Inc
1809 w.cas(c, func(n *claim) bool { n.Op.Phase = "probing"; return sameOp(n.Op.ID, op.ID) })
1810 case "probing":
1811 if !w.admits(b, c, op.ID) {
1812 return
1813 }
1814 if hasMain(b) || w.spawn(b, c, g, true) {
1815 w.probeAccepted(b, c)
1816 w.cas(c, func(n *claim) bool {
1817 n.Op.Phase = "probed" // holder.generation and holder.stamp moved at the probe's cold admission (claim rev 6.9)
1818 q := op.StopAt + horizonH
1819 if q > n.Quiet[b.Region] {
1820 n.Quiet[b.Region] = q
1821 }
1822 return sameOp(n.Op.ID, op.ID)
1823 })
1824 }
1825 case "probed":
1826 if b.open != nil {
1827 return // the admitted session's start is not complete: replication is not open yet (rev 7.11 §7.14)
1828 }
1829 if !hasMain(b) {
1830 // the probe died before its session opened replication or in
1831 // its settle (seed 487 / 30,000: a push released over a dead
1832 // probe whose marker never landed): the probe restarts, a cold
1833 // admission like any other
1834 w.stats["probe restarted"]++
1835 w.cas(c, func(n *claim) bool { n.Op.Phase = "probing"; return sameOp(n.Op.ID, op.ID) })
1836 return
1837 }
1838 if w.openRow(b, op.ID) || b.Row != nil && b.Row.Status == "ready" {
1839 w.release(c, b.ID)
1840 }
1841 }
1842}
1843
1844// sameStep: the operation and every phase the worker branches on are what
1845// the stale read said (claim C3's revalidation under the lease).
1846func sameStep(a, b *claim) bool {
1847 if a == nil || b == nil {
1848 return a == b
1849 }
1850 x, y := a.Op, b.Op
1851 if !(x.ID == y.ID && x.Kind == y.Kind && x.Phase == y.Phase && x.LandPhase == y.LandPhase &&
1852 x.SrcPhase == y.SrcPhase && x.RevPhase == y.RevPhase && x.Exec == y.Exec && x.Committed == y.Committed &&
1853 x.Opened == y.Opened && len(a.Obl) == len(b.Obl) && (a.Holder == nil) == (b.Holder == nil)) {
1854 return false
1855 }
1856 // the writer state is part of the step (claim rev 6.8 §6.4a, R8-2): a
1857 // stalled worker that read a cold admission in progress must not replay
1858 // its build once the admission completed and the writer runs, since the
1859 // build takes stop evidence and replaces the database (seed 10382 /
1860 // 30,000)
1861 if a.Holder != nil && (a.Holder.Session != b.Holder.Session || a.Holder.Gen != b.Holder.Gen || a.Holder.Stamp != b.Holder.Stamp) {
1862 return false
1863 }
1864 if a.Holder != nil && a.Holder.WriterBox != b.Holder.WriterBox {
1865 return false
1866 }
1867 return a.Holder == nil || len(a.Holder.Admissions) == len(b.Holder.Admissions)
1868}
1869
1870// sweep is claim rev 5's at-rest sweep (§6.2a, CC15): for a host with no
1871// operation running, each region's box lists the locations of the host's
1872// retired incarnations and every generation within holder.location other
1873// than holder.generation, and parks whatever it finds as a further
1874// retirement event, under a park-sweep obligation it owns. The claim's
1875// wording leaves out `moves/<host>/`; `sweep-staging` (CC20) adds it.
1876func (w *world) sweep(b *box, c *claim) {
1877 holderless := c != nil && c.Holder == nil && c.Op.Kind == "parking" && c.Op.ParkSweep != ""
1878 if !on("straggler-sweep") || c == nil || !(c.Op.Kind == "" || c.Op.Kind == "serving" || holderless) {
1879 return // a running holderless sweep admits another region's chores (R14-4); any other operation suppresses sweeps
1880 }
1881 for _, o := range c.Obl {
1882 if o.Kind == "park-sweep" && o.Region == b.Region {
1883 return // its own sweep is still running
1884 }
1885 }
1886 bk := w.buckets[b.Region]
1887 // one chore per scope location holding residue (claim rev 6.24 §2,
1888 // R16-2): the sweepable keys are grouped by the location they belong
1889 // to, an incarnation's own, the legacy instances/<host> (a level
1890 // directory or generation directly under the host prefix, never a
1891 // child incarnation) or moves/<host>; each chore's scope names that
1892 // location and its archive incarnation. The mutation of
1893 // scope-membership queues rev 7.16's two chores, instances/<host> and
1894 // moves/<host>, over the whole prefixes
1895 var prefixes []string
1896 for _, p := range []string{"instances/" + host + "/", "moves/" + host + "/"} {
1897 for _, k := range bk.keys(p) {
1898 if !hasNonMarker(bk.objs[k]) || !w.sweepable(c, b.Region, k) {
1899 continue
1900 }
1901 loc := p
1902 if on("scope-membership") {
1903 loc = scopeOf(k) + "/"
1904 }
1905 if !hasStr(prefixes, loc) {
1906 prefixes = append(prefixes, loc)
1907 }
1908 }
1909 }
1910 if len(prefixes) == 0 {
1911 return
1912 }
1913 w.cas(c, func(n *claim) bool {
1914 joining := n.Holder == nil && n.Op.Kind == "parking" && n.Op.ParkSweep != ""
1915 if !(n.Op.Kind == "" || n.Op.Kind == "serving" || joining) {
1916 return false // any other operation running suppresses new sweeps (claim rev 6.15 §6.2a)
1917 }
1918 // the chore's owner: on a serving host the session, as for
1919 // PublishGeneration; on a free host a holderless parking op, {kind:
1920 // parking, intent: {park_sweep: "v1"}}, no exec and no holder, the
1921 // only holderless claim that may carry obligations, so it blocks
1922 // free's successor create until its last acknowledgement frees the
1923 // claim again (FreeAfterPark; claim rev 6.15 §6.2a, R13-4). The
1924 // mutation of holderless-park queues rev 7.13's bare "sweep" chores
1925 // on the free claim. Another region's sweep while the op runs joins
1926 // it with its own chores (a reading, §19.4)
1927 op := "sweep"
1928 switch {
1929 case n.Holder != nil:
1930 op = fmt.Sprintf("session:%d", n.Holder.Session)
1931 case joining:
1932 op = n.Op.ID
1933 w.stats["sweep joined a running holderless op"]++
1934 case on("holderless-park"):
1935 n.Op = opState{Kind: "parking", ID: w.newID("park"), ParkSweep: "v1", Exec: -1, ReqAt: w.now}
1936 op = n.Op.ID
1937 w.stats["holderless sweeps"]++
1938 }
1939 for _, p := range prefixes {
1940 // the scope is persisted at the queuing CAS (claim rev 6.23 §2):
1941 // the sweeping box's complete listing is the evidence, taken now;
1942 // the location and its archive incarnation (the location's own,
1943 // the holder's for the legacy location or the staging sweep, the
1944 // newest retired on a free host; claim rev 6.24 §2)
1945 n.Obl = append(n.Obl, w.scoped(obligation{ID: w.newID("ob"), Op: op, Kind: "park-sweep", Region: b.Region, Box: -1, Prefix: p, Ret: "sweep"}, w.now, incOf(n, scopeLocation(p))))
1946 }
1947 // CoverParkedPredecessors at the queuing CAS (claim rev 6.21 item 6):
1948 // the predecessors with a sweepable object, no others
1949 w.coverParkedPredecessors(n, b.Region, w.sweptPredecessors(n, b.Region, false))
1950 return true
1951 })
1952}
1953
1954// sweepable: is k, in region r, one of the things the at-rest sweep parks?
1955func (w *world) sweepable(c *claim, r int, k string) bool {
1956 if strings.HasSuffix(k, "lock.json") {
1957 return false
1958 }
1959 if strings.HasPrefix(k, "moves/") {
1960 return on("sweep-staging") // CC20
1961 }
1962 h := c.Holder
1963 if !on("sweep-namespace") {
1964 // claim rev 5's discovery: retired_incarnations (capped) and the
1965 // legacy flag. A location older than the cap is never listed
1966 if h != nil && strings.HasPrefix(k, h.Location) {
1967 return w.deadGeneration(c, r, k)
1968 }
1969 for _, inc := range c.Retired {
1970 if strings.HasPrefix(k, locationFor(inc, false)) {
1971 return true
1972 }
1973 }
1974 return w.legacyRetired(c)
1975 }
1976 // claim rev 6 §6.2a (R3-4): locations come from the namespace itself.
1977 // Each child prefix of instances/<host>/ is an incarnation location, or
1978 // the legacy location (the host prefix, litestream's level layout):
1979 // residue unless it is holder.location, and within holder.location
1980 // residue unless it is holder.generation
1981 if h != nil && locationOf(k) == h.Location {
1982 return w.deadGeneration(c, r, k)
1983 }
1984 return true
1985}
1986
1987// locationOf is the location a key under instances/<host>/ belongs to:
1988// instances/<host>/<incarnation>/ for a child prefix, else the legacy
1989// location instances/<host>/ (a level directory directly under the host).
1990func locationOf(k string) string {
1991 p := "instances/" + host + "/"
1992 rest := strings.TrimPrefix(k, p)
1993 if i := strings.Index(rest, "/"); i > 0 {
1994 child := rest[:i]
1995 // an incarnation id as a child prefix (the claim's 64 hex
1996 // characters; the model's decimal ids) is an incarnation location.
1997 // A level directory (0000 to 0009, litestream's layout) or a
1998 // generation prefix (g-<op>/) directly under the host belongs to
1999 // the legacy location, the host prefix itself (claim rev 6.5 §6.2a)
2000 digits := len(child) > 0 && !isLevelDir(child)
2001 for _, r := range child {
2002 if r < '0' || r > '9' {
2003 digits = false
2004 }
2005 }
2006 if digits {
2007 return p + child + "/"
2008 }
2009 }
2010 return p
2011}
2012
2013// deadGeneration: within holder.location, everything but the live set.
2014// The live set is {authoritative region, holder.location,
2015// holder.generation} (claim rev 6.5 §6.2a, R4-3): the generation's
2016// objects are live only in the authoritative region's bucket, the region
2017// of the holder's current installation; a matching key in any other
2018// region is residue. The mutation of live-set-region exempts matching keys
2019// in every region.
2020func (w *world) deadGeneration(c *claim, r int, k string) bool {
2021 h := c.Holder
2022 if w.filesLive(c, r, k) {
2023 return false
2024 }
2025 e := w.execBox(c)
2026 authoritative := e != nil && e.Region == r
2027 if !on("live-set-region") {
2028 authoritative = true
2029 }
2030 // an absent marker is the origin generation "" (R11-1): an adopted
2031 // host's replica has no generation.json until its first cold admission
2032 // publishes one, and that admission's unfinished chain protects the
2033 // origin exactly as the residue oracle reads it
2034 mg := ""
2035 v := w.buckets[r].current(genMarkerKey(h.Location))
2036 if v != nil {
2037 mg = v.B.Gen
2038 }
2039 if v == nil && !on("absent-marker-origin") {
2040 // the mutation (rev 7.11's sweep): an absent marker is no
2041 // disagreement, so nothing protects the unfinished chain's origin
2042 mg = h.Gen
2043 }
2044 if authoritative && mg != h.Gen {
2045 // the marker disagrees with holder.generation. Normal while the
2046 // latest admission is unfinished: the marker names its predecessor,
2047 // and both generations are live (claim rev 6.10, R10-2). Any other
2048 // disagreement: touch nothing under the location, and alert (§6.4)
2049 chain, ok := w.unfinishedChain(c, mg)
2050 if ok && on("baseline-live") {
2051 for _, g := range append(chain, mg) {
2052 if isDataKey(k) && genPrefixOf(k) == dataKey(h.Location, g, "") {
2053 return false
2054 }
2055 if isClosedKey(k) && strings.HasPrefix(k, dataKey(h.Location, g, "closed/")) {
2056 return false
2057 }
2058 }
2059 if k == genMarkerKey(h.Location) {
2060 return false
2061 }
2062 } else if !w.markerAlerted[h.Location] {
2063 w.markerAlerted[h.Location] = true
2064 w.alert("box in region %d: generation.json names %q, the claim %q, and no unfinished admission explains it; the location is left untouched", r, mg, h.Gen)
2065 return false
2066 } else {
2067 return false
2068 }
2069 }
2070 if k == genMarkerKey(h.Location) {
2071 v := w.buckets[r].current(k)
2072 return v != nil && (v.B.Gen != h.Gen || !authoritative)
2073 }
2074 if isDataKey(k) && genPrefixOf(k) == dataKey(h.Location, h.Gen, "") {
2075 return !authoritative
2076 }
2077 if isClosedKey(k) {
2078 if strings.HasPrefix(k, dataKey(h.Location, h.Gen, "closed/")) {
2079 return !authoritative // the live generation's certificates, every session's
2080 }
2081 return true // a dead generation's certificates
2082 }
2083 if isFilesKey(k) {
2084 // a files manifest or object outside the files live set (region HA §5.6, L13):
2085 // its generation's, as the data keys are
2086 if genOf(h.Location, k) == h.Gen {
2087 return !authoritative
2088 }
2089 return true
2090 }
2091 return isDataKey(k)
2092}
2093
2094// genOf is the generation within loc that key k belongs to: "" for the
2095// origin layout (level directories and closed.json directly under loc),
2096// the op id for g-<op>/..., and "-" for a key of no generation (markers,
2097// the lock).
2098func genOf(loc, k string) string {
2099 rest := strings.TrimPrefix(k, loc)
2100 if strings.HasPrefix(rest, "files/") {
2101 return "" // a files manifest of the origin generation (region HA §5.6)
2102 }
2103 if strings.HasPrefix(rest, "g-") {
2104 if i := strings.Index(rest, "/"); i > 0 {
2105 return rest[2:i]
2106 }
2107 return "-"
2108 }
2109 if isDataKey(k) && genPrefixOf(k) == loc || strings.HasPrefix(rest, "closed/") {
2110 return ""
2111 }
2112 return "-"
2113}
2114
2115// legacyRetired: the host once held a legacy incarnation, now retired, so
2116// the bare namespace outside holder.location is a retired location.
2117func (w *world) legacyRetired(c *claim) bool {
2118 for _, inc := range c.Retired {
2119 if w.legacyIncs[inc] {
2120 return true
2121 }
2122 }
2123 return false
2124}
2125
2126// publishGen writes generation.json, conditional on the marker it
2127// supersedes: If-Match its version, or If-None-Match when there is none
2128// (moves rev 7.1 §5.1, model 29). generation.json is a reused key, so an
2129// earlier publication that completes late would otherwise put an older
2130// generation back, and every wake would refuse.
2131func (w *world) publishGen(b *box, loc, gen string, inc int, base int, key string, tx int) int {
2132 // the marker names the generation's snapshot by key, version id and TXID (R3-1, R4-1)
2133 // INV-11 (R7-4): generation.json is published only from a completed
2134 // build: the snapshot version it names exists and the anchor at its
2135 // TXID is current. A marker that names artifacts still in flight would
2136 // make the completion predicate true before the artifacts exist
2137 bk := w.buckets[b.Region]
2138 if tx > 0 {
2139 if sv := bk.byID(key, base); sv == nil || sv.Marker {
2140 w.violate("INV-11: generation.json for %s published before its snapshot %s@%d landed (R7-4)", gen, key, base)
2141 } else if on("restore-anchor") && bk.current(ltxKey(loc, gen, 0, tx, tx)) == nil {
2142 w.violate("INV-11: generation.json for %s published before its anchor [%d,%d] landed (R7-4)", gen, tx, tx)
2143 }
2144 }
2145 r := &request{kind: rPut, region: b.Region, key: genMarkerKey(loc), b: &blob{Kind: "gen", Gen: gen, Inc: inc, Base: base, SnapKey: key, TX: tx}}
2146 if w.fail["markerhold"] && w.rng.Intn(2) == 0 {
2147 // the biased schedule (rev 7.5): this publication is accepted and
2148 // completes at its worst moment, once a newer marker is current
2149 r.hold = true
2150 }
2151 if on("generation-conditional") {
2152 r.ifSet = true
2153 if cur := w.buckets[b.Region].current(r.key); cur != nil {
2154 r.ifVer = cur.ID
2155 }
2156 }
2157 return w.issue(b.ID, r)
2158}
2159
2160// anchorAcked is §5.1 step 7 inside a build (R6-1, R7-4): the anchor
2161// [tx, tx], every page of the snapshot the build installed, is PUT into the
2162// generation and the build goes on only once the PUT is acknowledged. The
2163// mutation of restore-anchor writes none; the mutation of anchor-acked
2164// does not wait.
2165func (w *world) anchorAcked(b *box, loc, gen, snapKey string, snapVer, tx int) bool {
2166 if !on("restore-anchor") || tx == 0 && on("valid-ltx") {
2167 return true // no transactions yet: there is no position to anchor (the mutation of valid-ltx writes [0,0], which the encoder refuses)
2168 }
2169 sv := w.buckets[b.Region].byID(snapKey, snapVer)
2170 if sv == nil {
2171 return false
2172 }
2173 if w.buckets[b.Region].current(ltxKey(loc, gen, 0, tx, tx)) != nil {
2174 return true // a replay finds its anchor current and reuses it
2175 }
2176 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...)}
2177 acked := w.ackedPut(b, ltxKey(loc, gen, 0, tx, tx), anchorOf(db, sv.B.Inc), false)
2178 w.stats["restore anchors"]++
2179 return acked || !on("anchor-acked")
2180}
2181