Skip to main content

comindly_core/
rt.rs

1//! Running async code from synchronous callers (CLI commands, the terminal UI, blocking-pool
2//! work): one shared runtime for the process, never one per call.
3
4use std::future::Future;
5use std::sync::OnceLock;
6
7use tokio::runtime::{Handle, Runtime, RuntimeFlavor};
8
9/// The process's shared runtime, built on first use.
10pub fn shared() -> &'static Runtime {
11    static RT: OnceLock<Runtime> = OnceLock::new();
12    RT.get_or_init(|| {
13        tokio::runtime::Builder::new_multi_thread()
14            .thread_name("comindly")
15            .enable_all()
16            .build()
17            .expect("tokio runtime")
18    })
19}
20
21/// Run `f` to completion from synchronous code. Inside a multi-threaded runtime the current
22/// thread hands its work over while it waits; elsewhere the shared runtime runs it.
23pub fn block_on<F>(f: F) -> F::Output
24where
25    F: Future + Send,
26    F::Output: Send,
27{
28    match Handle::try_current() {
29        Ok(h) if h.runtime_flavor() == RuntimeFlavor::MultiThread => {
30            tokio::task::block_in_place(|| h.block_on(f))
31        }
32        Ok(_) => std::thread::scope(|s| {
33            s.spawn(|| shared().block_on(f))
34                .join()
35                .expect("a worker thread failed")
36        }),
37        Err(_) => shared().block_on(f),
38    }
39}
40
41#[cfg(test)]
42mod tests {
43    use super::*;
44
45    #[test]
46    fn blocking_calls_nest_inside_the_runtime_and_its_workers() {
47        let from_main = shared().block_on(async { block_on(async { block_on(async { 1 }) }) });
48        let on_worker = shared()
49            .block_on(shared().spawn(async { block_on(async { block_on(async { 2 }) }) }))
50            .unwrap();
51        assert_eq!((from_main, on_worker), (1, 2));
52    }
53}