nodedb_cluster/multi_raft/rpc_dispatch.rs
1// SPDX-License-Identifier: BUSL-1.1
2
3//! Inbound RPC dispatch — look up the target group and delegate.
4//!
5//! Also holds the response handlers (`handle_append_entries_response`,
6//! `handle_request_vote_response`) and the helpers for the tick loop
7//! (`snapshot_metadata`, `advance_applied`, `match_index_for`).
8
9use nodedb_raft::{
10 AppendEntriesRequest, AppendEntriesResponse, InstallSnapshotRequest, InstallSnapshotResponse,
11 RequestVoteRequest, RequestVoteResponse, TimeoutNowRequest,
12};
13
14use crate::error::{ClusterError, Result};
15
16use super::core::MultiRaft;
17
18impl MultiRaft {
19 /// Route an AppendEntries RPC to the correct group.
20 pub fn handle_append_entries(
21 &mut self,
22 req: &AppendEntriesRequest,
23 ) -> Result<AppendEntriesResponse> {
24 let node = self
25 .groups
26 .get_mut(&req.group_id)
27 .ok_or(ClusterError::GroupNotFound {
28 group_id: req.group_id,
29 })?;
30 Ok(node.handle_append_entries(req))
31 }
32
33 /// Route a RequestVote RPC to the correct group.
34 pub fn handle_request_vote(&mut self, req: &RequestVoteRequest) -> Result<RequestVoteResponse> {
35 let node = self
36 .groups
37 .get_mut(&req.group_id)
38 .ok_or(ClusterError::GroupNotFound {
39 group_id: req.group_id,
40 })?;
41 Ok(node.handle_request_vote(req))
42 }
43
44 /// Route an InstallSnapshot RPC to the correct group.
45 pub fn handle_install_snapshot(
46 &mut self,
47 req: &InstallSnapshotRequest,
48 ) -> Result<InstallSnapshotResponse> {
49 let node = self
50 .groups
51 .get_mut(&req.group_id)
52 .ok_or(ClusterError::GroupNotFound {
53 group_id: req.group_id,
54 })?;
55 Ok(node.handle_install_snapshot(req)?)
56 }
57
58 /// Route a TimeoutNow RPC to the correct group.
59 ///
60 /// One-way — no response is produced. Silently ignored if the group is
61 /// not mounted on this node (mirrors `handle_request_vote` for absent
62 /// groups). The term+leader_id guard inside `RaftNode::handle_timeout_now`
63 /// remains in place as an additional correctness check.
64 pub fn handle_timeout_now(&mut self, req: &TimeoutNowRequest) {
65 if let Some(node) = self.groups.get_mut(&req.group_id) {
66 node.handle_timeout_now(req);
67 }
68 }
69
70 /// Durably persist a group's HardState (current_term/voted_for) if it
71 /// changed since the last persist. Must run under the `MultiRaft` lock
72 /// before an RPC reply that granted a vote or bumped the term leaves this
73 /// node, so a restart cannot forget the vote and let two leaders form.
74 ///
75 /// No-op when the group is not mounted on this node.
76 pub fn persist_group_hard_state(&mut self, group_id: u64) -> Result<()> {
77 if let Some(node) = self.groups.get_mut(&group_id) {
78 node.persist_hard_state_if_dirty()?;
79 }
80 Ok(())
81 }
82
83 /// Get the current term and snapshot metadata for a group (for building
84 /// InstallSnapshot RPCs).
85 pub fn snapshot_metadata(&self, group_id: u64) -> Result<(u64, u64, u64)> {
86 let node = self
87 .groups
88 .get(&group_id)
89 .ok_or(ClusterError::GroupNotFound { group_id })?;
90 Ok((
91 node.current_term(),
92 node.log_snapshot_index(),
93 node.log_snapshot_term(),
94 ))
95 }
96
97 /// Handle AppendEntries response for a specific group.
98 pub fn handle_append_entries_response(
99 &mut self,
100 group_id: u64,
101 peer: u64,
102 resp: &AppendEntriesResponse,
103 ) -> Result<()> {
104 let node = self
105 .groups
106 .get_mut(&group_id)
107 .ok_or(ClusterError::GroupNotFound { group_id })?;
108 node.handle_append_entries_response(peer, resp);
109 Ok(())
110 }
111
112 /// Handle RequestVote response for a specific group.
113 pub fn handle_request_vote_response(
114 &mut self,
115 group_id: u64,
116 peer: u64,
117 resp: &RequestVoteResponse,
118 ) -> Result<()> {
119 let node = self
120 .groups
121 .get_mut(&group_id)
122 .ok_or(ClusterError::GroupNotFound { group_id })?;
123 node.handle_request_vote_response(peer, resp);
124 Ok(())
125 }
126
127 /// Advance applied index for a group after processing committed entries.
128 ///
129 /// This is the DELIVERY watermark. See [`Self::save_applied_index`] for the
130 /// durable floor a restart resumes from.
131 pub fn advance_applied(&mut self, group_id: u64, applied_to: u64) -> Result<()> {
132 let node = self
133 .groups
134 .get_mut(&group_id)
135 .ok_or(ClusterError::GroupNotFound { group_id })?;
136 node.advance_applied(applied_to);
137 Ok(())
138 }
139
140 /// Durably record `applied_to` as the group's applied floor.
141 ///
142 /// `applied_to` MUST name an entry whose state-machine effects are already
143 /// durable — for data groups, one whose redo record the WAL has fsynced.
144 /// The next boot resumes delivery at `applied_to + 1`, so this is what
145 /// keeps WAL replay and Raft replay from applying the same entry twice.
146 ///
147 /// Monotonic per group: an index at or below the current floor is a no-op.
148 pub fn save_applied_index(&mut self, group_id: u64, applied_to: u64) -> Result<()> {
149 let node = self
150 .groups
151 .get_mut(&group_id)
152 .ok_or(ClusterError::GroupNotFound { group_id })?;
153 node.save_durable_applied_index(applied_to)?;
154 Ok(())
155 }
156
157 /// Query a peer's match_index from a specific Raft group's leader state.
158 pub fn match_index_for(&self, group_id: u64, peer: u64) -> Option<u64> {
159 self.groups.get(&group_id)?.match_index_for(peer)
160 }
161
162 /// Read the locally-applied index for a Raft group hosted on this
163 /// node. Returns `None` if the group is not mounted here.
164 ///
165 /// Used by the tick loop to mirror `last_applied` into the
166 /// per-group [`crate::applied_watcher::AppliedIndexWatcher`] —
167 /// covers both the regular apply path and the snapshot-install
168 /// path (which sets `last_applied = last_included_index`
169 /// directly without producing committed entries).
170 pub fn last_applied(&self, group_id: u64) -> Option<u64> {
171 self.groups.get(&group_id).map(|n| n.last_applied())
172 }
173
174 /// `(group_id, last_applied)` pairs for every locally-mounted
175 /// group. Cheap O(groups) snapshot — groups are few (one
176 /// metadata + handful of vshard groups per node).
177 pub fn applied_indices(&self) -> Vec<(u64, u64)> {
178 self.groups
179 .iter()
180 .map(|(gid, node)| (*gid, node.last_applied()))
181 .collect()
182 }
183}