Send `req_bytes` to every shard listed in `params` and collect responses. Each shard RPC runs concurrently via `FuturesUnordered`. Per-shard timeouts are enforced by `tokio::time::timeout`. The circuit breaker is checked before each call and updated on success/failure. Any shard error causes the whole fan-out to return `Err` — the coordinator decides whether to retry. Returns `Vec<(shard_id, res
(
params: &FanOutParams,
opcode: u32,
req_bytes: &[u8],
dispatch: &Arc<dyn ShardRpcDispatch>,
circuit_breaker: &CircuitBreaker,
)
| 107 | /// |
| 108 | /// Returns `Vec<(shard_id, response_payload_bytes)>` in arrival order. |
| 109 | pub async fn fan_out( |
| 110 | params: &FanOutParams, |
| 111 | opcode: u32, |
| 112 | req_bytes: &[u8], |
| 113 | dispatch: &Arc<dyn ShardRpcDispatch>, |
| 114 | circuit_breaker: &CircuitBreaker, |
| 115 | ) -> Result<Vec<(u32, Vec<u8>)>> { |
| 116 | if params.shard_ids.is_empty() { |
| 117 | return Ok(Vec::new()); |
| 118 | } |
| 119 | |
| 120 | // Build one future per shard and collect via FuturesUnordered for |
| 121 | // true concurrency (no sequential .await loop). |
| 122 | let mut futs = futures::stream::FuturesUnordered::new(); |
| 123 | |
| 124 | for &shard_id in ¶ms.shard_ids { |
| 125 | // Circuit-breaker gate: treat shard_id as the peer identifier. |
| 126 | circuit_breaker.check(shard_id as u64)?; |
| 127 | |
| 128 | let env = VShardEnvelope::new( |
| 129 | msg_type_from_opcode(opcode)?, |
| 130 | params.source_node, |
| 131 | 0, // target_node resolved by the dispatch impl |
| 132 | shard_id, |
| 133 | req_bytes.to_vec(), |
| 134 | ); |
| 135 | let timeout_ms = params.timeout_ms; |
| 136 | let dispatch = Arc::clone(dispatch); |
| 137 | let cb_shard = shard_id; |
| 138 | |
| 139 | futs.push(async move { |
| 140 | match call_with_wrong_owner_retry(&dispatch, env, timeout_ms).await { |
| 141 | Ok(resp) => Ok((cb_shard, resp.payload)), |
| 142 | Err(e) => Err((cb_shard, e)), |
| 143 | } |
| 144 | }); |
| 145 | } |
| 146 | |
| 147 | let mut results = Vec::with_capacity(params.shard_ids.len()); |
| 148 | while let Some(outcome) = futs.next().await { |
| 149 | match outcome { |
| 150 | Ok((shard_id, payload)) => { |
| 151 | circuit_breaker.record_success(shard_id as u64); |
| 152 | results.push((shard_id, payload)); |
| 153 | } |
| 154 | Err((shard_id, e)) => { |
| 155 | circuit_breaker.record_failure(shard_id as u64); |
| 156 | return Err(e); |
| 157 | } |
| 158 | } |
| 159 | } |
| 160 | |
| 161 | Ok(results) |
| 162 | } |
| 163 | |
| 164 | /// Send a distinct payload to each shard and collect responses. |
| 165 | /// |
no test coverage detected