use std::pin::Pin;
use anyhow::{Result, anyhow};
use futures::{future, Future};
use tokio::sync::oneshot::{self, error::TryRecvError};
use super::wait;
pub fn with_stop<
T: Send + 'static,
TFuture: Future<Output = T> + Send + 'static,
>(
original_future: TFuture,
) -> (Box<dyn FnOnce() -> Result<()> + Send>, Pin<Box<dyn Future<Output = Option<T>> + Send + 'static>>) {
let (test_end_sender, mut test_end_receiver) = oneshot::channel::<()>();
let futures: Vec<Pin<Box<dyn Future<Output = Option<T>> + Send + 'static>>> = vec![
Box::pin(async move {
while let Err(TryRecvError::Empty) = test_end_receiver.try_recv() {
wait(5).await;
}
return None;
}),
Box::pin(async move {
return Some(
original_future.await,
);
}),
];
let stop: Box<dyn FnOnce() -> Result<()> + Send> = Box::new(
move || {
match test_end_sender.send(()) {
Ok(_) => Ok(()),
Err(_) => Err(anyhow!("Cannot stop the future, might be already stopped.")),
}
},
);
let result_future: Pin<Box<dyn Future<Output = Option<T>> + Send + 'static>> = Box::pin(async move {
let (result, _, _) = future::select_all(futures).await;
result
});
return (stop, result_future);
}
#[cfg(test)]
mod tests {
use std::pin::Pin;
use futures::{future, Future};
use cs_utils::{
random_number,
futures::{wait, with_stop},
};
#[tokio::test]
async fn can_stop_a_future() {
let forever_future = async move {
loop {
wait(5).await;
}
};
let (stop, forever_future) = with_stop(forever_future);
let futures: Vec<Pin<Box<dyn Future<Output = ()>>>> = vec![
Box::pin(async move {
let result = forever_future.await;
assert!(
result.is_none(),
"Stopped future must complete with `None` result.",
);
}),
Box::pin(async move {
wait(25).await;
stop()
.expect("Cannot stop the future.");
wait(25).await;
}),
];
future::select_all(futures).await;
}
#[tokio::test]
async fn completed_future_returns_some_result() {
let completing_future_result = random_number(0..=i128::MAX);
let completing_future = async move {
wait(5).await;
return completing_future_result;
};
let (_stop, completing_future) = with_stop(completing_future);
let result = completing_future.await
.expect("Must complete with `Some(original result)`.");
assert_eq!(
result,
completing_future_result,
"Original and received future results must match.",
);
}
}