Raft::Node(T)
Inherits Raft::AdminOps < Raft::StatusSource < Reference < Object
Implements the Raft consensus protocol for a single peer in a single
group. Generic over T, the application's command type.
Threading contract
A Node is single-fiber. All five public entry points — tick,
step, propose, take_messages, and read_index — and all
registration methods (on_role_change, on_configuration_change,
on_configuration_applied) must be called from the same fiber that
owns the node's loop. Internal state (@current_term, @log,
@peers, @outbox, @pending_reads, @pending_apply) is mutated
without locks because of this invariant.
Callbacks fire on that same fiber and must therefore be non-blocking — channel a signal to your own fiber if you need to do real work.
If you need to call propose from a different fiber (e.g. an HTTP
handler), send the request over a channel and have the loop fiber
do the actual call. The KV and queue examples demonstrate this
pattern in start_group_loop.
Constructors
Instance methods
Add a new node to the cluster as a learner, or re-admit a returning member.
- New id: adds as Learner, returns true.
- Known id, different non-empty address: updates the stored address and replicates the new configuration, returns true.
- Known id, same address (or empty address passed): no-op, returns true. Returns false if not leader, if a configuration change is already in flight, or if node_id == @id.
Bootstrap this node as a single-node cluster. Only works when the node has no peers (fresh start).
T-free log scalars for StatusSource — let status/metrics consumers read log shape without holding a reference to the mutable Log(T).
Fires whenever the peer list changes — both when config entries are stored in the log (followers, before commit) and when they're committed (leader). Use this to register transport peer addresses from config entries; followers can route to new peers as soon as they see the entry, without waiting for the cluster commit cycle.
Runs on the Raft fiber; keep it non-blocking. See class docs for the full threading contract.
Fires when a Configuration entry is committed (apply phase). Block receives the new peer list. Use this for actions that should only run after a membership change is durable across a majority — e.g. cascading data-group reconfiguration in a multi-raft setup.
Runs on the Raft fiber; keep it non-blocking. See class docs for the full threading contract.
Called on every role transition (Follower → Candidate, Candidate → Leader, Leader → Follower, etc.). Block receives (old_role, new_role).
The block runs on the Raft fiber — keep it non-blocking. For work that needs to be done off the Raft fiber (e.g., AMQP listener startup on becoming leader), set a flag or signal a channel from the block and let another fiber do the heavy lifting.
Fires on every transition, including intermediate ones (Follower →
Candidate without then becoming Leader). Filter on new_role if you
only care about specific transitions.
Promote a learner to voter. Returns false if not leader or node is not a learner.
Linearizable read primitive. Block fires with the commit_index that is safe to read at, or nil if leadership could not be confirmed.
Behaviour:
- On a non-leader: callback fires synchronously with nil.
- On a standalone leader (no other voters): registered for the apply gate only; fires once @last_applied >= the captured commit_index.
- On a multi-voter leader: waits for a heartbeat-ack quorum to confirm leadership at or after registration time, then waits for the apply gate.
The callback fires with nil in three cases: the node is not the leader at call time; the leader steps down before quorum confirmation; or Config.read_index_timeout_ticks ticks elapse without quorum confirmation. Note: once a read is quorum-confirmed and parked at the apply gate, it has no upper time bound — a wedged state machine will hold reads open indefinitely until the node steps down.
Remove a node from the cluster. Returns false if not leader, node not found, or trying to remove self.