carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package hostclaim
2
3import "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.
8const (
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.
16const (
17 PhaseRelocatingProbing = "probing"
18 PhaseRelocatingServing = "serving"
19)
20
21// recoverySessionRow retains the admitted recovery writer through opening.
22var 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.
40var 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.
52func 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.
71func 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.
77func 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.
90func 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.
113func 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