Spawn a single `CalvinTestNode`. `all_node_ids` is the full 3-node voter set; `node_idx` is the index of this node in that list. Registers the sequencer group **before** spawning background Raft tasks. `seed_nodes` must be empty for the bootstrap node (index 0) and contain the bootstrap node's address for nodes 1 and 2.
(
node_idx: usize,
all_node_ids: &[u64],
seed_nodes: Vec<SocketAddr>,
)
| 202 | /// `seed_nodes` must be empty for the bootstrap node (index 0) and contain |
| 203 | /// the bootstrap node's address for nodes 1 and 2. |
| 204 | async fn spawn_one_calvin_node( |
| 205 | node_idx: usize, |
| 206 | all_node_ids: &[u64], |
| 207 | seed_nodes: Vec<SocketAddr>, |
| 208 | ) -> Result<CalvinTestNode, Box<dyn std::error::Error + Send + Sync>> { |
| 209 | let node_id = all_node_ids[node_idx]; |
| 210 | let transport = Arc::new(NexarTransport::with_timeout( |
| 211 | node_id, |
| 212 | "127.0.0.1:0".parse().unwrap(), |
| 213 | Duration::from_secs(4), |
| 214 | TransportCredentials::Insecure, |
| 215 | )?); |
| 216 | |
| 217 | let data_dir = tempfile::tempdir()?; |
| 218 | let data_dir_path = data_dir.path().to_path_buf(); |
| 219 | let catalog = Arc::new(ClusterCatalog::open(&data_dir_path.join("cluster.redb"))?); |
| 220 | |
| 221 | let listen_addr = transport.local_addr(); |
| 222 | let seeds = if seed_nodes.is_empty() { |
| 223 | vec![listen_addr] |
| 224 | } else { |
| 225 | seed_nodes |
| 226 | }; |
| 227 | |
| 228 | let config = ClusterConfig { |
| 229 | node_id, |
| 230 | listen_addr, |
| 231 | seed_nodes: seeds, |
| 232 | num_groups: 2, |
| 233 | replication_factor: 3, |
| 234 | data_dir: data_dir_path.clone(), |
| 235 | force_bootstrap: false, |
| 236 | join_retry: nodedb_cluster::JoinRetryPolicy { |
| 237 | max_attempts: 8, |
| 238 | max_backoff_secs: 2, |
| 239 | }, |
| 240 | swim_udp_addr: None, |
| 241 | election_timeout_min: Duration::from_millis(150), |
| 242 | election_timeout_max: Duration::from_millis(300), |
| 243 | install_snapshot_chunk_bytes: 4 * 1024 * 1024, |
| 244 | orphan_partial_max_age_secs: 300, |
| 245 | }; |
| 246 | |
| 247 | let lifecycle = ClusterLifecycleTracker::new(); |
| 248 | let state = start_cluster(&config, &catalog, Arc::clone(&transport), &lifecycle).await?; |
| 249 | lifecycle.to_ready(state.topology.read().map(|t| t.node_count()).unwrap_or(0)); |
| 250 | |
| 251 | // Unwrap MultiRaft immediately — no other Arc clones of it yet. |
| 252 | let mut multi_raft_value = Arc::try_unwrap(state.multi_raft) |
| 253 | .unwrap_or_else(|_| { |
| 254 | panic!("MultiRaft should have no extra strong Arc owners in Calvin test setup") |
| 255 | }) |
| 256 | .into_inner() |
| 257 | .unwrap_or_else(|p| p.into_inner()); |
| 258 | |
| 259 | // Register the sequencer group before spawning any tasks. |
| 260 | // This is the race-fix: we do this synchronously here so the 150 ms |
| 261 | // election timeout cannot fire before the group is registered on |
no test coverage detected