Skip to main content

moduvex_runtime/
runtime.rs

1//! Runtime builder and handle.
2//!
3//! Provides [`RuntimeBuilder`] for configuring and constructing a [`Runtime`],
4//! and [`Runtime`] for driving async entry points.
5//!
6//! # Example
7//! ```
8//! use moduvex_runtime::Runtime;
9//!
10//! let rt = Runtime::builder()
11//!     .thread_per_core()
12//!     .enable_io()
13//!     .enable_time()
14//!     .build()
15//!     .unwrap();
16//!
17//! rt.block_on(async { 1 + 1 });
18//! ```
19//!
20//! # Multi-threaded example
21//! ```
22//! use moduvex_runtime::Runtime;
23//!
24//! let rt = Runtime::builder()
25//!     .worker_threads(4)
26//!     .build()
27//!     .unwrap();
28//!
29//! rt.block_on(async { 1 + 1 });
30//! ```
31
32use std::future::Future;
33
34use crate::executor;
35
36// ── RuntimeBuilder ───────────────────────────────────────────────────────────
37
38/// Configures a [`Runtime`].
39///
40/// Defaults to single-threaded (1 worker). Call [`worker_threads`] to opt into
41/// multi-threaded work-stealing mode.
42///
43/// [`worker_threads`]: RuntimeBuilder::worker_threads
44pub struct RuntimeBuilder {
45    /// Number of worker threads. 1 = single-threaded (default).
46    num_workers: usize,
47}
48
49impl RuntimeBuilder {
50    fn new() -> Self {
51        Self { num_workers: 1 }
52    }
53
54    /// Use thread-per-core threading model (single-threaded, default).
55    pub fn thread_per_core(self) -> Self {
56        Self { num_workers: 1 }
57    }
58
59    /// Set the number of worker threads for multi-threaded work-stealing mode.
60    ///
61    /// - `n = 1`: single-threaded (same as default)
62    /// - `n > 1`: spawns N-1 background threads; main thread is worker 0
63    ///
64    /// # Panics
65    /// Panics if `n == 0`.
66    pub fn worker_threads(self, n: usize) -> Self {
67        assert!(n > 0, "worker_threads must be at least 1");
68        Self { num_workers: n }
69    }
70
71    /// Enable the I/O reactor (no-op: always enabled).
72    pub fn enable_io(self) -> Self {
73        self
74    }
75
76    /// Enable the timer wheel (no-op: always enabled).
77    pub fn enable_time(self) -> Self {
78        self
79    }
80
81    /// Build the runtime.
82    pub fn build(self) -> std::io::Result<Runtime> {
83        Ok(Runtime {
84            num_workers: self.num_workers,
85        })
86    }
87}
88
89// ── Runtime ──────────────────────────────────────────────────────────────────
90
91/// A configured async runtime.
92///
93/// Created via [`Runtime::builder`]. Drives futures to completion with
94/// [`Runtime::block_on`].
95pub struct Runtime {
96    /// Number of worker threads (1 = single-threaded).
97    num_workers: usize,
98}
99
100impl Runtime {
101    /// Create a new [`RuntimeBuilder`].
102    pub fn builder() -> RuntimeBuilder {
103        RuntimeBuilder::new()
104    }
105
106    /// Drive `future` to completion, with `spawn()` available inside.
107    ///
108    /// Uses single-threaded mode when `num_workers == 1` (default), or
109    /// multi-threaded work-stealing when `num_workers > 1`.
110    pub fn block_on<F>(&self, future: F) -> F::Output
111    where
112        F: Future + Send + 'static,
113        F::Output: Send + 'static,
114    {
115        if self.num_workers <= 1 {
116            executor::block_on_with_spawn(future)
117        } else {
118            executor::block_on_multi(future, self.num_workers)
119        }
120    }
121}
122
123// ── Tests ────────────────────────────────────────────────────────────────────
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128
129    #[test]
130    fn builder_creates_runtime() {
131        let rt = Runtime::builder()
132            .thread_per_core()
133            .enable_io()
134            .enable_time()
135            .build()
136            .unwrap();
137        let v = rt.block_on(async { 42u32 });
138        assert_eq!(v, 42);
139    }
140
141    #[test]
142    fn runtime_spawn_works() {
143        let rt = Runtime::builder().build().unwrap();
144        let result = rt.block_on(async {
145            let jh = crate::spawn(async { 100u32 });
146            jh.await.unwrap()
147        });
148        assert_eq!(result, 100);
149    }
150
151    #[test]
152    fn runtime_worker_threads_api() {
153        let rt = Runtime::builder()
154            .worker_threads(2)
155            .build()
156            .unwrap();
157        let v = rt.block_on(async { 7u32 });
158        assert_eq!(v, 7);
159    }
160
161    #[test]
162    fn runtime_multi_thread_spawn() {
163        use std::sync::atomic::{AtomicUsize, Ordering};
164        use std::sync::Arc;
165
166        let rt = Runtime::builder().worker_threads(4).build().unwrap();
167        let counter = Arc::new(AtomicUsize::new(0));
168        let c = counter.clone();
169
170        rt.block_on(async move {
171            let mut handles = Vec::new();
172            for _ in 0..50 {
173                let cc = c.clone();
174                handles.push(crate::spawn(async move {
175                    cc.fetch_add(1, Ordering::SeqCst);
176                }));
177            }
178            for h in handles {
179                h.await.unwrap();
180            }
181        });
182
183        assert_eq!(counter.load(std::sync::atomic::Ordering::SeqCst), 50);
184    }
185
186    #[test]
187    #[should_panic(expected = "worker_threads must be at least 1")]
188    fn runtime_zero_workers_panics() {
189        Runtime::builder().worker_threads(0).build().unwrap();
190    }
191}