use crate::config::{Config, Strategy};
use crate::error::{Error, ProviderError};
use crate::provider::BoxedProvider;
use crate::types::ProviderResult;
use std::collections::HashMap;
use std::future::Future;
use std::net::IpAddr;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::Instant;
use tokio::time::timeout;
type BoxFut<'a> = Pin<Box<dyn Future<Output = Result<ProviderResult, ProviderError>> + Send + 'a>>;
pub struct Resolver {
config: Config,
}
impl Resolver {
pub fn new(config: Config) -> Self {
Self { config }
}
#[inline]
fn matching_providers(&self) -> impl Iterator<Item = &BoxedProvider> {
self.config
.providers
.iter()
.filter(|p| p.supports_version(self.config.version))
}
fn make_provider_future<'a>(&'a self, provider: &'a BoxedProvider) -> BoxFut<'a> {
let provider_name = provider.name().to_string();
let protocol = provider.protocol();
let start = Instant::now();
let fut = provider.get_ip(self.config.version);
let timeout_duration = self.config.timeout;
Box::pin(async move {
match timeout(timeout_duration, fut).await {
Ok(Ok(ip)) => {
let latency = start.elapsed();
Ok(ProviderResult {
ip,
provider: provider_name,
protocol,
latency,
})
}
Ok(Err(e)) => Err(e),
Err(_) => Err(ProviderError::message(provider_name, "timeout")),
}
})
}
pub async fn resolve(&self) -> Result<ProviderResult, Error> {
if self.matching_providers().next().is_none() {
return Err(Error::NoProvidersForVersion);
}
match self.config.strategy {
Strategy::First => self.resolve_first().await,
Strategy::Race => self.resolve_race().await,
Strategy::Consensus { min_agree } => self.resolve_consensus(min_agree).await,
}
}
async fn resolve_first(&self) -> Result<ProviderResult, Error> {
let mut errors = Vec::new();
for provider in self.matching_providers() {
match self.make_provider_future(provider).await {
Ok(result) => return Ok(result),
Err(e) => errors.push(e),
}
}
Err(Error::AllProvidersFailed(errors))
}
async fn resolve_race(&self) -> Result<ProviderResult, Error> {
let mut futures: Vec<BoxFut<'_>> = self
.matching_providers()
.map(|p| self.make_provider_future(p))
.collect();
if futures.is_empty() {
return Err(Error::NoProvidersForVersion);
}
let mut errors = Vec::new();
while !futures.is_empty() {
let (result, _index, remaining) = select_first(futures).await;
futures = remaining;
match result {
Ok(provider_result) => return Ok(provider_result),
Err(e) => errors.push(e),
}
}
Err(Error::AllProvidersFailed(errors))
}
async fn resolve_consensus(&self, min_agree: usize) -> Result<ProviderResult, Error> {
let futures: Vec<BoxFut<'_>> = self
.matching_providers()
.map(|p| self.make_provider_future(p))
.collect();
if futures.is_empty() {
return Err(Error::NoProvidersForVersion);
}
let all_results = join_all_vec(futures).await;
let mut ip_results: HashMap<IpAddr, Vec<ProviderResult>> = HashMap::new();
let mut errors = Vec::new();
for result in all_results {
match result {
Ok(pr) => ip_results.entry(pr.ip).or_default().push(pr),
Err(e) => errors.push(e),
}
}
let mut best: Option<(IpAddr, usize)> = None;
for (ip, providers) in &ip_results {
if providers.len() >= min_agree {
match &best {
None => best = Some((*ip, providers.len())),
Some((_, current_len)) if providers.len() > *current_len => {
best = Some((*ip, providers.len()))
}
_ => {}
}
}
}
match best {
Some((ip, _)) => {
if let Some(providers) = ip_results.remove(&ip) {
if let Some(fastest) = providers.into_iter().min_by_key(|p| p.latency) {
return Ok(fastest);
}
}
Err(Error::ConsensusNotReached {
required: min_agree,
got: 0,
errors,
})
}
None => {
let max_agreement = ip_results.values().map(|v| v.len()).max().unwrap_or(0);
Err(Error::ConsensusNotReached {
required: min_agree,
got: max_agreement,
errors,
})
}
}
}
}
async fn select_first<F: Future + Unpin>(mut futures: Vec<F>) -> (F::Output, usize, Vec<F>) {
std::future::poll_fn(|cx: &mut Context<'_>| {
for (i, fut) in futures.iter_mut().enumerate() {
if let Poll::Ready(output) = Pin::new(fut).poll(cx) {
futures.swap_remove(i);
return Poll::Ready((output, i, std::mem::take(&mut futures)));
}
}
Poll::Pending
})
.await
}
async fn join_all_vec<T, F: Future<Output = T> + Unpin>(mut futures: Vec<F>) -> Vec<T> {
let total = futures.len();
let mut results: Vec<Option<T>> = (0..total).map(|_| None).collect();
let mut done = 0;
std::future::poll_fn(|cx: &mut Context<'_>| {
for (i, fut) in futures.iter_mut().enumerate() {
if results[i].is_some() {
continue;
}
if let Poll::Ready(output) = Pin::new(fut).poll(cx) {
results[i] = Some(output);
done += 1;
}
}
if done == total {
Poll::Ready(())
} else {
Poll::Pending
}
})
.await;
results
.into_iter()
.map(|r| r.expect("bug: future completed but result slot is empty"))
.collect()
}