Execute a single tick: drive Raft, dispatch outbound messages, apply commits, promote caught-up learners.
(&self)
| 37 | /// Execute a single tick: drive Raft, dispatch outbound messages, |
| 38 | /// apply commits, promote caught-up learners. |
| 39 | pub(super) fn do_tick(&self) { |
| 40 | // Tick under lock and extract Ready. |
| 41 | let ready = { |
| 42 | let mut mr = self.multi_raft.lock().unwrap_or_else(|p| p.into_inner()); |
| 43 | mr.tick() |
| 44 | }; |
| 45 | |
| 46 | // Dispatch outgoing messages and persist log/HardState first (even if |
| 47 | // ready looks "empty" we still want to run the learner-promotion step |
| 48 | // each tick so a just-caught-up learner is promoted promptly). |
| 49 | if !ready.is_empty() { |
| 50 | let mut ae_batches: BatchMap<u64, Vec<(u64, nodedb_raft::AppendEntriesRequest)>> = |
| 51 | BatchMap::new(); |
| 52 | let mut vote_batches: BatchMap<u64, Vec<(u64, nodedb_raft::RequestVoteRequest)>> = |
| 53 | BatchMap::new(); |
| 54 | |
| 55 | for (group_id, group_ready) in &ready.groups { |
| 56 | for (peer, req) in &group_ready.messages { |
| 57 | ae_batches |
| 58 | .entry(*peer) |
| 59 | .or_default() |
| 60 | .push((*group_id, req.clone())); |
| 61 | } |
| 62 | for (peer, req) in &group_ready.vote_requests { |
| 63 | vote_batches |
| 64 | .entry(*peer) |
| 65 | .or_default() |
| 66 | .push((*group_id, req.clone())); |
| 67 | } |
| 68 | } |
| 69 | |
| 70 | // Dispatch batched AppendEntries — one task per peer. |
| 71 | // |
| 72 | // Each detached task subscribes to the shutdown watch |
| 73 | // and wraps its RPC awaits in `tokio::select!` so a |
| 74 | // `RaftLoop::begin_shutdown` signal (or the `run` loop |
| 75 | // propagating an external shutdown) cancels the |
| 76 | // in-flight QUIC call at the next await point. This |
| 77 | // is what lets graceful shutdown drop the |
| 78 | // `Arc<Mutex<MultiRaft>>` clone promptly and release |
| 79 | // per-group redb locks for an in-process restart. |
| 80 | for (peer, messages) in ae_batches { |
| 81 | let transport = self.transport.clone(); |
| 82 | let mr = self.multi_raft.clone(); |
| 83 | let mut shutdown_rx = self.shutdown_watch.subscribe(); |
| 84 | tokio::spawn(async move { |
| 85 | if *shutdown_rx.borrow() { |
| 86 | return; |
| 87 | } |
| 88 | for (group_id, req) in messages { |
| 89 | tokio::select! { |
| 90 | biased; |
| 91 | _ = shutdown_rx.changed() => return, |
| 92 | rpc = transport.append_entries(peer, req) => { |
| 93 | match rpc { |
| 94 | Ok(resp) => { |
| 95 | let mut mr = |
| 96 | mr.lock().unwrap_or_else(|p| p.into_inner()); |
no test coverage detected