use alloc::boxed::Box;
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::future::Future;
use core::marker::PhantomData;
use crate::pipe::SendPipe;
use futures::future::select_all;
use proxima_core::markers::DropSafe;
use crate::pipe::fanout::{AllOrNothing, FanPolicy};
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
pub enum RaceBuildError {
#[error("race requires at least one sink")]
EmptySinks,
}
pub struct Race<Sink, Policy = AllOrNothing> {
sinks: Arc<Vec<Sink>>,
policy: PhantomData<fn() -> Policy>,
}
impl<Sink, Policy> Clone for Race<Sink, Policy> {
fn clone(&self) -> Self {
Self {
sinks: Arc::clone(&self.sinks),
policy: PhantomData,
}
}
}
impl<Sink, Policy> Race<Sink, Policy>
where
Sink: SendPipe + DropSafe,
Policy: FanPolicy,
{
pub fn build(sinks: Vec<Sink>) -> Result<Self, RaceBuildError> {
if sinks.is_empty() {
return Err(RaceBuildError::EmptySinks);
}
Ok(Self {
sinks: Arc::new(sinks),
policy: PhantomData,
})
}
#[must_use]
pub fn sink_count(&self) -> usize {
self.sinks.len()
}
}
impl<Sink, Policy> SendPipe for Race<Sink, Policy>
where
Sink: SendPipe + DropSafe,
Sink::In: Clone + Send,
Sink::Out: Send,
Policy: FanPolicy,
{
type In = Sink::In;
type Out = Sink::Out;
type Err = Sink::Err;
fn call(&self, item: Sink::In) -> impl Future<Output = Result<Sink::Out, Sink::Err>> + Send {
let sinks = Arc::clone(&self.sinks);
async move {
let mut live: Vec<_> = sinks
.iter()
.map(|sink| Box::pin(sink.call(item.clone())))
.collect();
loop {
let (result, _winner, rest) = select_all(live).await;
match result {
Ok(output) => return Ok(output), Err(err) => {
if Policy::SHORT_CIRCUIT || rest.is_empty() {
return Err(err);
}
live = rest; }
}
}
}
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used, clippy::expect_used)]
use super::*;
use crate::pipe::BestEffort;
use alloc::sync::Arc;
use alloc::vec;
use core::sync::atomic::{AtomicUsize, Ordering};
use futures::executor::block_on;
#[derive(Debug, PartialEq)]
struct SinkErr(u32);
struct Sink {
calls: Arc<AtomicUsize>,
fail: bool,
}
impl SendPipe for Sink {
type In = u32;
type Out = u32;
type Err = SinkErr;
fn call(&self, input: u32) -> impl Future<Output = Result<u32, SinkErr>> + Send {
let calls = Arc::clone(&self.calls);
let fail = self.fail;
async move {
calls.fetch_add(1, Ordering::Relaxed);
if fail { Err(SinkErr(input)) } else { Ok(input) }
}
}
}
impl DropSafe for Sink {}
fn ok(calls: &Arc<AtomicUsize>) -> Sink {
Sink {
calls: Arc::clone(calls),
fail: false,
}
}
fn bad(calls: &Arc<AtomicUsize>) -> Sink {
Sink {
calls: Arc::clone(calls),
fail: true,
}
}
#[test]
fn empty_sinks_is_a_build_error() {
let built = Race::<Sink, AllOrNothing>::build(vec![]);
assert_eq!(built.err(), Some(RaceBuildError::EmptySinks));
}
#[test]
fn race_returns_an_ok_winner() {
let calls = Arc::new(AtomicUsize::new(0));
let race =
Race::<_, AllOrNothing>::build(vec![ok(&calls), ok(&calls), ok(&calls)]).unwrap();
assert_eq!(block_on(race.call(99)), Ok(99));
}
#[test]
fn best_effort_skips_failures_and_finds_the_ok() {
let calls = Arc::new(AtomicUsize::new(0));
let race =
Race::<_, BestEffort>::build(vec![bad(&calls), bad(&calls), ok(&calls)]).unwrap();
assert_eq!(block_on(race.call(7)), Ok(7));
}
#[test]
fn all_failing_surfaces_an_error() {
let calls = Arc::new(AtomicUsize::new(0));
let race = Race::<_, BestEffort>::build(vec![bad(&calls), bad(&calls)]).unwrap();
assert!(block_on(race.call(3)).is_err(), "no Ok anywhere -> Err");
}
}