mirror of https://github.com/apache/cassandra
5.5 KiB
5.5 KiB
Protocol Specification Patterns
Protocol Structure Template
A protocol spec typically has:
- State variables for each participant
- Message types as sets of records
- Actions for each protocol step (one per label/action)
- Invariants for safety properties
- Liveness for progress guarantees
---- MODULE ProtocolName ----
EXTENDS Integers, Sequences, FiniteSets, TLC
CONSTANTS Nodes, NULL, MaxMsgs
VARIABLES
node_state, \* [Nodes -> StateSet]
msgs, \* set or sequence of messages
history \* auxiliary variable for properties
vars == <<node_state, msgs, history>>
\* --- Message Types ---
MsgType == [type: {"req", "ack", "nack"}, from: Nodes, to: Nodes, data: DataType]
\* --- Helpers ---
Send(m) == msgs' = msgs \union {m}
Discard(m) == msgs' = msgs \ {m}
\* --- Actions ---
Init ==
/\ node_state = [n \in Nodes |-> "idle"]
/\ msgs = {}
/\ history = <<>>
SendRequest(n) ==
/\ node_state[n] = "idle"
/\ node_state' = [node_state EXCEPT ![n] = "waiting"]
/\ \E dest \in Nodes \ {n}:
Send([type |-> "req", from |-> n, to |-> dest, data |-> "payload"])
/\ UNCHANGED history
HandleRequest(n) ==
/\ \E m \in msgs:
/\ m.type = "req" /\ m.to = n
/\ node_state' = [node_state EXCEPT ![n] = "processing"]
/\ Send([type |-> "ack", from |-> n, to |-> m.from, data |-> m.data])
/\ Discard(m) \* careful: updating msgs twice — may need to combine
/\ UNCHANGED history
Next == \E n \in Nodes: SendRequest(n) \/ HandleRequest(n)
\* --- Properties ---
TypeInvariant ==
/\ node_state \in [Nodes -> {"idle", "waiting", "processing", "done"}]
/\ msgs \subseteq MsgType
Safety == \* domain-specific
Liveness == \A n \in Nodes: node_state[n] = "waiting" ~> node_state[n] = "done"
Spec == Init /\ [][Next]_vars /\ \A n \in Nodes: WF_vars(Next)
====
Request-Response Protocol
\* Client sends request, server responds
\* Invariant: every response corresponds to a prior request
VARIABLES
client_state, \* [Clients -> {"idle", "waiting", "done"}]
server_state, \* [Servers -> {"idle", "processing"}]
requests, \* set of request messages in flight
responses, \* set of response messages in flight
request_log \* auxiliary: tracks all requests ever sent
SendRequest(c) ==
/\ client_state[c] = "idle"
/\ client_state' = [client_state EXCEPT ![c] = "waiting"]
/\ \E s \in Servers:
requests' = requests \union {[from |-> c, to |-> s, id |-> next_id]}
/\ request_log' = request_log \union {[from |-> c, id |-> next_id]}
/\ UNCHANGED <<server_state, responses>>
HandleRequest(s) ==
/\ \E req \in requests:
/\ req.to = s
/\ server_state' = [server_state EXCEPT ![s] = "processing"]
/\ responses' = responses \union {[from |-> s, to |-> req.from, id |-> req.id]}
/\ requests' = requests \ {req}
/\ UNCHANGED <<client_state, request_log>>
\* Safety: every response has a matching request
ResponseMatchesRequest ==
\A resp \in responses:
\E req \in request_log: req.id = resp.id /\ req.from = resp.to
Handshake Protocols
\* Three-phase handshake (like TCP SYN/SYN-ACK/ACK)
VARIABLE
conn_state \* [Nodes \X Nodes -> {"closed", "syn_sent", "syn_rcvd", "established"}]
Init == conn_state = [p \in Nodes \X Nodes |-> "closed"]
SynSend(a, b) ==
/\ conn_state[<<a,b>>] = "closed"
/\ conn_state' = [conn_state EXCEPT ![<<a,b>>] = "syn_sent"]
/\ msgs' = msgs \union {[type |-> "SYN", from |-> a, to |-> b]}
SynAck(b, a) ==
/\ \E m \in msgs: m.type = "SYN" /\ m.from = a /\ m.to = b
/\ conn_state[<<b,a>>] = "closed"
/\ conn_state' = [conn_state EXCEPT ![<<b,a>>] = "syn_rcvd"]
/\ msgs' = msgs \union {[type |-> "SYN_ACK", from |-> b, to |-> a]}
Ack(a, b) ==
/\ \E m \in msgs: m.type = "SYN_ACK" /\ m.from = b /\ m.to = a
/\ conn_state[<<a,b>>] = "syn_sent"
/\ conn_state' = [conn_state EXCEPT ![<<a,b>>] = "established"]
/\ msgs' = msgs \union {[type |-> "ACK", from |-> a, to |-> b]}
Token-Based Protocols
\* Mutual exclusion via token passing
VARIABLES
token_holder, \* Nodes \union {NULL}
in_cs \* [Nodes -> BOOLEAN]
TokenPass(from, to) ==
/\ token_holder = from
/\ ~in_cs[from]
/\ token_holder' = to
/\ UNCHANGED in_cs
EnterCS(n) ==
/\ token_holder = n
/\ ~in_cs[n]
/\ in_cs' = [in_cs EXCEPT ![n] = TRUE]
/\ UNCHANGED token_holder
ExitCS(n) ==
/\ in_cs[n]
/\ in_cs' = [in_cs EXCEPT ![n] = FALSE]
/\ UNCHANGED token_holder
\* Safety: at most one node in CS
MutualExclusion ==
\A n1, n2 \in Nodes:
(in_cs[n1] /\ in_cs[n2]) => n1 = n2
Gossip Protocol
VARIABLES
knowledge \* [Nodes -> SUBSET Information]
Init == knowledge = [n \in Nodes |-> {info_n}] \* each knows its own info
Gossip(a, b) ==
/\ knowledge' = [knowledge EXCEPT
![a] = @ \union knowledge[b],
![b] = @ \union knowledge[a]]
\* Convergence: eventually everyone knows everything
AllInfo == UNION {knowledge[n] : n \in Nodes}
EventualConvergence == <>(\A n \in Nodes: knowledge[n] = AllInfo)
Retry with Backoff (Abstract)
VARIABLES
attempt, \* [Agents -> Nat]
status \* [Agents -> {"pending", "success", "failed"}]
Retry(a) ==
/\ status[a] = "failed"
/\ attempt[a] < MaxRetries
/\ attempt' = [attempt EXCEPT ![a] = @ + 1]
/\ status' = [status EXCEPT ![a] = "pending"]
Succeed(a) ==
/\ status[a] = "pending"
/\ status' = [status EXCEPT ![a] = "success"]
/\ UNCHANGED attempt
Fail(a) ==
/\ status[a] = "pending"
/\ status' = [status EXCEPT ![a] = "failed"]
/\ UNCHANGED attempt