use std::{
fmt,
future::Future,
sync::{Arc, OnceLock},
thread,
};
use rayon::ThreadPool;
use tokio::{
runtime::{self, Handle, Runtime},
sync::oneshot,
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),
}
}
pub(crate) fn spawn_on<F: FnOnce() + Send + 'static>(pool: Option<&ThreadPool>, f: F) {
match pool {
Some(pool) => pool.spawn(f),
None => rayon::spawn(f),
}
}
#[derive(Debug)]
pub(crate) struct PoolDropped(pub(crate) &'static str);
impl fmt::Display for PoolDropped {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.0)
}
}
impl std::error::Error for PoolDropped {}
pub(crate) async fn run_on_pool<R, F>(
pool: Option<&ThreadPool>,
what: &'static str,
f: F,
) -> Result<R, PoolDropped>
where
F: FnOnce() -> R + Send + 'static,
R: Send + 'static,
{
let (tx, rx) = oneshot::channel();
spawn_on(pool, move || {
let _ = tx.send(f());
});
rx.await.map_err(|_| PoolDropped(what))
}
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"
);
}
}