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
3// ---------- the application ----------
4
5// traffic: every running process of the host does its own work, whether or
6// not a request arrived: background workers and descendants included.
7func (w *world) traffic(b *box) {
8 if b.Destroyed || b.Frozen {
9 return
10 }
11 for _, p := range b.Procs {
12 if w.rng.Intn(3) != 0 {
13 continue
14 }
15 w.nextW++
16 wr := w.nextW
17 if b.DB != nil && b.DB.Inc == p.Inc {
18 w.commit(b.DB, wr)
19 } else {
20 w.txs[wr] = tx{W: wr} // into a purged or foreign database: nowhere
21 }
22 // acknowledged to the client or the outside world either way: a
23 // write into a purged or foreign database is a lost write. The
24 // acknowledgement is the process's session's (rev 7.13, R12-1): the
25 // database under it may be gone (seed 899 / 3,000)
26 w.acks = append(w.acks, ack{W: wr, Inc: p.Inc, Box: b.ID, Session: p.Session, Gen: p.Gen, At: w.now, Kind: "write"})
27 w.writeInc[wr] = p.Inc
28 if p.Main && w.rng.Intn(4) == 0 && b.Row != nil && b.Row.Inc == p.Inc {
29 w.nextW++
30 b.Timers = append(b.Timers, w.nextW)
31 w.acks = append(w.acks, ack{W: w.nextW, Inc: p.Inc, Box: b.ID, Session: p.Session, Gen: p.Gen, At: w.now, Kind: "timer"})
32 }
33 }
34}
35
36// idleStop: the activator's ordinary idle stop (main only; a descendant
37// keeps running, as today).
38func (w *world) idleStop(b *box) {
39 if !w.agentUp(b) || !hasMain(b) {
40 return
41 }
42 w.replicate(b)
43 // the unit stop kills the cgroup: the main process and every descendant
44 // placed in it; an escaped descendant survives, and while it lives the
45 // session cannot certify (rev 7.10)
46 var keep []*proc
47 for _, p := range b.Procs {
48 if !p.Main && !p.Strict {
49 keep = append(keep, p)
50 }
51 }
52 b.Procs = keep
53 if b.open != nil {
54 // the session's start is still finishing its PUTs: the agent's
55 // replicator opens when they land and ships what the app wrote,
56 // then closes (rev 7.11 §7.14); no certificate now
57 w.stats["idle stop before replication opened"]++
58 return
59 }
60 // the clean close (claim rev 6.9 §6.4a): drain, then the certificate
61 if b.DB != nil {
62 b.DB.Closing = true
63 w.closeStep(b)
64 }
65}
66
67// wake: a request reaches this box's edge for the host.
68func (w *world) wake(b *box) {
69 if !w.agentUp(b) || b.Row == nil || b.Row.Status != "ready" {
70 return
71 }
72 // the edge routes on the row (edge.go:595): a ready row receives the
73 // public request whether or not the instance already runs. INV-5: no
74 // public delivery to a landing target before the commit (R3-5)
75 if c := w.claim; c != nil && c.Op.Kind == "landing" && !c.Op.Committed && c.Op.Tgt == b.ID {
76 w.violate("INV-5: a public request reached the landing target, box %d, before the commit of %s", b.ID, c.Op.ID)
77 return
78 }
79 if hasMain(b) {
80 return
81 }
82 if b.Old {
83 if w.rec == nil {
84 return
85 }
86 c := &claim{Holder: &holder{Inc: w.rec.Inc, App: w.rec.App, Legacy: true}}
87 w.spawn(b, c, "", false)
88 return
89 }
90 c, ok := w.admissionClaim(b)
91 if !ok || !w.admits(b, c, "") {
92 return
93 }
94 if !w.completedHandover(b, c) {
95 return // N6: the first serving wake after a completion hands over from the probe's receipt
96 }
97 if b.DB == nil && on("quiet-before-ack") && w.now < c.Quiet[b.Region] {
98 return // a first install into this region waits out quiet_after (claim §6.2a)
99 }
100 w.spawn(b, c, c.Holder.Gen, false)
101}
102
103// ---------- reconcile ----------
104
105func (w *world) retireRow(b *box) {
106 if b.Row == nil {
107 return
108 }
109 if !b.Old && !w.evidence(b, "retire") {
110 return
111 }
112 if b.Old {
113 var keep []*proc
114 for _, p := range b.Procs {
115 if !p.Main {
116 keep = append(keep, p)
117 }
118 }
119 b.Procs = keep
120 }
121 b.Row = nil
122 b.Config = 0 // reapConfigEnv
123 // the schedule converger prunes timers of a host with no row
124 b.Timers, b.History, b.Attempt = nil, nil, 0
125}
126
127func (w *world) ownsLocalObligation(c *claim, b *box) bool {
128 for _, o := range c.Obl {
129 if o.Kind == "park-local" && o.Box == b.ID {
130 return true
131 }
132 }
133 return false
134}
135
136func (w *world) reconcile(b *box) {
137 if !w.agentUp(b) {
138 return
139 }
140 if b.Old {
141 if w.rec != nil && w.rec.Region == b.Region {
142 if b.Row == nil || b.Row.Inc != w.rec.Inc {
143 b.Row = &row{Inc: w.rec.Inc, App: w.rec.App, Status: "ready"}
144 }
145 } else if b.Row != nil {
146 w.retireRow(b)
147 }
148 return
149 }
150 c, ok := w.admissionClaim(b) // reconcile's keep-or-create is an admission decision (C4)
151 if !ok {
152 return
153 }
154 if c == nil {
155 if w.claim == nil {
156 w.adopt()
157 }
158 return
159 }
160 w.reconcileClaimed(b, c)
161}
162
163// adopt is the one adoption routine every actor uses (claim §9): a
164// create-only claim for an unclaimed legacy record, with discovery.
165func (w *world) adopt() bool {
166 if w.claim != nil || w.rec == nil {
167 return false
168 }
169 eb := w.adoptionOwner(w.rec.Region) // exactly one proven owner (claim P5-D2)
170 if eb == nil {
171 return false
172 }
173 // adoption writes holder.stamp (claim rev 6.5 §2). The pre-claim writer
174 // is the legacy identity (claim rev 6.24, R16-3): session 0 with
175 // generation "" (read as @root), the pre-release process on the
176 // executing box its writer_box; the first cold admission's writes_lost
177 // names it (seed 94288 / 100,000 named box 0 and left the legacy writes
178 // undeclared), and a lost-target return declares it in
179 // additional_writes_lost. Rev 7.13 to 7.16 gave it session 1, which hid
180 // the session-zero case from E
181 n := &claim{Ver: 1, Holder: &holder{Inc: w.rec.Inc, App: w.rec.App, Legacy: true, Location: locationFor(w.rec.Inc, true), Stamp: 1, Session: 0, WriterBox: eb.ID},
182 Op: opState{Kind: "serving", Exec: eb.ID, Parts: []int{eb.ID}}, Regions: []int{w.rec.Region}, Boxes: []int{eb.ID}, Complete: false}
183 if w.stampBox == nil {
184 w.stampBox = map[int]int{}
185 }
186 w.stampBox[1] = eb.ID // the adoption's stamp names the serving box's installation (claim rev 6.5 §2)
187 if on("legacy-discovery") {
188 // every other region parks whatever it holds: it cannot be the
189 // live lineage, because the record names another region
190 for r := 0; r < nRegions; r++ {
191 if r == w.rec.Region {
192 continue
193 }
194 for _, p := range []string{"instances/" + host + "/", "moves/" + host + "/"} {
195 n.Obl = append(n.Obl, obligation{ID: w.newID("ob"), Op: "adopt", Kind: "park-replica", Region: r, Box: -1, Prefix: p, Ret: "legacy"})
196 }
197 }
198 // and local state on every box but the record region's serving box
199 // (claim rev 4 §9, the J2 N5 note): it cannot be the live instance's
200 for _, b := range w.liveBoxes() {
201 if b.ID != eb.ID {
202 n.Obl = append(n.Obl, obligation{ID: w.newID("ob"), Op: "adopt", Kind: "park-local", Box: b.ID, Region: -1, Ret: "legacy"})
203 }
204 }
205 }
206 w.claim = n
207 w.logf("adopted legacy host inc %d on box %d", w.rec.Inc, eb.ID)
208 return true
209}
210
211func (w *world) reconcileClaimed(b *box, c *claim) {
212 h := c.Holder
213 if h == nil || w.rec == nil || w.rec.Inc != h.Inc {
214 if b.Row != nil && !w.ownsLocalObligation(c, b) {
215 w.retireRow(b)
216 }
217 return
218 }
219 if b.Row != nil && b.Row.Inc != h.Inc {
220 w.retireRow(b) // an orphan row (claim C1)
221 return
222 }
223 switch c.Op.Kind {
224 case "serving", "creating", "pushing":
225 if c.Op.Exec == b.ID {
226 if b.Row != nil && b.Row.Status != "ready" && c.Op.Kind == "serving" && on("stale-hold-release") && !w.completionHolds(c, b) {
227 // a hold whose operation has ended (the claim is serving here)
228 b.Row.Status, b.Row.HoldOp = "ready", ""
229 }
230 if b.Row == nil && c.Op.Kind == "serving" {
231 b.Row = &row{Inc: h.Inc, App: h.App, Status: "ready"}
232 w.initRecord(b, c) // the local record from holder.stamp, on this fresh read (claim rev 6.5 §6.4)
233 }
234 } else if w.catchUpHold(b, c) {
235 // a delayed source's row, held for the completed op until its
236 // park-local retires it (finding 3, ruling 85)
237 } else if b.Row != nil && !(on("participant-rows") && w.ownsLocalObligation(c, b)) {
238 w.retireRow(b)
239 }
240 default:
241 if c.Op.Kind == "relocating" && b.ID == c.Op.Src && b.Row != nil && b.Row.Status == "ready" && c.Op.Release != nil {
242 // §5.3, L12 (rule reloc-row-cases): the former executor's row is
243 // held once its admission fence closed, never ready, until its
244 // park-local retires it. The mutation reopens it as a stale hold
245 if on("reloc-row-cases") {
246 b.Row.Status, b.Row.HoldOp = "relocating", c.Op.ID
247 }
248 }
249 if w.isParticipant(c, b) {
250 if !on("participant-rows") && w.rec.Region != b.Region && b.Row != nil {
251 w.retireRow(b) // region mismatch retires a participant's row
252 }
253 return
254 }
255 if b.Row != nil && !w.ownsLocalObligation(c, b) {
256 w.retireRow(b)
257 }
258 }
259}
260
261// ---------- background passes with work in flight (J-N8) ----------
262
263func (w *world) passPlan(b *box, kind string) {
264 if !w.agentUp(b) || b.Row == nil || b.Row.Status != "ready" {
265 return
266 }
267 if kind == "config" && !b.HasKey {
268 return
269 }
270 b.plans = append(b.plans, &plan{Kind: kind, Inc: b.Row.Inc, App: b.Row.App, Ver: w.cfgVer[b.Row.App], At: w.now + w.pauseLen(), Stamp: b.stampOf(b.Row.Inc)})
271}
272
273// passExec runs one independent process's write. It needs the box running,
274// not the agent. Under the host guard (an exclusive flock shared by the
275// agent and every root command, claim rev 4 §6.4) it reads the claim
276// fresh and refuses if the incarnation, the operation or the config
277// identity it selected has moved on. The mutation keeps an in-process lock
278// only: the process acts on what it validated before it paused.
279func (w *world) passExec(b *box) {
280 if b.Destroyed || b.Frozen || len(b.plans) == 0 {
281 return
282 }
283 i := w.rng.Intn(len(b.plans))
284 p := b.plans[i]
285 if p.At > w.now {
286 return
287 }
288 b.plans = append(b.plans[:i:i], b.plans[i+1:]...)
289 if on("hostguard") {
290 if b.Partitioned {
291 return
292 }
293 c := w.claim
294 if c == nil || c.Holder == nil || c.Holder.Inc != p.Inc || b.Row == nil || b.Row.Inc != p.Inc {
295 return
296 }
297 if p.Kind == "config" && p.Ver != w.cfgVer[p.App] {
298 return // the selected bundle is no longer the held one
299 }
300 }
301 // the local record (claim rev 6.5 §6.4): the writer carries the stamp
302 // it selected under, admitted only if it is still the active one
303 if !b.admitsStamp(p.Inc, p.Stamp) {
304 return
305 }
306 if p.Stamp != b.stampOf(p.Inc) {
307 // the oracle: a writer of a retired installation wrote host state
308 w.violate("INV-6: a %s pass of a retired installation (stamp %d, active %d) wrote host state for inc %d on box %d", p.Kind, p.Stamp, b.stampOf(p.Inc), p.Inc, b.ID)
309 }
310 switch p.Kind {
311 case "config":
312 b.Config, b.CfgVer = p.Inc, p.Ver
313 case "adopt":
314 b.Live, b.LiveInc = p.App, p.Inc
315 }
316}
317
318// ---------- timers ----------
319
320func (w *world) runner(b *box) {
321 if !w.agentUp(b) || b.Row == nil {
322 return
323 }
324 if len(b.Timers) > 0 {
325 if b.Row.Status == "ready" && hasMain(b) {
326 id := b.Timers[0]
327 b.Timers = b.Timers[1:]
328 b.History = append(b.History, id)
329 w.delivered[id] = true
330 } else if b.Row.Status != "ready" && !on("timer-freeze") {
331 b.Attempt++ // a refused delivery still advances bookkeeping
332 }
333 }
334 if w.rng.Intn(4) == 0 {
335 b.History = nil // the runner's own daily prune (runner.go:260)
336 }
337}
338
339// ---------- self-fencing (claim §7.1) ----------
340
341func (w *world) watchdog(b *box) {
342 if b.Destroyed || b.Frozen {
343 return
344 }
345 if !on("watchdog-independent") && !w.agentUp(b) {
346 return
347 }
348 if b.ExecUntil > 0 && w.localNow(b) > b.ExecUntil && len(b.Procs) > 0 {
349 b.Procs = nil
350 w.logf("box %d self-fenced (exec lease expired)", b.ID)
351 }
352}
353
354// ---------- faults ----------
355
356func (w *world) fault() {
357 if len(w.pending) > 0 && w.rng.Intn(3) == 0 {
358 i := w.rng.Intn(len(w.pending))
359 r := w.pending[i]
360 if r.beyond && w.fail["recreate"] {
361 return // the biased schedule keeps it for its worst moment
362 }
363 w.pending = append(w.pending[:i:i], w.pending[i+1:]...)
364 w.complete(r)
365 return
366 }
367 bs := w.liveBoxes()
368 b := bs[w.rng.Intn(len(bs))]
369 switch w.rng.Intn(9) {
370 case 0:
371 if !b.AgentDown {
372 b.AgentDown = true
373 var keep []*proc
374 for _, p := range b.Procs {
375 if b.Nested {
376 keep = append(keep, p)
377 }
378 }
379 b.Procs = keep
380 b.forget()
381 w.logf("box %d agent crashed", b.ID)
382 // a crash that takes the disk with it (an instance replaced, a
383 // volume gone): the writer's uploads may still be in flight,
384 // which is the writer-epoch hazard (claim rev 6.6 §6.4a, R7-1)
385 // in every operation that has this box write (R8-2), not only
386 // while serving
387 if c := w.claim; w.fail["disk"] && c != nil && c.Holder != nil && c.Op.Exec == b.ID && b.DB != nil && w.rng.Intn(3) == 0 {
388 // the fault removes physical state only (R10-6): the
389 // acknowledged, unuploaded writes are declared lost by the
390 // next admission's writes_lost record, never exempted here
391 b.DB = nil
392 b.Procs = nil // nothing survives the disk: a descendant with no database would acknowledge writes into nowhere
393 w.stats["disk-lost with the crash"]++
394 w.logf("box %d lost its disk with the crash", b.ID)
395 }
396 } else {
397 b.AgentDown = false
398 }
399 case 1:
400 b.Procs = nil
401 b.plans = nil // a reboot ends every process
402 b.AgentDown, b.Frozen, b.AgentPaused = false, false, false
403 b.forget()
404 if w.rng.Intn(6) == 0 {
405 // the purge of the local database the next admitted writer must
406 // make returns an error for a while (claim rev 6.9): the writer
407 // does not start
408 b.PurgeFails = !b.PurgeFails
409 if b.PurgeFails {
410 w.stats["purge failures injected"]++
411 }
412 }
413 if w.rng.Intn(8) == 0 && len(b.Stamps) > 0 {
414 // the local stamp record is lost with the reboot: the box
415 // refuses every writer until it re-initializes from a fresh
416 // claim read naming it (claim rev 6.5 §6.4; rev 7.7, R6-4)
417 b.Stamps = nil
418 w.logf("box %d lost its local stamp record", b.ID)
419 w.stats["stamp records lost"]++
420 }
421 if !on("floor-boot-recovery") && w.rng.Intn(3) == 0 {
422 b.Old = true
423 w.logf("box %d boot-recovered an old executable", b.ID)
424 }
425 w.logf("box %d rebooted", b.ID)
426 case 2:
427 if w.fail["partition"] {
428 b.Partitioned = !b.Partitioned
429 }
430 case 3:
431 if w.fail["freezetgt"] {
432 // the biased schedule (rev 7.5): freeze the landing target in
433 // its probe or settle window, and keep it frozen until the
434 // source runs again, if a declare-lost let it
435 c := w.claim
436 if c != nil && c.Op.Kind == "landing" && b.ID == c.Op.Tgt && !b.Frozen && (c.Op.LandPhase == "probing" || c.Op.LandPhase == "probed") {
437 b.Frozen = true
438 w.logf("box %d frozen in its settle window (biased schedule)", b.ID)
439 break
440 }
441 if b.Frozen && c != nil && (c.Op.Kind == "landing" || c.Op.Kind == "restoring") && b.ID == c.Op.Tgt {
442 if s := w.boxByID(c.Op.Src); s != nil && hasMain(s) {
443 b.Frozen = false
444 w.logf("box %d resumed beside the recovered source (biased schedule)", b.ID)
445 }
446 break
447 }
448 }
449 if w.fail["freeze"] {
450 b.Frozen = !b.Frozen
451 }
452 case 4:
453 if w.fail["pause"] {
454 b.AgentPaused = !b.AgentPaused
455 }
456 case 5:
457 c := w.claim
458 committed := c != nil && (c.Op.Committed || w.rec != nil && w.rec.MoveID == c.Op.ID && w.rec.MoveState == "committed")
459 if w.fail["destroy"] && c != nil && (c.Op.Kind == "landing" || c.Op.Kind == "restoring") && !committed && b.ID == c.Op.Tgt {
460 b.Destroyed = true
461 b.Procs = nil
462 w.logf("box %d destroyed", b.ID)
463 }
464 case 6:
465 w.console = nil // console crash: plans dropped
466 case 7:
467 // the serving box loses its database file while the app is idle: the
468 // next wake restores cold. Writes after the last completed
469 // checkpoint are lost by design (the RPO), and declared so; every
470 // durable write must come back (§14.2.3).
471 c := w.claim
472 if w.fail["disk"] && c != nil && c.Holder != nil && c.Op.Kind == "serving" && c.Op.Exec == b.ID && b.DB != nil && !hasMain(b) && len(b.Procs) == 0 {
473 // physical loss only (R10-6): the next admission declares
474 b.DB = nil
475 w.stats["disk-lost"]++
476 w.logf("box %d lost its database file", b.ID)
477 }
478 default:
479 w.completeDue(false)
480 }
481}
482
483func (w *world) replaceDestroyed() {
484 for r := 0; r < nRegions; r++ {
485 for len(w.regionBoxes(r)) < w.regionMin(r) { // the region's minimum (H5): the group launches replacements
486 w.nextID++
487 nb := &box{ID: 100 + w.nextID, Region: r, HasKey: true, Enrolled: w.perRegion == 1, EnrollAt: w.now, DeleteOnTermination: true}
488 nb.forget()
489 w.boxes = append(w.boxes, nb)
490 w.logf("box %d replaces the destroyed box in region %d", nb.ID, r)
491 }
492 }
493}
494