use std::{
cell::{LazyCell, OnceCell},
panic::catch_unwind,
};
thread_local! {
static CAN_BLOCK: OnceCell<bool> = const { OnceCell::new() };
static REUSABLE_RT: LazyCell<tokio::runtime::Runtime> = LazyCell::new(|| {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("tokio runtime")
})
}
pub fn can_block_current_tokio() -> bool {
CAN_BLOCK.with(|cell| {
*cell.get_or_init(|| {
let result = catch_unwind(|| {
tokio::runtime::Handle::current().block_on(async {
tokio::task::yield_now().await;
});
});
result.is_ok() })
})
}
fn block_on_with_thread<F>(fut: F) -> F::Output
where
F: Future + Send,
F::Output: Send,
{
std::thread::scope(|scope| {
scope
.spawn(move || block_on(fut))
.join()
.expect("inner-thread panicked")
})
}
pub fn block_on<F>(f: F) -> F::Output
where
F: IntoFuture,
{
let Ok(rt) = tokio::runtime::Handle::try_current() else {
return REUSABLE_RT.with(|cell| cell.block_on(f.into_future()));
};
if can_block_current_tokio() {
rt.block_on(f.into_future())
} else {
panic!("you've gone too deep with the nested block_on calls, use \"block_on_mt\" instead.")
}
}
pub fn runtime_handle() -> tokio::runtime::Handle {
tokio::runtime::Handle::try_current().unwrap_or_else(|_| REUSABLE_RT.with(|v| v.handle().clone()))
}
pub fn block_on_mt<F>(f: F) -> F::Output
where
F: Future + Send,
F::Output: Send,
{
let Ok(rt) = tokio::runtime::Handle::try_current() else {
return REUSABLE_RT.with(|cell| cell.block_on(f));
};
if can_block_current_tokio() {
rt.block_on(f)
} else {
block_on_with_thread(f)
}
}
#[cfg(test)]
#[path = "tests/future.rs"]
mod tests;