| 1 | package hostclaim |
| 2 | |
| 3 | import "fmt" |
| 4 | |
| 5 | // Phases of the landing op (moves spec §7.7, §7.9, §19.1). The claim's phase |
| 6 | // follows the record's landing_phase; these three are the steps an admitted |
| 7 | // probe session lives through. |
| 8 | const ( |
| 9 | PhaseLandingProbing = "probing" |
| 10 | PhaseLandingProbed = "probed" |
| 11 | PhaseLandingCommitted = PhaseCommitted |
| 12 | ) |
| 13 | |
| 14 | // The relocating phases an admitted writer session lives through (claim §4, |
| 15 | // region HA §5.8): the target's probe in probing, then serving. |
| 16 | const ( |
| 17 | PhaseRelocatingProbing = "probing" |
| 18 | PhaseRelocatingServing = "serving" |
| 19 | ) |
| 20 | |
| 21 | // recoverySessionRow retains the admitted recovery writer through opening. |
| 22 | var recoverySessionRow = []string{PhaseProbing, PhaseProbed, PhaseOpened} |
| 23 | |
| 24 | // sessionSteps is the forward-step table for an admitted writer session |
| 25 | // (claim Plan 9 D1, the moves ruling H3 option A). The session's selection |
| 26 | // binds op id, kind, session, stamp and generation, not phase. A phase change |
| 27 | // keeps the session only along its kind's row, only forward and one step at a |
| 28 | // time. A kind with no row, every backward or skipped step and every change of op or kind |
| 29 | // still ends the session, exactly as main's phase equality did. |
| 30 | // |
| 31 | // landing: the probe's admitted session becomes the serving session after the |
| 32 | // commit and the open (moves §7.9 step 2: "the target serves on the session |
| 33 | // its probe was admitted with"; §7.14's rows "landing: probing, probed" and |
| 34 | // "landing, committed, not yet finalized" share the flip's op.exec handover). |
| 35 | // |
| 36 | // relocating: the target's cold probe is admitted in probing and, once the |
| 37 | // probe passes, serves on that same session in the op's serving phase (claim |
| 38 | // §4's relocating phase table, claim Plan 11 R1). probe-failed is not on the |
| 39 | // row: a failed probe's session ends. |
| 40 | var sessionSteps = map[string][]string{ |
| 41 | KindLanding: {PhaseLandingProbing, PhaseLandingProbed, PhaseLandingCommitted}, |
| 42 | KindRelocating: {PhaseRelocatingProbing, PhaseRelocatingServing}, |
| 43 | KindReverting: recoverySessionRow, |
| 44 | KindRestoring: recoverySessionRow, |
| 45 | } |
| 46 | |
| 47 | // SessionContinues reports whether a writer session last checked while the op |
| 48 | // of this kind was at phase from may keep working now that it is at phase to: |
| 49 | // the same phase or the adjacent forward step of its row, never a skip (M9). |
| 50 | // This is transition authority. A reader that missed a step uses |
| 51 | // SessionObserves, comparing against the last phase it checked. |
| 52 | func SessionContinues(kind, from, to string) bool { |
| 53 | if from == to { |
| 54 | return true |
| 55 | } |
| 56 | row := sessionSteps[kind] |
| 57 | for i := 0; i+1 < len(row); i++ { |
| 58 | if row[i] == from { |
| 59 | return row[i+1] == to |
| 60 | } |
| 61 | } |
| 62 | return false |
| 63 | } |
| 64 | |
| 65 | // SessionObserves reports whether a writer session last checked at phase from |
| 66 | // may observe the op at phase to: the same phase or any later phase on kind's |
| 67 | // row, so a session that missed an intermediate phase can still catch up. It |
| 68 | // grants observation only. Callers also bind op id, kind, session, stamp and |
| 69 | // generation, and every transition that changes the claim is still guarded by |
| 70 | // SessionContinues (KeepWriterSession), which never skips a step. |
| 71 | func SessionObserves(kind, from, to string) bool { |
| 72 | return SessionReaches(kind, from, to) |
| 73 | } |
| 74 | |
| 75 | // SessionPhase reports whether phase is on kind's session row: a phase at |
| 76 | // which an admitted writer session of that kind may be working. |
| 77 | func SessionPhase(kind, phase string) bool { |
| 78 | for _, p := range sessionSteps[kind] { |
| 79 | if p == phase { |
| 80 | return true |
| 81 | } |
| 82 | } |
| 83 | return false |
| 84 | } |
| 85 | |
| 86 | // SessionReaches reports whether to is from or a later phase on kind's |
| 87 | // session row. It identifies a session for teardown and grants no write |
| 88 | // authority: a stop names the session at any phase it went on to reach, never |
| 89 | // at an earlier one. Write authority is SessionContinues. |
| 90 | func SessionReaches(kind, from, to string) bool { |
| 91 | if from == to { |
| 92 | return true |
| 93 | } |
| 94 | at := -1 |
| 95 | for i, p := range sessionSteps[kind] { |
| 96 | if p == from { |
| 97 | at = i |
| 98 | } |
| 99 | if p == to { |
| 100 | return at >= 0 |
| 101 | } |
| 102 | } |
| 103 | return false |
| 104 | } |
| 105 | |
| 106 | // KeepWriterSession guards a phase CAS that the admitted writer session must |
| 107 | // survive, such as the landing's probed and committed steps. fn runs first. |
| 108 | // Afterwards the op id, kind and executor and the holder's incarnation, |
| 109 | // session, stamp, generation and writer box must be exactly as before, and the |
| 110 | // phase must be the same or the adjacent forward step (SessionContinues). Anything else |
| 111 | // is ErrWrongState, so the CAS writes nothing. The commit therefore cannot |
| 112 | // move the generation (moves §7.9 step 1, R10-3) or start a new session. |
| 113 | func KeepWriterSession(opID string, fn func(*Claim) error) func(*Claim) error { |
| 114 | return func(c *Claim) error { |
| 115 | if err := RequireOp(opID)(c); err != nil { |
| 116 | return err |
| 117 | } |
| 118 | if fn == nil || c.Holder == nil { |
| 119 | return fmt.Errorf("%w: a session-keeping step names its transition and its holder", ErrMalformed) |
| 120 | } |
| 121 | kind, phase, exec := c.Op.Kind, c.Op.Phase, c.Op.Exec |
| 122 | before := *c.Holder |
| 123 | if err := fn(c); err != nil { |
| 124 | return err |
| 125 | } |
| 126 | after := c.Holder |
| 127 | if after == nil || c.Op.ID != opID || c.Op.Kind != kind || c.Op.Exec != exec || |
| 128 | after.Incarnation != before.Incarnation || after.Session != before.Session || after.Stamp != before.Stamp || |
| 129 | after.Generation != before.Generation || after.WriterBox != before.WriterBox || !SessionContinues(kind, phase, c.Op.Phase) { |
| 130 | return fmt.Errorf("%w: a session-keeping step keeps the writer session and moves %s forward from %q, not to %q", ErrWrongState, kind, phase, c.Op.Phase) |
| 131 | } |
| 132 | return nil |
| 133 | } |
| 134 | } |
| 135 | |