Skip to main content

mkit_core/history/
tokio_executor.rs

1//! `tokio`-runtime-backed [`Executor`] for the journaled history MMR.
2//!
3//! Layered identically to the executor introduced for transport-enc
4//! Phase 2 (issue #156) but kept inside `mkit-core::history` so the
5//! `history-mmr` feature is self-contained — consumers don't need to
6//! depend on `mkit-transport-enc` just to persist commit history.
7//!
8//! ## Threading and `block_on`
9//!
10//! [`TokioExecutor`] wraps a long-lived [`Arc<tokio::runtime::Runtime>`].
11//! Its `block_on` calls [`tokio::runtime::Handle::block_on`] on the
12//! wrapped runtime. This is safe from any thread that is **not**
13//! itself a tokio worker on the same runtime — synchronous mkit code
14//! paths (`refs::update_ref`, the CLI's commit loop) are fine, but
15//! calling `block_on` from inside a `runtime.spawn()` task on this
16//! runtime would panic. The history module never spawns tasks of its
17//! own onto this runtime, so the constraint is purely about external
18//! callers.
19
20use std::sync::Arc;
21
22use crate::protocol::async_shim::Executor;
23
24/// `Arc`-shared sync/async bridge for the journaled history MMR.
25///
26/// Wraps an owned `Arc<tokio::runtime::Runtime>`. The runtime survives
27/// for as long as any `TokioExecutor` clone is held, which means a
28/// single [`crate::history::CommitHistory`] holding one executor keeps
29/// the runtime alive for every `append` / `prove` / `root` call in its
30/// lifetime.
31#[derive(Clone, Debug)]
32pub struct TokioExecutor {
33    runtime: Arc<tokio::runtime::Runtime>,
34}
35
36impl TokioExecutor {
37    /// Build a fresh multi-thread tokio runtime and wrap it.
38    ///
39    /// Two worker threads is enough to keep the commonware journaled
40    /// MMR's internal locks unblocked without dedicating a thread per
41    /// shard; bump if profiling ever shows contention.
42    ///
43    /// # Errors
44    ///
45    /// Returns the underlying [`std::io::Error`] if tokio refuses to
46    /// allocate worker threads (almost always means the host is out of
47    /// resources).
48    pub fn new() -> std::io::Result<Self> {
49        let runtime = tokio::runtime::Builder::new_multi_thread()
50            .worker_threads(2)
51            .enable_all()
52            .thread_name("mkit-history")
53            .build()?;
54        Ok(Self {
55            runtime: Arc::new(runtime),
56        })
57    }
58
59    /// Construct a `TokioExecutor` that shares an existing tokio
60    /// runtime. Useful when the embedding application already owns
61    /// one (e.g. a long-running server that wants the history MMR and
62    /// other tokio-based services on the same pool).
63    #[must_use]
64    pub fn from_runtime(runtime: Arc<tokio::runtime::Runtime>) -> Self {
65        Self { runtime }
66    }
67
68    /// Borrow the wrapped runtime's handle. Surfaced for callers that
69    /// want to run helper async tasks on the same runtime the history
70    /// MMR uses.
71    #[must_use]
72    pub fn handle(&self) -> tokio::runtime::Handle {
73        self.runtime.handle().clone()
74    }
75}
76
77impl Executor for TokioExecutor {
78    fn block_on<F, T>(&self, fut: F) -> T
79    where
80        F: core::future::Future<Output = T> + Send,
81        T: Send,
82    {
83        self.runtime.handle().block_on(fut)
84    }
85}