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}