use std::{future::Future, net::SocketAddr};
use crate::address::HostWithPort;
use rama_core::{
error::{BoxError, BoxErrorExt as _},
extensions::Extensions,
futures::{
Stream, StreamExt as _,
stream::{BoxStream, FuturesUnordered},
},
};
use rama_macros::Extension;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Extension)]
#[extension(tags(net))]
pub struct ConnectorTarget(pub HostWithPort);
pub trait AddressCandidates: Send + Sync + 'static {
fn stream<'a>(
&'a self,
extensions: &'a Extensions,
) -> BoxStream<'a, Result<SocketAddr, BoxError>>;
}
#[derive(Extension)]
#[extension(tags(net))]
pub struct ConnectorTargetStream(pub Box<dyn AddressCandidates>);
impl ConnectorTargetStream {
#[must_use]
pub fn new(candidates: impl AddressCandidates) -> Self {
Self(Box::new(candidates))
}
pub fn stream<'a>(
&'a self,
extensions: &'a Extensions,
) -> BoxStream<'a, Result<SocketAddr, BoxError>> {
self.0.stream(extensions)
}
}
impl core::fmt::Debug for ConnectorTargetStream {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("ConnectorTargetStream")
.finish_non_exhaustive()
}
}
pub async fn race_connect<S, C, F, Fut>(
candidates: S,
max_in_flight: usize,
dial: F,
) -> Result<(SocketAddr, C), BoxError>
where
S: Stream<Item = Result<SocketAddr, BoxError>>,
F: Fn(SocketAddr) -> Fut + Sync,
Fut: Future<Output = Result<C, BoxError>> + Send,
C: Send,
{
let max_in_flight = max_in_flight.max(1);
let dial = &dial;
let mut candidates = std::pin::pin!(candidates);
let mut in_flight = FuturesUnordered::new();
let mut candidates_done = false;
let mut last_err: Option<BoxError> = None;
enum Event<C> {
Candidate(Option<Result<SocketAddr, BoxError>>),
Dialed(Option<(SocketAddr, Result<C, BoxError>)>),
}
loop {
if candidates_done && in_flight.is_empty() {
break;
}
let event = if !candidates_done && in_flight.len() < max_in_flight {
if in_flight.is_empty() {
Event::Candidate(candidates.next().await)
} else {
tokio::select! {
candidate = candidates.next() => Event::Candidate(candidate),
dialed = in_flight.next() => Event::Dialed(dialed),
}
}
} else {
Event::Dialed(in_flight.next().await)
};
match event {
Event::Candidate(Some(Ok(addr))) => {
in_flight.push(async move { (addr, dial(addr).await) });
}
Event::Candidate(Some(Err(err))) => last_err = Some(err),
Event::Candidate(None) => candidates_done = true,
Event::Dialed(Some((addr, Ok(conn)))) => return Ok((addr, conn)),
Event::Dialed(Some((_addr, Err(err)))) => last_err = Some(err),
Event::Dialed(None) => {}
}
}
Err(last_err
.unwrap_or_else(|| BoxError::from_static_str("race_connect: no connection candidates")))
}
#[cfg(test)]
mod tests {
use super::*;
use rama_core::futures::stream;
fn addr(port: u16) -> SocketAddr {
SocketAddr::from(([127, 0, 0, 1], port))
}
#[tokio::test]
async fn race_connect_returns_a_success_skipping_failures() {
let candidates = stream::iter([addr(1), addr(2), addr(3)].map(Ok::<_, BoxError>));
let (won, conn) = race_connect(candidates, 3, |a: SocketAddr| async move {
if a.port() == 1 {
Err(BoxError::from_static_str("refused"))
} else {
Ok::<_, BoxError>(a.port())
}
})
.await
.unwrap();
assert_ne!(won.port(), 1);
assert_eq!(conn, won.port());
}
#[tokio::test]
async fn race_connect_all_failures_returns_last_error() {
let candidates = stream::iter([addr(1), addr(2)].map(Ok::<_, BoxError>));
let result = race_connect(candidates, 3, |_a| async move {
Err::<u16, BoxError>(BoxError::from_static_str("always refused"))
})
.await;
result.unwrap_err();
}
}