use std::{
future::Future,
sync::{Arc, OnceLock},
thread,
};
use tokio::{
runtime::{self, Handle, Runtime},
task::block_in_place,
};
const FALLBACK_QUERY_RUNTIME_WORKERS: usize = 4;
pub(crate) fn bridge_sync_to_async<F, T>(fut: F) -> T
where
F: Future<Output = T>,
{
match runtime::Handle::try_current() {
Ok(handle) => block_in_place(|| handle.block_on(fut)),
Err(_) => build_current_thread_runtime().block_on(fut),
}
}
pub(crate) fn bridge_on_runtime<F: Future>(fut: F, runtime: &Runtime) -> F::Output {
match Handle::try_current() {
Ok(_ambient) => block_in_place(|| runtime.handle().block_on(fut)),
Err(_) => runtime.block_on(fut),
}
}
pub(crate) fn bridge_sync_to_async_send<F, T>(fut: F) -> T
where
F: Future<Output = T> + Send + 'static,
T: Send + 'static,
{
match runtime::Handle::try_current() {
Ok(handle) if matches!(handle.runtime_flavor(), runtime::RuntimeFlavor::MultiThread) => {
block_in_place(|| handle.block_on(fut))
}
Ok(_) => thread::spawn(move || build_current_thread_runtime().block_on(fut))
.join()
.expect("sync→async bridge worker thread panicked"),
Err(_) => build_current_thread_runtime().block_on(fut),
}
}
static SHARED_IO_RUNTIME: OnceLock<Arc<runtime::Runtime>> = OnceLock::new();
pub(crate) fn shared_io_runtime() -> Arc<runtime::Runtime> {
Arc::clone(SHARED_IO_RUNTIME.get_or_init(|| build_query_runtime("infino-io")))
}
fn build_query_runtime(thread_name: &str) -> Arc<runtime::Runtime> {
let workers = thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(FALLBACK_QUERY_RUNTIME_WORKERS);
Arc::new(
runtime::Builder::new_multi_thread()
.worker_threads(workers)
.enable_all()
.thread_name(thread_name)
.build()
.expect(
"invariant: tokio Runtime build only fails on \
catastrophic OS resource exhaustion",
),
)
}
fn build_current_thread_runtime() -> runtime::Runtime {
runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect(
"invariant: tokio Runtime build only fails on \
catastrophic OS resource exhaustion",
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bridge_on_runtime_drives_on_the_passed_runtime() {
let owned = build_query_runtime("bridge-test");
let owned_id = format!("{:?}", owned.handle().id());
let seen = bridge_on_runtime(async { format!("{:?}", Handle::current().id()) }, &owned);
assert_eq!(
seen, owned_id,
"no-ambient drive must use the passed runtime"
);
let ambient = build_query_runtime("bridge-test-ambient");
let owned_for_task = Arc::clone(&owned);
let seen = ambient.block_on(async move {
tokio::spawn(async move {
bridge_on_runtime(
async { format!("{:?}", Handle::current().id()) },
&owned_for_task,
)
})
.await
.expect("bridge task")
});
assert_eq!(
seen, owned_id,
"an ambient runtime must not capture the drive"
);
}
}