diff --git a/futures-macro/src/select.rs b/futures-macro/src/select.rs index 57bb74a84..529b9a5db 100644 --- a/futures-macro/src/select.rs +++ b/futures-macro/src/select.rs @@ -279,7 +279,7 @@ fn select_inner(input: TokenStream, random: bool) -> TokenStream { let shuffle = if random { quote! { - __futures_crate::async_await::shuffle(&mut __select_arr); + __futures_crate::shuffle(&mut __select_arr); } } else { quote!() diff --git a/futures-macro/src/stream_select.rs b/futures-macro/src/stream_select.rs index 9927b5307..7571a565b 100644 --- a/futures-macro/src/stream_select.rs +++ b/futures-macro/src/stream_select.rs @@ -56,7 +56,7 @@ pub(crate) fn stream_select(input: TokenStream) -> Result(_: &T) {} diff --git a/futures-util/src/future/mod.rs b/futures-util/src/future/mod.rs index 1280ce986..be352fab4 100644 --- a/futures-util/src/future/mod.rs +++ b/futures-util/src/future/mod.rs @@ -85,8 +85,11 @@ mod join_all; #[cfg(feature = "alloc")] pub use self::join_all::{join_all, JoinAll}; +mod select_strategy; +pub use select_strategy::{Biased, Fair, IsBiased}; + mod select; -pub use self::select::{select, Select}; +pub use self::select::{select, select_biased, Select}; #[cfg(feature = "alloc")] mod select_all; @@ -102,12 +105,12 @@ mod try_join_all; pub use self::try_join_all::{try_join_all, TryJoinAll}; mod try_select; -pub use self::try_select::{try_select, TrySelect}; +pub use self::try_select::{try_select, try_select_biased, TrySelect}; #[cfg(feature = "alloc")] mod select_ok; #[cfg(feature = "alloc")] -pub use self::select_ok::{select_ok, SelectOk}; +pub use self::select_ok::{select_ok, select_ok_biased, SelectOk}; mod either; pub use self::either::Either; diff --git a/futures-util/src/future/select.rs b/futures-util/src/future/select.rs index 7e33d195f..9a887173f 100644 --- a/futures-util/src/future/select.rs +++ b/futures-util/src/future/select.rs @@ -1,5 +1,6 @@ -use super::assert_future; +use super::{assert_future, Biased, Fair, IsBiased}; use crate::future::{Either, FutureExt}; +use core::marker::PhantomData; use core::pin::Pin; use futures_core::future::{FusedFuture, Future}; use futures_core::task::{Context, Poll}; @@ -7,11 +8,12 @@ use futures_core::task::{Context, Poll}; /// Future for the [`select()`] function. #[must_use = "futures do nothing unless you `.await` or poll them"] #[derive(Debug)] -pub struct Select { +pub struct Select { inner: Option<(A, B)>, + _phantom: PhantomData, } -impl Unpin for Select {} +impl Unpin for Select {} /// Waits for either one of two differently-typed futures to complete. /// @@ -22,6 +24,10 @@ impl Unpin for Select {} /// Note that this function consumes the receiving futures and returns a /// wrapped version of them. /// +/// If both futures are ready when this is polled, the winner will be pseudo-randomly +/// selected, unless the std feature is not enabled. If std is not enabled, the first +/// argument will always win. +/// /// Also note that if both this and the second future have the same /// output type you can use the `Either::factor_first` method to /// conveniently extract out the value at the end. @@ -81,20 +87,103 @@ impl Unpin for Select {} /// }) /// } /// ``` -pub fn select(future1: A, future2: B) -> Select +pub fn select(future1: A, future2: B) -> Select +where + A: Future + Unpin, + B: Future + Unpin, +{ + assert_future::, _>(Select { + inner: Some((future1, future2)), + _phantom: PhantomData, + }) +} + +/// Waits for either one of two differently-typed futures to complete, giving preferential treatment to the first one. +/// +/// This function will return a new future which awaits for either one of both +/// futures to complete. The returned future will finish with both the value +/// resolved and a future representing the completion of the other work. +/// +/// Note that this function consumes the receiving futures and returns a +/// wrapped version of them. +/// +/// If both futures are ready when this is polled, the winner will always be the first argument. +/// +/// Also note that if both this and the second future have the same +/// output type you can use the `Either::factor_first` method to +/// conveniently extract out the value at the end. +/// +/// # Examples +/// +/// A simple example +/// +/// ``` +/// # futures::executor::block_on(async { +/// use futures::{ +/// pin_mut, +/// future::Either, +/// future::self, +/// }; +/// +/// // These two futures have different types even though their outputs have the same type. +/// let future1 = async { +/// future::pending::<()>().await; // will never finish +/// 1 +/// }; +/// let future2 = async { +/// future::ready(2).await +/// }; +/// +/// // 'select_biased' requires Future + Unpin bounds +/// pin_mut!(future1); +/// pin_mut!(future2); +/// +/// let value = match future::select_biased(future1, future2).await { +/// Either::Left((value1, _)) => value1, // `value1` is resolved from `future1` +/// // `_` represents `future2` +/// Either::Right((value2, _)) => value2, // `value2` is resolved from `future2` +/// // `_` represents `future1` +/// }; +/// +/// assert!(value == 2); +/// # }); +/// ``` +/// +/// A more complex example +/// +/// ``` +/// use futures::future::{self, Either, Future, FutureExt}; +/// +/// // A poor-man's join implemented on top of select +/// +/// fn join(a: A, b: B) -> impl Future +/// where A: Future + Unpin, +/// B: Future + Unpin, +/// { +/// future::select_biased(a, b).then(|either| { +/// match either { +/// Either::Left((x, b)) => b.map(move |y| (x, y)).left_future(), +/// Either::Right((y, a)) => a.map(move |x| (x, y)).right_future(), +/// } +/// }) +/// } +/// ``` +pub fn select_biased(future1: A, future2: B) -> Select where A: Future + Unpin, B: Future + Unpin, { assert_future::, _>(Select { inner: Some((future1, future2)), + _phantom: PhantomData, }) } -impl Future for Select +impl Future for Select where A: Future + Unpin, B: Future + Unpin, + BIASED: IsBiased, { type Output = Either<(A::Output, B), (B::Output, A)>; @@ -111,22 +200,37 @@ where let (a, b) = self.inner.as_mut().expect("cannot poll Select twice"); - if let Poll::Ready(val) = a.poll_unpin(cx) { - return Poll::Ready(Either::Left((val, unwrap_option(self.inner.take()).1))); + macro_rules! poll_wrap { + ($to_poll:expr, $unpolled:expr, $wrap:expr) => { + if let Poll::Ready(val) = $to_poll.poll_unpin(cx) { + return Poll::Ready($wrap((val, $unpolled))); + } + }; } - if let Poll::Ready(val) = b.poll_unpin(cx) { - return Poll::Ready(Either::Right((val, unwrap_option(self.inner.take()).0))); + #[cfg(feature = "std")] + if BIASED::IS_BIASED || crate::gen_index(2) == 0 { + poll_wrap!(a, unwrap_option(self.inner.take()).1, Either::Left); + poll_wrap!(b, unwrap_option(self.inner.take()).0, Either::Right); + } else { + poll_wrap!(b, unwrap_option(self.inner.take()).0, Either::Right); + poll_wrap!(a, unwrap_option(self.inner.take()).1, Either::Left); } + #[cfg(not(feature = "std"))] + { + poll_wrap!(a, unwrap_option(self.inner.take()).1, Either::Left); + poll_wrap!(b, unwrap_option(self.inner.take()).0, Either::Right); + } Poll::Pending } } -impl FusedFuture for Select +impl FusedFuture for Select where A: Future + Unpin, B: Future + Unpin, + BIASED: IsBiased, { fn is_terminated(&self) -> bool { self.inner.is_none() diff --git a/futures-util/src/future/select_ok.rs b/futures-util/src/future/select_ok.rs index 5d5579930..848cd52b8 100644 --- a/futures-util/src/future/select_ok.rs +++ b/futures-util/src/future/select_ok.rs @@ -1,7 +1,9 @@ use super::assert_future; +use super::{Biased, Fair, IsBiased}; use crate::future::TryFutureExt; use alloc::vec::Vec; use core::iter::FromIterator; +use core::marker::PhantomData; use core::mem; use core::pin::Pin; use futures_core::future::{Future, TryFuture}; @@ -10,11 +12,12 @@ use futures_core::task::{Context, Poll}; /// Future for the [`select_ok`] function. #[derive(Debug)] #[must_use = "futures do nothing unless you `.await` or poll them"] -pub struct SelectOk { +pub struct SelectOk { inner: Vec, + _phantom: PhantomData, } -impl Unpin for SelectOk {} +impl Unpin for SelectOk {} /// Creates a new future which will select the first successful future over a list of futures. /// @@ -26,15 +29,55 @@ impl Unpin for SelectOk {} /// This function is only available when the `std` or `alloc` feature of this /// library is activated, and it is activated by default. /// +/// # Note for users migrating from 0.3 to 0.4 +/// +/// This function used to be biased in favor of futures that appeared earlier in the +/// iterator. This is no longer the case, the futures are now shuffled prior to being polled. +/// This prevents starvation problems. It also has the side effect that the returned `Vec` +/// of remaining futures may be longer than it was in version 0.3, because of this shuffling. +/// Some futures that would have been polled and had errors get dropped, may now instead +/// remain in the collection without being polled. +/// +/// If you were relying on this biased behavior, consider switching to the [`select_ok_biased`] function. +/// +/// # Panics +/// +/// This function will panic if the iterator specified contains no items. +pub fn select_ok(iter: I) -> SelectOk +where + I: IntoIterator, + I::Item: TryFuture + Unpin, +{ + let ret = SelectOk { inner: iter.into_iter().collect(), _phantom: PhantomData }; + assert!(!ret.inner.is_empty(), "iterator provided to select_ok was empty"); + assert_future::< + Result<(::Ok, Vec), ::Error>, + _, + >(ret) +} + +/// Creates a new future which will select the first successful future over a list of futures. +/// +/// The returned future will wait for any future within `iter` to be ready and Ok. Unlike +/// `select_all`, this will only return the first successful completion, or the last +/// failure. This is useful in contexts where any success is desired and failures +/// are ignored, unless all the futures fail. +/// +/// This function is only available when the `std` or `alloc` feature of this +/// library is activated, and it is activated by default. +/// +/// If multiple futures are ready at the same time this function is biased towards +/// entries that are earlier in the list. +/// /// # Panics /// /// This function will panic if the iterator specified contains no items. -pub fn select_ok(iter: I) -> SelectOk +pub fn select_ok_biased(iter: I) -> SelectOk where I: IntoIterator, I::Item: TryFuture + Unpin, { - let ret = SelectOk { inner: iter.into_iter().collect() }; + let ret = SelectOk { inner: iter.into_iter().collect(), _phantom: PhantomData }; assert!(!ret.inner.is_empty(), "iterator provided to select_ok was empty"); assert_future::< Result<(::Ok, Vec), ::Error>, @@ -42,28 +85,34 @@ where >(ret) } -impl Future for SelectOk { +impl Future for SelectOk { type Output = Result<(Fut::Ok, Vec), Fut::Error>; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let Self { inner, _phantom } = &mut *self; + #[cfg(feature = "std")] + { + if !BIASED::IS_BIASED { + crate::shuffle(inner); + } + } // loop until we've either exhausted all errors, a success was hit, or nothing is ready loop { - let item = - self.inner.iter_mut().enumerate().find_map(|(i, f)| match f.try_poll_unpin(cx) { - Poll::Pending => None, - Poll::Ready(e) => Some((i, e)), - }); + let item = inner.iter_mut().enumerate().find_map(|(i, f)| match f.try_poll_unpin(cx) { + Poll::Pending => None, + Poll::Ready(e) => Some((i, e)), + }); match item { Some((idx, res)) => { // always remove Ok or Err, if it's not the last Err continue looping - drop(self.inner.remove(idx)); + drop(inner.remove(idx)); match res { Ok(e) => { - let rest = mem::take(&mut self.inner); + let rest = mem::take(inner); return Poll::Ready(Ok((e, rest))); } Err(e) => { - if self.inner.is_empty() { + if inner.is_empty() { return Poll::Ready(Err(e)); } } diff --git a/futures-util/src/future/select_strategy.rs b/futures-util/src/future/select_strategy.rs new file mode 100644 index 000000000..4d82e1328 --- /dev/null +++ b/futures-util/src/future/select_strategy.rs @@ -0,0 +1,25 @@ +/// When used with a select future, this will make the future biased. +/// When multiple futures are ready, the winner will be the first one +/// specified. +#[derive(Debug)] +pub struct Biased; + +/// When used with a select future, this will make the future fair. +/// When multiple futures are ready, the winner will be pseudo-randomly +/// selected. This is the default behavior. +#[derive(Debug)] +pub struct Fair; + +/// Reports whether the type is an instance of [`Biased`] or not. +pub trait IsBiased { + /// Contains the answer to our question: is this biased? + const IS_BIASED: bool; +} + +impl IsBiased for Biased { + const IS_BIASED: bool = true; +} + +impl IsBiased for Fair { + const IS_BIASED: bool = false; +} diff --git a/futures-util/src/future/try_select.rs b/futures-util/src/future/try_select.rs index bc282f7db..fbc3e5c6a 100644 --- a/futures-util/src/future/try_select.rs +++ b/futures-util/src/future/try_select.rs @@ -1,4 +1,6 @@ +use super::{Biased, Fair, IsBiased}; use crate::future::{Either, TryFutureExt}; +use core::marker::PhantomData; use core::pin::Pin; use futures_core::future::{Future, TryFuture}; use futures_core::task::{Context, Poll}; @@ -6,11 +8,12 @@ use futures_core::task::{Context, Poll}; /// Future for the [`try_select()`] function. #[must_use = "futures do nothing unless you `.await` or poll them"] #[derive(Debug)] -pub struct TrySelect { +pub struct TrySelect { inner: Option<(A, B)>, + _phantom: PhantomData, } -impl Unpin for TrySelect {} +impl Unpin for TrySelect {} type EitherOk = Either<(::Ok, B), (::Ok, A)>; type EitherErr = Either<(::Error, B), (::Error, A)>; @@ -24,6 +27,10 @@ type EitherErr = Either<(::Error, B), (::E /// Note that this function consumes the receiving futures and returns a /// wrapped version of them. /// +/// If both futures are ready when this is polled, the winner will be pseudo-randomly +/// selected, unless the `std` feature is disabled. If the std feature is disabled, +/// the first argument will always win. +/// /// Also note that if both this and the second future have the same /// success/error type you can use the `Either::factor_first` method to /// conveniently extract out the value at the end. @@ -50,36 +57,104 @@ type EitherErr = Either<(::Error, B), (::E /// }) /// } /// ``` -pub fn try_select(future1: A, future2: B) -> TrySelect +pub fn try_select(future1: A, future2: B) -> TrySelect +where + A: TryFuture + Unpin, + B: TryFuture + Unpin, +{ + super::assert_future::, EitherErr>, _>(TrySelect { + inner: Some((future1, future2)), + _phantom: PhantomData, + }) +} + +/// Waits for either one of two differently-typed futures to complete, giving preferential treatment to the first one. +/// +/// This function will return a new future which awaits for either one of both +/// futures to complete. The returned future will finish with both the value +/// resolved and a future representing the completion of the other work. +/// +/// Note that this function consumes the receiving futures and returns a +/// wrapped version of them. +/// +/// If both futures are ready when this is polled, the winner will always be the first one. +/// +/// Also note that if both this and the second future have the same +/// success/error type you can use the `Either::factor_first` method to +/// conveniently extract out the value at the end. +/// +/// # Examples +/// +/// ``` +/// use futures::future::{self, Either, Future, FutureExt, TryFuture, TryFutureExt}; +/// +/// // A poor-man's try_join implemented on top of select +/// +/// fn try_join(a: A, b: B) -> impl TryFuture +/// where A: TryFuture + Unpin + 'static, +/// B: TryFuture + Unpin + 'static, +/// E: 'static, +/// { +/// future::try_select_biased(a, b).then(|res| -> Box> + Unpin> { +/// match res { +/// Ok(Either::Left((x, b))) => Box::new(b.map_ok(move |y| (x, y))), +/// Ok(Either::Right((y, a))) => Box::new(a.map_ok(move |x| (x, y))), +/// Err(Either::Left((e, _))) => Box::new(future::err(e)), +/// Err(Either::Right((e, _))) => Box::new(future::err(e)), +/// } +/// }) +/// } +/// ``` +pub fn try_select_biased(future1: A, future2: B) -> TrySelect where A: TryFuture + Unpin, B: TryFuture + Unpin, { super::assert_future::, EitherErr>, _>(TrySelect { inner: Some((future1, future2)), + _phantom: PhantomData, }) } -impl Future for TrySelect +impl Future for TrySelect where A: TryFuture, B: TryFuture, + BIASED: IsBiased, { type Output = Result, EitherErr>; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let (mut a, mut b) = self.inner.take().expect("cannot poll Select twice"); - match a.try_poll_unpin(cx) { - Poll::Ready(Err(x)) => Poll::Ready(Err(Either::Left((x, b)))), - Poll::Ready(Ok(x)) => Poll::Ready(Ok(Either::Left((x, b)))), - Poll::Pending => match b.try_poll_unpin(cx) { - Poll::Ready(Err(x)) => Poll::Ready(Err(Either::Right((x, a)))), - Poll::Ready(Ok(x)) => Poll::Ready(Ok(Either::Right((x, a)))), - Poll::Pending => { - self.inner = Some((a, b)); - Poll::Pending + macro_rules! poll_wrap { + ($poll_first:expr, $poll_second:expr, $wrap_first:expr, $wrap_second:expr) => { + match $poll_first.try_poll_unpin(cx) { + Poll::Ready(Err(x)) => Poll::Ready(Err($wrap_first((x, $poll_second)))), + Poll::Ready(Ok(x)) => Poll::Ready(Ok($wrap_first((x, $poll_second)))), + Poll::Pending => match $poll_second.try_poll_unpin(cx) { + Poll::Ready(Err(x)) => Poll::Ready(Err($wrap_second((x, $poll_first)))), + Poll::Ready(Ok(x)) => Poll::Ready(Ok($wrap_second((x, $poll_first)))), + Poll::Pending => { + self.inner = Some((a, b)); + Poll::Pending + } + }, } - }, + }; + } + + #[cfg(feature = "std")] + { + if BIASED::IS_BIASED || crate::gen_index(2) == 0 { + poll_wrap!(a, b, Either::Left, Either::Right) + } else { + poll_wrap!(b, a, Either::Right, Either::Left) + } + } + + #[cfg(not(feature = "std"))] + { + poll_wrap!(a, b, Either::Left, Either::Right) } } } diff --git a/futures-util/src/lib.rs b/futures-util/src/lib.rs index abe35e68f..b77b2f812 100644 --- a/futures-util/src/lib.rs +++ b/futures-util/src/lib.rs @@ -34,6 +34,11 @@ mod async_await; #[doc(hidden)] pub use self::async_await::*; +#[cfg(feature = "std")] +mod random; +#[cfg(feature = "std")] +pub use self::random::*; + // Not public API. #[cfg(feature = "async-await")] #[doc(hidden)] diff --git a/futures-util/src/async_await/random.rs b/futures-util/src/random.rs similarity index 96% rename from futures-util/src/async_await/random.rs rename to futures-util/src/random.rs index 2ac2f78a8..fcb168f4c 100644 --- a/futures-util/src/async_await/random.rs +++ b/futures-util/src/random.rs @@ -17,7 +17,7 @@ pub fn shuffle(slice: &mut [T]) { } /// Return a value from `0..n`. -fn gen_index(n: usize) -> usize { +pub(crate) fn gen_index(n: usize) -> usize { (random() % n as u64) as usize } diff --git a/futures/tests/future_select.rs b/futures/tests/future_select.rs new file mode 100644 index 000000000..5e2dffa94 --- /dev/null +++ b/futures/tests/future_select.rs @@ -0,0 +1,26 @@ +use std::future::ready; + +use futures::future::{select, select_biased}; +use futures_executor::block_on; + +#[test] +fn is_fair() { + let mut results = Vec::with_capacity(100); + for _ in 0..100 { + let (i, _) = block_on(select(ready(0), ready(1))).factor_first(); + results.push(i); + } + const THRESHOLD: usize = 30; + assert_eq!(results.iter().filter(|i| **i == 0).take(THRESHOLD).count(), THRESHOLD); + assert_eq!(results.iter().filter(|i| **i == 1).take(THRESHOLD).count(), THRESHOLD) +} + +#[test] +fn is_biased() { + let mut results = Vec::with_capacity(100); + for _ in 0..100 { + let (i, _) = block_on(select_biased(ready(0), ready(1))).factor_first(); + results.push(i); + } + assert!(results.iter().all(|i| *i == 0)); +} diff --git a/futures/tests/future_select_ok.rs b/futures/tests/future_select_ok.rs index 8aec00362..fa4c48720 100644 --- a/futures/tests/future_select_ok.rs +++ b/futures/tests/future_select_ok.rs @@ -1,30 +1,79 @@ +use std::fmt::Debug; +use std::time::Duration; + use futures::executor::block_on; -use futures::future::{err, ok, select_ok}; +use futures::future::{err, ok, select_ok, select_ok_biased, Future}; +use futures_channel::oneshot; +use std::thread; #[test] fn ignore_err() { let v = vec![err(1), err(2), ok(3), ok(4)]; let (i, v) = block_on(select_ok(v)).ok().unwrap(); - assert_eq!(i, 3); + assert!(i == 3 || i == 4); - assert_eq!(v.len(), 1); + assert!(v.len() < 4); - let (i, v) = block_on(select_ok(v)).ok().unwrap(); - assert_eq!(i, 4); + let (j, v) = block_on(select_ok(v)).ok().unwrap(); + assert!(j == 3 || j == 4); + assert_ne!(j, i); - assert!(v.is_empty()); + assert!(v.len() < 3); } #[test] fn last_err() { - let v = vec![ok(1), err(2), err(3)]; + let (ok_sender, ok_receiver) = oneshot::channel(); + let (first_err_sender, first_err_receiver) = oneshot::channel(); + let (second_err_sender, second_err_receiver) = oneshot::channel(); + async fn await_unwrap(o: impl Future>) -> T { + o.await.unwrap() + } + let v = vec![ + Box::pin(await_unwrap(ok_receiver)), + Box::pin(await_unwrap(first_err_receiver)), + Box::pin(await_unwrap(second_err_receiver)), + ]; + ok_sender.send(Ok(1)).unwrap(); let (i, v) = block_on(select_ok(v)).ok().unwrap(); assert_eq!(i, 1); assert_eq!(v.len(), 2); + first_err_sender.send(Err(2)).unwrap(); + + thread::spawn(move || { + thread::sleep(Duration::from_millis(100)); + second_err_sender.send(Err(3)).unwrap(); + }); let i = block_on(select_ok(v)).err().unwrap(); assert_eq!(i, 3); } + +#[test] +fn is_fair() { + let mut results = Vec::with_capacity(100); + for _ in 0..100 { + let v = vec![err(1), err(2), ok(3), ok(4)]; + + let (i, _v) = block_on(select_ok(v)).ok().unwrap(); + results.push(i); + } + const THRESHOLD: usize = 30; + assert_eq!(results.iter().filter(|i| **i == 3).take(THRESHOLD).count(), THRESHOLD); + assert_eq!(results.iter().filter(|i| **i == 4).take(THRESHOLD).count(), THRESHOLD); +} + +#[test] +fn is_biased() { + let mut results = Vec::with_capacity(100); + for _ in 0..100 { + let v = vec![err(1), err(2), ok(3), ok(4)]; + + let (i, _v) = block_on(select_ok_biased(v)).ok().unwrap(); + results.push(i); + } + assert!(results.iter().all(|i| *i == 3)); +} diff --git a/futures/tests/future_try_select.rs b/futures/tests/future_try_select.rs new file mode 100644 index 000000000..40dcd6da7 --- /dev/null +++ b/futures/tests/future_try_select.rs @@ -0,0 +1,25 @@ +use futures::future::{ok, try_select, try_select_biased}; +use futures_executor::block_on; + +#[test] +fn is_fair() { + let mut results = Vec::with_capacity(100); + for _ in 0..100 { + let (i, _) = block_on(try_select(ok::<_, ()>(0), ok::<_, ()>(1))).unwrap().factor_first(); + results.push(i); + } + const THRESHOLD: usize = 30; + assert_eq!(results.iter().filter(|i| **i == 0).take(THRESHOLD).count(), THRESHOLD); + assert_eq!(results.iter().filter(|i| **i == 1).take(THRESHOLD).count(), THRESHOLD) +} + +#[test] +fn is_biased() { + let mut results = Vec::with_capacity(100); + for _ in 0..100 { + let (i, _) = + block_on(try_select_biased(ok::<_, ()>(0), ok::<_, ()>(1))).unwrap().factor_first(); + results.push(i); + } + assert!(results.iter().all(|i| *i == 0)); +}