use std::sync::OnceLock;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::thread;
use std::time::Duration;
use futures::Future;
use net_traits::AsyncRuntime;
use tokio::runtime::{Builder, Handle, Runtime};
pub struct AsyncRuntimeHolder {
runtime: Option<Runtime>,
}
impl AsyncRuntimeHolder {
pub(crate) fn new(runtime: Runtime) -> Self {
Self {
runtime: Some(runtime),
}
}
pub(crate) fn new_empty() -> Self {
Self { runtime: None }
}
}
impl AsyncRuntime for AsyncRuntimeHolder {
fn shutdown(&mut self) {
if let Some(runtime) = self.runtime.take() {
runtime.shutdown_timeout(Duration::from_millis(100));
}
}
}
static ASYNC_RUNTIME_HANDLE: OnceLock<Handle> = OnceLock::new();
pub fn init_async_runtime() -> Box<dyn AsyncRuntime> {
let runtime = Builder::new_multi_thread()
.thread_name_fn(|| {
static ATOMIC_ID: AtomicUsize = AtomicUsize::new(0);
let id = ATOMIC_ID.fetch_add(1, Ordering::Relaxed);
format!("tokio-runtime-{}", id)
})
.worker_threads(
thread::available_parallelism()
.map(|i| i.get())
.unwrap_or(servo_config::pref!(thread_pool_fallback_workers) as usize)
.min(servo_config::pref!(thread_pool_async_runtime_workers_max).max(1) as usize),
)
.enable_io()
.enable_time()
.build()
.expect("Unable to build tokio-runtime runtime");
let is_first_init = ASYNC_RUNTIME_HANDLE.set(runtime.handle().clone()).is_ok();
if is_first_init {
std::mem::forget(runtime);
log::debug!(
"async_runtime handle installed (first init) — runtime leaked process-wide"
);
} else {
log::debug!(
"async_runtime handle already initialized — idempotent skip (BaoRuntime multi-instance)"
);
}
Box::new(AsyncRuntimeHolder::new_empty())
}
pub fn async_runtime_initialized() -> bool {
ASYNC_RUNTIME_HANDLE.get().is_some()
}
pub fn spawn_task<F>(task: F)
where
F: Future + 'static + std::marker::Send,
F::Output: Send + 'static,
{
if let Some(handle) = ASYNC_RUNTIME_HANDLE.get() {
handle.spawn(task);
} else {
log::warn!("async_runtime not available — task dropped (BaoRuntime multi-instance transient)");
}
}
pub fn spawn_blocking_task<F, R>(task: F) -> F::Output
where
F: Future,
{
ASYNC_RUNTIME_HANDLE
.get()
.expect("Runtime handle should be initialized on start-up")
.block_on(task)
}