1use std::fmt;
19use std::future::Future;
20use std::net::SocketAddr;
21use std::time::Duration;
22
23use futures::StreamExt;
24use futures::stream::FuturesUnordered;
25
26use crate::resolve::Candidates;
27
28#[derive(Debug, Clone, PartialEq, Eq)]
34pub struct Failure<E> {
35 pub addr: SocketAddr,
37
38 pub error: E,
40}
41
42impl<E: fmt::Display> fmt::Display for Failure<E> {
43 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44 write!(f, "{}: {}", self.addr, self.error)
45 }
46}
47
48impl<E: std::error::Error + 'static> std::error::Error for Failure<E> {
49 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
50 Some(&self.error)
51 }
52}
53
54pub(crate) trait Aggregate: Sized {
59 fn aggregate(failures: Vec<Failure<Self>>) -> Self;
64
65 fn resolve(error: Option<std::io::Error>) -> Self;
69}
70
71pub(crate) fn describe<E: fmt::Display>(failures: &[Failure<E>]) -> String {
73 failures.iter().map(|f| f.to_string()).collect::<Vec<_>>().join("; ")
74}
75
76pub(crate) async fn race<C, E, F, Fut>(mut candidates: Candidates, delay: Duration, mut dial: F) -> Result<C, E>
100where
101 F: FnMut(SocketAddr) -> Fut,
102 Fut: Future<Output = Result<C, E>>,
103 E: Aggregate + fmt::Display,
104{
105 let mut attempts = FuturesUnordered::new();
106 let mut failures: Vec<(usize, Failure<E>)> = Vec::new();
107 let mut exhausted = false;
108
109 let mut ready = tokio::time::Instant::now();
112
113 let mut next_index = 0;
114 let mut start = |addr: SocketAddr, attempts: &mut FuturesUnordered<_>| {
115 let index = next_index;
116 next_index += 1;
117 tracing::debug!(%addr, index, "dialing");
118 let attempt = dial(addr);
119 attempts.push(async move { (index, addr, attempt.await) });
120 };
121
122 loop {
123 if exhausted && attempts.is_empty() {
124 if failures.is_empty() {
125 return Err(E::resolve(candidates.failure()));
126 }
127
128 failures.sort_by_key(|(index, _)| *index);
131 return Err(collapse(failures.into_iter().map(|(_, failure)| failure).collect()));
132 }
133
134 tokio::select! {
135 biased;
138
139 Some((index, addr, res)) = attempts.next(), if !attempts.is_empty() => {
140 match res {
141 Ok(conn) => {
142 tracing::debug!(%addr, index, "connected");
143 return Ok(conn);
144 }
145 Err(err) => {
146 tracing::debug!(%addr, index, %err, "connection attempt failed");
151 failures.push((index, Failure { addr, error: err }));
152 ready = tokio::time::Instant::now();
155 }
156 }
157 }
158
159 addr = pull(&mut candidates, ready), if !exhausted => {
162 match addr {
163 Some(addr) => {
164 start(addr, &mut attempts);
165 ready = tokio::time::Instant::now() + delay;
166 }
167 None => exhausted = true,
168 }
169 }
170 }
171 }
172}
173
174async fn pull(candidates: &mut Candidates, ready: tokio::time::Instant) -> Option<SocketAddr> {
179 tokio::time::sleep_until(ready).await;
180 candidates.next().await
181}
182
183fn collapse<E: Aggregate>(mut failures: Vec<Failure<E>>) -> E {
186 match failures.len() {
187 1 => failures.pop().expect("checked len").error,
188 _ => E::aggregate(failures),
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195 use crate::client::DEFAULT_FAILOVER_DELAY;
196 use std::sync::Arc;
197 use std::sync::atomic::{AtomicUsize, Ordering};
198
199 fn addr(s: &str) -> SocketAddr {
200 s.parse().unwrap()
201 }
202
203 #[derive(Debug, PartialEq, Eq)]
206 enum TestError {
207 Dial(&'static str),
208 All(Vec<Failure<TestError>>),
209 }
210
211 impl fmt::Display for TestError {
212 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
213 match self {
214 Self::Dial(err) => write!(f, "{err}"),
215 Self::All(failures) => write!(f, "all {} attempts failed: {}", failures.len(), describe(failures)),
216 }
217 }
218 }
219
220 impl Aggregate for TestError {
221 fn aggregate(failures: Vec<Failure<Self>>) -> Self {
222 Self::All(failures)
223 }
224
225 fn resolve(error: Option<std::io::Error>) -> Self {
226 match error {
227 Some(_) => Self::Dial("lookup failed"),
228 None => Self::Dial("no addresses"),
229 }
230 }
231 }
232
233 fn failed(dest: &str, err: &'static str) -> Failure<TestError> {
234 Failure {
235 addr: addr(dest),
236 error: TestError::Dial(err),
237 }
238 }
239
240 #[tokio::test(start_paused = true)]
241 async fn first_success_returns_immediately() {
242 let dials = Arc::new(AtomicUsize::new(0));
243 let counter = dials.clone();
244 let res: Result<&str, TestError> = race(
245 Candidates::fixed([addr("1.1.1.1:1"), addr("2.2.2.2:2")]),
246 DEFAULT_FAILOVER_DELAY,
247 move |_| {
248 counter.fetch_add(1, Ordering::SeqCst);
249 async { Ok("winner") }
250 },
251 )
252 .await;
253 assert_eq!(res, Ok("winner"));
254 assert_eq!(dials.load(Ordering::SeqCst), 1, "no second dial after a fast success");
255 }
256
257 #[tokio::test(start_paused = true)]
258 async fn second_wins_when_first_hangs() {
259 let start = tokio::time::Instant::now();
260 let res: Result<&str, TestError> = race(
261 Candidates::fixed([addr("1.1.1.1:1"), addr("2.2.2.2:2")]),
262 DEFAULT_FAILOVER_DELAY,
263 |dest| async move {
264 if dest == addr("1.1.1.1:1") {
265 std::future::pending().await
266 } else {
267 Ok("second")
268 }
269 },
270 )
271 .await;
272 assert_eq!(res, Ok("second"));
273 assert_eq!(
274 start.elapsed(),
275 DEFAULT_FAILOVER_DELAY,
276 "second dial waits out the stagger"
277 );
278 }
279
280 #[tokio::test(start_paused = true)]
281 async fn failure_starts_the_next_attempt_immediately() {
282 let start = tokio::time::Instant::now();
283 let res: Result<&str, TestError> = race(
284 Candidates::fixed([addr("1.1.1.1:1"), addr("2.2.2.2:2")]),
285 DEFAULT_FAILOVER_DELAY,
286 |dest| async move {
287 if dest == addr("1.1.1.1:1") {
288 Err(TestError::Dial("boom"))
289 } else {
290 Ok("second")
291 }
292 },
293 )
294 .await;
295 assert_eq!(res, Ok("second"));
296 assert_eq!(start.elapsed(), Duration::ZERO, "failure must not wait for the timer");
297 }
298
299 #[tokio::test(start_paused = true)]
303 async fn all_failures_are_reported_when_the_preferred_fails_first() {
304 let res: Result<&str, TestError> = race(
305 Candidates::fixed([addr("1.1.1.1:1"), addr("2.2.2.2:2")]),
306 Duration::from_millis(10),
307 |dest| async move {
308 if dest == addr("1.1.1.1:1") {
309 Err(TestError::Dial("network unreachable"))
310 } else {
311 tokio::time::sleep(Duration::from_secs(1)).await;
312 Err(TestError::Dial("invalid peer certificate"))
313 }
314 },
315 )
316 .await;
317 assert_eq!(
318 res,
319 Err(TestError::All(vec![
320 failed("1.1.1.1:1", "network unreachable"),
321 failed("2.2.2.2:2", "invalid peer certificate"),
322 ]))
323 );
324 }
325
326 #[tokio::test(start_paused = true)]
331 async fn all_failures_are_reported_when_the_preferred_times_out_last() {
332 let res: Result<&str, TestError> = race(
333 Candidates::fixed([addr("1.1.1.1:1"), addr("2.2.2.2:2")]),
334 Duration::from_millis(10),
335 |dest| async move {
336 if dest == addr("1.1.1.1:1") {
337 tokio::time::sleep(Duration::from_secs(30)).await;
338 Err(TestError::Dial("timed out"))
339 } else {
340 Err(TestError::Dial("invalid peer certificate"))
341 }
342 },
343 )
344 .await;
345 assert_eq!(
346 res,
347 Err(TestError::All(vec![
348 failed("1.1.1.1:1", "timed out"),
349 failed("2.2.2.2:2", "invalid peer certificate"),
350 ]))
351 );
352 }
353
354 #[tokio::test(start_paused = true)]
357 async fn a_lone_failure_is_returned_unwrapped() {
358 let res: Result<&str, TestError> = race(
359 Candidates::fixed([addr("1.1.1.1:1")]),
360 DEFAULT_FAILOVER_DELAY,
361 |_| async { Err(TestError::Dial("invalid peer certificate")) },
362 )
363 .await;
364 assert_eq!(res, Err(TestError::Dial("invalid peer certificate")));
365 }
366
367 #[test]
368 fn describe_lists_every_attempt() {
369 let failures = [failed("1.1.1.1:1", "timed out"), failed("2.2.2.2:2", "bad cert")];
370 assert_eq!(describe(&failures), "1.1.1.1:1: timed out; 2.2.2.2:2: bad cert");
371 }
372
373 #[tokio::test(start_paused = true)]
374 async fn losers_are_dropped_on_success() {
375 struct Guard(Arc<AtomicUsize>);
377 impl Drop for Guard {
378 fn drop(&mut self) {
379 self.0.fetch_add(1, Ordering::SeqCst);
380 }
381 }
382
383 let dropped = Arc::new(AtomicUsize::new(0));
384 let count = dropped.clone();
385 let res: Result<&str, TestError> = race(
386 Candidates::fixed([addr("1.1.1.1:1"), addr("2.2.2.2:2")]),
387 Duration::ZERO,
388 move |dest| {
389 let guard = Guard(count.clone());
390 async move {
391 if dest == addr("1.1.1.1:1") {
392 let _guard = guard;
393 std::future::pending().await
394 } else {
395 drop(guard);
396 tokio::time::sleep(Duration::from_millis(1)).await;
397 Ok("second")
398 }
399 }
400 },
401 )
402 .await;
403 assert_eq!(res, Ok("second"));
404 assert_eq!(dropped.load(Ordering::SeqCst), 2, "the hung attempt was not aborted");
405 }
406
407 #[tokio::test(start_paused = true)]
408 async fn zero_delay_dials_all_at_once() {
409 let start = tokio::time::Instant::now();
410 let res: Result<&str, TestError> = race(
411 Candidates::fixed([addr("1.1.1.1:1"), addr("2.2.2.2:2")]),
412 Duration::ZERO,
413 |dest| async move {
414 if dest == addr("1.1.1.1:1") {
415 std::future::pending().await
416 } else {
417 Ok("second")
418 }
419 },
420 )
421 .await;
422 assert_eq!(res, Ok("second"));
423 assert_eq!(start.elapsed(), Duration::ZERO);
424 }
425
426 #[tokio::test(start_paused = true)]
429 async fn an_empty_resolution_reports_why() {
430 let res: Result<&str, TestError> = race(Candidates::fixed([]), DEFAULT_FAILOVER_DELAY, |_| async {
431 unreachable!("dialed without an address")
432 })
433 .await;
434 assert_eq!(res, Err(TestError::Dial("no addresses")));
435 }
436
437 #[tokio::test(start_paused = true)]
441 async fn dials_the_first_address_to_resolve() {
442 let start = tokio::time::Instant::now();
443 let res: Result<&str, TestError> = race(
444 Candidates::slow(
445 (&[], Duration::from_secs(30)),
446 (&[addr("1.1.1.1:1")], Duration::from_millis(100)),
447 ),
448 DEFAULT_FAILOVER_DELAY,
449 |_| async { Ok("winner") },
450 )
451 .await;
452 assert_eq!(res, Ok("winner"));
453 assert_eq!(
454 start.elapsed(),
455 Duration::from_millis(100),
456 "waited for the other query"
457 );
458 }
459
460 #[tokio::test(start_paused = true)]
466 async fn a_late_candidate_starts_as_soon_as_it_resolves() {
467 let start = tokio::time::Instant::now();
468 let res: Result<&str, TestError> = race(
469 Candidates::slow(
470 (&[addr("[2001:db8::1]:1"), addr("1.1.1.1:1")], Duration::from_secs(1)),
471 (&[addr("1.1.1.1:1")], Duration::ZERO),
472 ),
473 DEFAULT_FAILOVER_DELAY,
474 |dest| async move {
475 match dest.is_ipv6() {
476 true => Ok("second"),
477 false => std::future::pending().await,
478 }
479 },
480 )
481 .await;
482 assert_eq!(res, Ok("second"));
483 assert_eq!(start.elapsed(), Duration::from_secs(1));
484 }
485}