#[cfg(test)]
mod tests;
use std::cell::Cell;
use std::future::Future;
use std::pin::Pin;
use std::sync::{Arc, OnceLock};
use std::task::{Context, Poll};
use tokio::runtime::{Handle, Runtime, RuntimeFlavor};
#[doc(hidden)]
pub fn block_on_isolated<F, Fut, T>(f: F) -> T
where
F: FnOnce() -> Fut + Send,
Fut: Future<Output = T>,
T: Send,
{
match tokio::runtime::Handle::try_current() {
Ok(_) => std::thread::scope(|s| {
let context = crate::otel::Context::current();
let identity = crate::authz::Identity::current();
s.spawn(move || {
let _context = context.attach();
let _identity = identity.map(crate::authz::identity::enter);
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(f())
})
.join()
.unwrap()
}),
Err(_) => tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(f()),
}
}
thread_local! {
static ON_SHARED: Cell<bool> = const { Cell::new(false) };
}
#[doc(hidden)]
pub fn runtime() -> &'static Runtime {
static RUNTIME: OnceLock<Runtime> = OnceLock::new();
RUNTIME.get_or_init(|| {
tokio::runtime::Builder::new_multi_thread()
.thread_name("rws-async")
.on_thread_start(|| ON_SHARED.with(|on| on.set(true)))
.enable_all()
.build()
.expect("the rws-async runtime")
})
}
#[doc(hidden)]
pub fn block_on_shared<T, Fut>(future: Fut) -> T
where
Fut: Future<Output = T> + Send + 'static,
T: Send + 'static,
{
let (tx, rx) = std::sync::mpsc::sync_channel(1);
let carried = CatchPanic(Box::pin(Carried::capture(future)));
runtime().spawn(async move {
let _ = tx.send(carried.await);
});
let wait = move || rx.recv();
let received = if ON_SHARED.with(Cell::get) { tokio::task::block_in_place(wait) } else { wait() };
match received {
Ok(Ok(output)) => output,
Ok(Err(panic)) => std::panic::resume_unwind(panic),
Err(_) => panic!("the rws-async runtime dropped the task"),
}
}
#[doc(hidden)]
pub async fn run_async<T, Fut>(future: Fut) -> T
where
Fut: Future<Output = T> + Send + 'static,
T: Send + 'static,
{
let here = matches!(Handle::try_current().map(|h| h.runtime_flavor()), Ok(RuntimeFlavor::MultiThread));
if here {
return future.await;
}
match runtime().spawn(Carried::capture(future)).await {
Ok(output) => output,
Err(error) if error.is_panic() => std::panic::resume_unwind(error.into_panic()),
Err(error) => panic!("the rws-async runtime dropped the task: {error}"),
}
}
struct Carried<F> {
future: Pin<Box<F>>,
context: crate::otel::Context,
identity: Option<crate::authz::Identity>,
hints: Option<Arc<crate::early_hints::Sink>>,
}
impl<F: Future> Carried<F> {
fn capture(future: F) -> Carried<F> {
Carried {
future: Box::pin(future),
context: crate::otel::Context::current(),
identity: crate::authz::Identity::current(),
hints: crate::early_hints::current(),
}
}
}
impl<F: Future> Future for Carried<F> {
type Output = F::Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<F::Output> {
let this = self.get_mut();
let _context = (!this.context.is_empty()).then(|| this.context.attach());
let _identity = this.identity.clone().map(crate::authz::identity::enter);
let future = &mut this.future;
match &this.hints {
Some(sink) => crate::early_hints::with_sink(Some(Arc::clone(sink)), || future.as_mut().poll(cx)),
None => future.as_mut().poll(cx),
}
}
}
struct CatchPanic<F>(Pin<Box<F>>);
impl<F: Future> Future for CatchPanic<F> {
type Output = Result<F::Output, Box<dyn std::any::Any + Send>>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let future = &mut self.0;
match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| future.as_mut().poll(cx))) {
Ok(Poll::Ready(output)) => Poll::Ready(Ok(output)),
Ok(Poll::Pending) => Poll::Pending,
Err(panic) => Poll::Ready(Err(panic)),
}
}
}