Decide whether this node should bootstrap a new cluster. Returns `true` iff any of the following hold: 1. `config.force_bootstrap` is set (operator escape hatch). 2. This node's `listen_addr` is the lexicographically smallest entry in `config.seed_nodes` — the deterministic elected bootstrapper. Returns `false` in every other case, including when the designated bootstrapper is currently unreach
(config: &ClusterConfig, transport: &NexarTransport)
| 82 | /// and logging, not a decision input. With a deterministic rule there |
| 83 | /// is nothing to race on. |
| 84 | pub(super) async fn should_bootstrap(config: &ClusterConfig, transport: &NexarTransport) -> bool { |
| 85 | if config.force_bootstrap { |
| 86 | info!( |
| 87 | node_id = config.node_id, |
| 88 | listen_addr = %config.listen_addr, |
| 89 | "force_bootstrap flag set — bootstrapping unconditionally" |
| 90 | ); |
| 91 | return true; |
| 92 | } |
| 93 | |
| 94 | let designated = match designated_bootstrapper(&config.seed_nodes) { |
| 95 | Some(addr) => addr, |
| 96 | None => { |
| 97 | // Empty seed list — caller already treats this as an |
| 98 | // implicit single-node bootstrap (`seed_nodes = [self]` |
| 99 | // fallback in `TestNode::spawn` / the main binary's |
| 100 | // config layer). Bootstrap is the only reasonable choice. |
| 101 | return true; |
| 102 | } |
| 103 | }; |
| 104 | |
| 105 | if designated == config.listen_addr { |
| 106 | info!( |
| 107 | node_id = config.node_id, |
| 108 | listen_addr = %config.listen_addr, |
| 109 | "this node is the designated bootstrapper" |
| 110 | ); |
| 111 | return true; |
| 112 | } |
| 113 | |
| 114 | info!( |
| 115 | node_id = config.node_id, |
| 116 | listen_addr = %config.listen_addr, |
| 117 | %designated, |
| 118 | "deferring to designated bootstrapper; probing for liveness" |
| 119 | ); |
| 120 | |
| 121 | // Non-blocking best-effort probe — each attempt is bounded by |
| 122 | // PROBE_TIMEOUT, so the total window is at most |
| 123 | // MAX_PROBE_ATTEMPTS * (PROBE_TIMEOUT + PROBE_INTERVAL). Exits |
| 124 | // early as soon as the bootstrapper answers, so the common case is |
| 125 | // a single sub-second round trip. |
| 126 | for attempt in 0..MAX_PROBE_ATTEMPTS { |
| 127 | let probe_result = tokio::time::timeout( |
| 128 | PROBE_TIMEOUT, |
| 129 | ping_probe(designated, transport, config.node_id), |
| 130 | ) |
| 131 | .await; |
| 132 | |
| 133 | match probe_result { |
| 134 | Ok(Ok(())) => { |
| 135 | info!( |
| 136 | node_id = config.node_id, |
| 137 | %designated, |
| 138 | attempt, |
| 139 | "designated bootstrapper is up" |
| 140 | ); |
| 141 | return false; |
no test coverage detected