Skip to main content

mesh_client/
runtime.rs

1//! Dedicated-thread tokio runtime for mesh-client.
2//!
3//! The tokio runtime is owned by a dedicated OS thread so that its `Drop`
4//! never executes inside a tokio context. The public `CoreRuntime` holds only
5//! a `Handle` plus a shutdown channel to the owning thread, which means
6//! dropping `CoreRuntime` from arbitrary call sites (including from inside a
7//! tokio task) only signals the owning thread and joins it — the real
8//! `tokio::runtime::Runtime::drop` always runs on the dedicated thread, which
9//! is itself not inside a tokio context.
10
11use std::time::Duration;
12
13#[derive(Debug, thiserror::Error)]
14pub enum RuntimeError {
15    #[error("failed to spawn dedicated tokio thread")]
16    ThreadSpawnFailed,
17    #[error("failed to receive runtime handle from dedicated thread")]
18    HandleRecvFailed,
19}
20
21pub struct CoreRuntime {
22    handle: tokio::runtime::Handle,
23    shutdown_tx: std::sync::mpsc::Sender<()>,
24    thread: Option<std::thread::JoinHandle<()>>,
25}
26
27impl CoreRuntime {
28    pub fn new() -> Result<Self, RuntimeError> {
29        let (handle_tx, handle_rx) = std::sync::mpsc::channel();
30        let (shutdown_tx, shutdown_rx) = std::sync::mpsc::channel::<()>();
31        let thread = std::thread::Builder::new()
32            .name("mesh-client-tokio".to_string())
33            .spawn(move || {
34                let rt = tokio::runtime::Builder::new_multi_thread()
35                    .enable_all()
36                    .worker_threads(2)
37                    .thread_name("mesh-core-worker")
38                    .build()
39                    .expect("tokio runtime build");
40                handle_tx.send(rt.handle().clone()).expect("send handle");
41                let _ = shutdown_rx.recv();
42                rt.shutdown_timeout(Duration::from_secs(5));
43            })
44            .map_err(|_| RuntimeError::ThreadSpawnFailed)?;
45        let handle = handle_rx
46            .recv()
47            .map_err(|_| RuntimeError::HandleRecvFailed)?;
48        Ok(Self {
49            handle,
50            shutdown_tx,
51            thread: Some(thread),
52        })
53    }
54
55    pub fn handle(&self) -> &tokio::runtime::Handle {
56        &self.handle
57    }
58}
59
60impl Drop for CoreRuntime {
61    fn drop(&mut self) {
62        let _ = self.shutdown_tx.send(());
63        if let Some(thread) = self.thread.take() {
64            let _ = thread.join();
65        }
66    }
67}