use std::future::Future;
use std::sync::{Arc, Mutex};
use crate::error::Result;
pub(super) struct Carried<R, F> {
slot: Arc<Mutex<Option<R>>>,
run: F,
}
impl<R, F: FnOnce(R) -> Result<()>> Carried<R, F> {
pub(super) fn run(self) -> Result<()> {
let token = self.slot.lock().unwrap_or_else(|e| e.into_inner()).take();
match token {
Some(token) => (self.run)(token),
None => Ok(()),
}
}
}
struct TakeBack<R> {
slot: Arc<Mutex<Option<R>>>,
armed: bool,
}
impl<R> Drop for TakeBack<R> {
fn drop(&mut self) {
if self.armed {
let token = self.slot.lock().unwrap_or_else(|e| e.into_inner()).take();
drop(token);
}
}
}
pub(super) fn carry<R, F, H, O>(token: R, run: F, hand_off: H) -> O
where
H: FnOnce(Carried<R, F>) -> O,
{
let slot = Arc::new(Mutex::new(Some(token)));
let mut guard = TakeBack {
slot: slot.clone(),
armed: true,
};
let handed = hand_off(Carried { slot, run });
guard.armed = false;
handed
}
pub(super) fn on_pool<R, F>(token: R, run: F) -> crate::BoxFuture<'static, Result<()>>
where
R: Send + 'static,
F: FnOnce(R) -> Result<()> + Send + 'static,
{
carry(token, run, |task| {
<crate::Tokio as crate::BinderAsyncPool>::spawn(
move || task.run(),
|result| async move { result },
)
})
}
pub(super) enum Either<A, B> {
A(A),
B(B),
}
impl<A, B, O> Either<A, B>
where
A: Future<Output = O>,
B: Future<Output = O>,
{
pub(super) async fn run(self) -> O {
match self {
Either::A(a) => a.await,
Either::B(b) => b.await,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicUsize, Ordering};
struct Token(Arc<AtomicUsize>);
impl Drop for Token {
fn drop(&mut self) {
self.0.fetch_add(1, Ordering::SeqCst);
}
}
#[test]
fn a_hand_off_that_queues_and_unwinds_gives_the_token_back() {
let settled = Arc::new(AtomicUsize::new(0));
let ran = Arc::new(AtomicUsize::new(0));
let counter = ran.clone();
let mut queued = None;
let handed = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
carry(
Token(settled.clone()),
move |token: Token| {
counter.fetch_add(1, Ordering::SeqCst);
drop(token);
Ok(())
},
|task| {
queued = Some(task);
std::panic::resume_unwind(Box::new("no thread to run it"));
},
)
}));
assert!(handed.is_err());
assert_eq!(
settled.load(Ordering::SeqCst),
1,
"settled by the take-back"
);
queued.expect("queued").run().expect("a task with no token");
assert_eq!(ran.load(Ordering::SeqCst), 0, "the token settled once");
assert_eq!(settled.load(Ordering::SeqCst), 1);
}
}