use std::future::Future;
#[cfg(not(target_arch = "wasm32"))]
pub async fn relieve_caller_stack<T, F, Fut>(make_future: F) -> T
where
F: FnOnce() -> Fut + Send + 'static,
Fut: Future<Output = T> + Send + 'static,
T: Send + 'static,
{
struct AbortOnDrop(tokio::task::AbortHandle);
impl Drop for AbortOnDrop {
fn drop(&mut self) {
self.0.abort();
}
}
let handle = tokio::spawn(async move {
let future: std::pin::Pin<Box<Fut>> = Box::pin(make_future());
future.await
});
let _guard = AbortOnDrop(handle.abort_handle());
match handle.await {
Ok(value) => value,
Err(join_error) => match join_error.try_into_panic() {
Ok(panic) => std::panic::resume_unwind(panic),
Err(_) => std::future::pending().await,
},
}
}
#[cfg(target_arch = "wasm32")]
pub async fn relieve_caller_stack<T, F, Fut>(make_future: F) -> T
where
F: FnOnce() -> Fut + 'static,
Fut: Future<Output = T> + 'static,
T: 'static,
{
make_future().await
}
#[cfg(all(test, not(target_arch = "wasm32")))]
mod tests {
use super::relieve_caller_stack;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
#[tokio::test]
async fn resolves_with_the_future_output() {
let value = relieve_caller_stack(|| async { 6 * 7 }).await;
assert_eq!(value, 42);
}
#[tokio::test]
async fn propagates_panics_to_the_caller() {
let result = tokio::spawn(async {
relieve_caller_stack(|| async { panic!("stack relief panic probe") }).await
})
.await;
let join_error = result.expect_err("panic must propagate");
assert!(join_error.is_panic());
}
#[tokio::test]
async fn dropping_the_caller_aborts_the_spawned_work() {
let entered = Arc::new(AtomicBool::new(false));
let finished = Arc::new(AtomicBool::new(false));
let entered_clone = Arc::clone(&entered);
let finished_clone = Arc::clone(&finished);
let caller = tokio::spawn(async move {
relieve_caller_stack(move || async move {
entered_clone.store(true, Ordering::SeqCst);
tokio::time::sleep(std::time::Duration::from_secs(300)).await;
finished_clone.store(true, Ordering::SeqCst);
})
.await;
});
while !entered.load(Ordering::SeqCst) {
tokio::task::yield_now().await;
}
caller.abort();
let _ = caller.await;
for _ in 0..64 {
tokio::task::yield_now().await;
}
assert!(!finished.load(Ordering::SeqCst));
}
}