Skip to main content

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}