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}