package hostclaim import "fmt" // Phases of the landing op (moves spec §7.7, §7.9, §19.1). The claim's phase // follows the record's landing_phase; these three are the steps an admitted // probe session lives through. const ( PhaseLandingProbing = "probing" PhaseLandingProbed = "probed" PhaseLandingCommitted = PhaseCommitted ) // The relocating phases an admitted writer session lives through (claim §4, // region HA §5.8): the target's probe in probing, then serving. const ( PhaseRelocatingProbing = "probing" PhaseRelocatingServing = "serving" ) // recoverySessionRow retains the admitted recovery writer through opening. var recoverySessionRow = []string{PhaseProbing, PhaseProbed, PhaseOpened} // sessionSteps is the forward-step table for an admitted writer session // (claim Plan 9 D1, the moves ruling H3 option A). The session's selection // binds op id, kind, session, stamp and generation, not phase. A phase change // keeps the session only along its kind's row, only forward and one step at a // time. A kind with no row, every backward or skipped step and every change of op or kind // still ends the session, exactly as main's phase equality did. // // landing: the probe's admitted session becomes the serving session after the // commit and the open (moves §7.9 step 2: "the target serves on the session // its probe was admitted with"; §7.14's rows "landing: probing, probed" and // "landing, committed, not yet finalized" share the flip's op.exec handover). // // relocating: the target's cold probe is admitted in probing and, once the // probe passes, serves on that same session in the op's serving phase (claim // §4's relocating phase table, claim Plan 11 R1). probe-failed is not on the // row: a failed probe's session ends. var sessionSteps = map[string][]string{ KindLanding: {PhaseLandingProbing, PhaseLandingProbed, PhaseLandingCommitted}, KindRelocating: {PhaseRelocatingProbing, PhaseRelocatingServing}, KindReverting: recoverySessionRow, KindRestoring: recoverySessionRow, } // SessionContinues reports whether a writer session last checked while the op // of this kind was at phase from may keep working now that it is at phase to: // the same phase or the adjacent forward step of its row, never a skip (M9). // This is transition authority. A reader that missed a step uses // SessionObserves, comparing against the last phase it checked. func SessionContinues(kind, from, to string) bool { if from == to { return true } row := sessionSteps[kind] for i := 0; i+1 < len(row); i++ { if row[i] == from { return row[i+1] == to } } return false } // SessionObserves reports whether a writer session last checked at phase from // may observe the op at phase to: the same phase or any later phase on kind's // row, so a session that missed an intermediate phase can still catch up. It // grants observation only. Callers also bind op id, kind, session, stamp and // generation, and every transition that changes the claim is still guarded by // SessionContinues (KeepWriterSession), which never skips a step. func SessionObserves(kind, from, to string) bool { return SessionReaches(kind, from, to) } // SessionPhase reports whether phase is on kind's session row: a phase at // which an admitted writer session of that kind may be working. func SessionPhase(kind, phase string) bool { for _, p := range sessionSteps[kind] { if p == phase { return true } } return false } // SessionReaches reports whether to is from or a later phase on kind's // session row. It identifies a session for teardown and grants no write // authority: a stop names the session at any phase it went on to reach, never // at an earlier one. Write authority is SessionContinues. func SessionReaches(kind, from, to string) bool { if from == to { return true } at := -1 for i, p := range sessionSteps[kind] { if p == from { at = i } if p == to { return at >= 0 } } return false } // KeepWriterSession guards a phase CAS that the admitted writer session must // survive, such as the landing's probed and committed steps. fn runs first. // Afterwards the op id, kind and executor and the holder's incarnation, // session, stamp, generation and writer box must be exactly as before, and the // phase must be the same or the adjacent forward step (SessionContinues). Anything else // is ErrWrongState, so the CAS writes nothing. The commit therefore cannot // move the generation (moves §7.9 step 1, R10-3) or start a new session. func KeepWriterSession(opID string, fn func(*Claim) error) func(*Claim) error { return func(c *Claim) error { if err := RequireOp(opID)(c); err != nil { return err } if fn == nil || c.Holder == nil { return fmt.Errorf("%w: a session-keeping step names its transition and its holder", ErrMalformed) } kind, phase, exec := c.Op.Kind, c.Op.Phase, c.Op.Exec before := *c.Holder if err := fn(c); err != nil { return err } after := c.Holder if after == nil || c.Op.ID != opID || c.Op.Kind != kind || c.Op.Exec != exec || after.Incarnation != before.Incarnation || after.Session != before.Session || after.Stamp != before.Stamp || after.Generation != before.Generation || after.WriterBox != before.WriterBox || !SessionContinues(kind, phase, c.Op.Phase) { 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) } return nil } }