1#![cfg_attr(not(debug_assertions), deny(clippy::disallowed_methods))]
5#![cfg_attr(debug_assertions, warn(clippy::disallowed_methods))]
6#![allow(clippy::tabs_in_doc_comments)]
7
8pub mod cache;
9
10pub mod context;
11
12pub mod fatal;
13
14pub mod io;
15
16pub mod pool;
17
18pub mod shutdown;
19
20pub mod sync;
21
22pub mod actor;
23
24pub mod version_epoch;
25
26#[cfg(reifydb_dst)]
27pub mod testing;
28
29#[cfg(not(reifydb_dst))]
30use std::future::Future;
31use std::thread;
32
33use reifydb_value::Result as ValueResult;
34
35use crate::{
36 actor::system::ActorSystem,
37 context::clock::{Clock, MockClock},
38 pool::{PoolConfig, Pools},
39 shutdown::{Shutdown, ShutdownErrors},
40};
41
42#[derive(Clone)]
43pub struct RuntimeConfig {
44 pub clock: Clock,
45 pub rng: context::rng::Rng,
46 pub fatal: fatal::FatalConfig,
47}
48
49impl Default for RuntimeConfig {
50 fn default() -> Self {
51 Self {
52 clock: Clock::Real,
53 rng: context::rng::Rng::default(),
54 fatal: fatal::FatalConfig::default(),
55 }
56 }
57}
58
59impl RuntimeConfig {
60 pub fn seeded(mut self, seed: u64) -> Self {
61 self.clock = Clock::Mock(MockClock::from_millis(seed));
62 self.rng = context::rng::Rng::seeded(seed);
63 self
64 }
65
66 pub fn fatal(mut self, config: fatal::FatalConfig) -> Self {
67 self.fatal = config;
68 self
69 }
70
71 pub fn clock(mut self, clock: Clock) -> Self {
72 self.clock = clock;
73 self
74 }
75}
76
77use std::fmt;
78#[cfg(target_arch = "wasm32")]
79use std::{
80 pin::Pin,
81 task::{Context, Poll},
82};
83
84#[cfg(target_arch = "wasm32")]
85use futures_util::future::LocalBoxFuture;
86#[cfg(all(reifydb_target = "host", not(reifydb_dst)))]
87use tokio::runtime as tokio_runtime;
88#[cfg(all(reifydb_target = "host", not(reifydb_dst)))]
89use tokio::task::JoinHandle;
90
91#[cfg(target_arch = "wasm32")]
92#[derive(Clone, Copy, Debug)]
93pub struct WasmHandle;
94
95#[cfg(target_arch = "wasm32")]
96pub struct WasmJoinHandle<T> {
97 future: LocalBoxFuture<'static, T>,
98}
99
100#[cfg(target_arch = "wasm32")]
101impl<T> Future for WasmJoinHandle<T> {
102 type Output = Result<T, WasmJoinError>;
103
104 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
105 match self.future.as_mut().poll(cx) {
106 Poll::Ready(v) => Poll::Ready(Ok(v)),
107 Poll::Pending => Poll::Pending,
108 }
109 }
110}
111
112#[cfg(target_arch = "wasm32")]
113#[derive(Debug)]
114pub struct WasmJoinError;
115
116#[cfg(target_arch = "wasm32")]
117impl fmt::Display for WasmJoinError {
118 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119 write!(f, "WASM task failed")
120 }
121}
122
123#[cfg(target_arch = "wasm32")]
124use std::error::Error;
125
126#[cfg(target_arch = "wasm32")]
127impl Error for WasmJoinError {}
128
129use crate::actor::system::ActorSpawner;
130
131pub struct Runtime {
132 system: ActorSystem,
133 pools: Pools,
134 clock: Clock,
135 rng: context::rng::Rng,
136}
137
138impl Runtime {
139 pub fn from_config(config: RuntimeConfig, pools: PoolConfig) -> Self {
140 let pools = Pools::new(pools);
141 let system = ActorSystem::new(pools.clone(), config.clock.clone());
142
143 Self {
144 system,
145 pools,
146 clock: config.clock,
147 rng: config.rng,
148 }
149 }
150
151 pub fn handle(&self) -> RuntimeHandle {
152 RuntimeHandle {
153 system: self.system.clone(),
154 pools: self.pools.clone(),
155 clock: self.clock.clone(),
156 rng: self.rng.clone(),
157 }
158 }
159
160 pub fn actor_system(&self) -> ActorSystem {
161 self.system.clone()
162 }
163
164 pub fn spawner(&self) -> ActorSpawner {
165 self.system.spawner()
166 }
167
168 pub fn clock(&self) -> &Clock {
169 &self.clock
170 }
171
172 pub fn rng(&self) -> &context::rng::Rng {
173 &self.rng
174 }
175
176 #[cfg(all(reifydb_target = "host", not(reifydb_dst)))]
177 pub fn task_spawner(&self) -> context::spawner::Spawner {
178 context::spawner::Spawner::host(self.pools.handle())
179 }
180
181 #[cfg(not(all(reifydb_target = "host", not(reifydb_dst))))]
182 pub fn task_spawner(&self) -> context::spawner::Spawner {
183 context::spawner::Spawner::inline()
184 }
185
186 #[cfg(all(reifydb_target = "host", not(reifydb_dst)))]
187 pub fn tokio(&self) -> tokio_runtime::Handle {
188 self.pools.handle()
189 }
190
191 #[cfg(target_arch = "wasm32")]
192 pub fn tokio(&self) -> WasmHandle {
193 WasmHandle
194 }
195
196 #[cfg(all(reifydb_target = "host", not(reifydb_dst)))]
197 pub fn spawn<F>(&self, future: F) -> JoinHandle<F::Output>
198 where
199 F: Future + Send + 'static,
200 F::Output: Send + 'static,
201 {
202 self.pools.spawn(future)
203 }
204
205 #[cfg(target_arch = "wasm32")]
206 pub fn spawn<F>(&self, future: F) -> WasmJoinHandle<F::Output>
207 where
208 F: Future + 'static,
209 F::Output: 'static,
210 {
211 WasmJoinHandle {
212 future: Box::pin(future),
213 }
214 }
215
216 #[cfg(all(reifydb_target = "host", not(reifydb_dst)))]
217 pub fn block_on<F>(&self, future: F) -> F::Output
218 where
219 F: Future,
220 {
221 self.pools.block_on(future)
222 }
223
224 #[cfg(target_arch = "wasm32")]
225 pub fn block_on<F>(&self, _future: F) -> F::Output
226 where
227 F: Future,
228 {
229 unimplemented!("block_on not supported in WASM - use async execution instead")
230 }
231}
232
233impl Shutdown for Runtime {
234 fn shutdown(&self) -> ValueResult<()> {
235 let mut errors = ShutdownErrors::new("runtime");
236 self.system.shutdown();
237 if let Err(err) = self.system.join() {
238 errors.fail("actor system", err);
239 }
240 self.pools.shutdown();
241 errors.finish()
242 }
243}
244
245impl Drop for Runtime {
246 fn drop(&mut self) {
247 if let Err(err) = self.shutdown() {
248 if thread::panicking() {
249 eprintln!("runtime shutdown failed during unwind: {err}");
250 } else {
251 panic!("runtime shutdown failed: {err}");
252 }
253 }
254 }
255}
256
257impl fmt::Debug for Runtime {
258 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
259 f.debug_struct("Runtime").finish_non_exhaustive()
260 }
261}
262
263#[derive(Clone)]
264pub struct RuntimeHandle {
265 system: ActorSystem,
266 pools: Pools,
267 clock: Clock,
268 rng: context::rng::Rng,
269}
270
271impl RuntimeHandle {
272 pub fn actor_system(&self) -> ActorSystem {
273 self.system.clone()
274 }
275
276 pub fn spawner(&self) -> ActorSpawner {
277 self.system.spawner()
278 }
279
280 pub fn pools(&self) -> Pools {
281 self.pools.clone()
282 }
283
284 pub fn clock(&self) -> &Clock {
285 &self.clock
286 }
287
288 pub fn rng(&self) -> &context::rng::Rng {
289 &self.rng
290 }
291
292 #[cfg(all(reifydb_target = "host", not(reifydb_dst)))]
293 pub fn task_spawner(&self) -> context::spawner::Spawner {
294 context::spawner::Spawner::host(self.pools.handle())
295 }
296
297 #[cfg(not(all(reifydb_target = "host", not(reifydb_dst))))]
298 pub fn task_spawner(&self) -> context::spawner::Spawner {
299 context::spawner::Spawner::inline()
300 }
301
302 #[cfg(all(reifydb_target = "host", not(reifydb_dst)))]
303 pub fn tokio(&self) -> tokio_runtime::Handle {
304 self.pools.handle()
305 }
306
307 #[cfg(target_arch = "wasm32")]
308 pub fn tokio(&self) -> WasmHandle {
309 WasmHandle
310 }
311
312 #[cfg(all(reifydb_target = "host", not(reifydb_dst)))]
313 pub fn spawn<F>(&self, future: F) -> JoinHandle<F::Output>
314 where
315 F: Future + Send + 'static,
316 F::Output: Send + 'static,
317 {
318 self.pools.spawn(future)
319 }
320
321 #[cfg(target_arch = "wasm32")]
322 pub fn spawn<F>(&self, future: F) -> WasmJoinHandle<F::Output>
323 where
324 F: Future + 'static,
325 F::Output: 'static,
326 {
327 WasmJoinHandle {
328 future: Box::pin(future),
329 }
330 }
331
332 #[cfg(all(reifydb_target = "host", not(reifydb_dst)))]
333 pub fn block_on<F>(&self, future: F) -> F::Output
334 where
335 F: Future,
336 {
337 self.pools.block_on(future)
338 }
339
340 #[cfg(target_arch = "wasm32")]
341 pub fn block_on<F>(&self, _future: F) -> F::Output
342 where
343 F: Future,
344 {
345 unimplemented!("block_on not supported in WASM - use async execution instead")
346 }
347}
348
349impl fmt::Debug for RuntimeHandle {
350 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
351 f.debug_struct("RuntimeHandle").finish_non_exhaustive()
352 }
353}
354
355#[cfg(all(test, not(reifydb_single_threaded)))]
356mod tests {
357 use super::*;
358
359 fn test_config() -> RuntimeConfig {
360 RuntimeConfig::default()
361 }
362
363 fn test_pools() -> PoolConfig {
364 PoolConfig {
365 coordination_threads: 2,
366 flow_threads: 2,
367 maintenance_threads: 1,
368 task_threads: 2,
369 compute_threads: 2,
370 async_threads: 2,
371 }
372 }
373
374 #[test]
375 fn test_runtime_creation() {
376 let runtime = Runtime::from_config(test_config(), test_pools());
377 let result = runtime.block_on(async { 42 });
378 assert_eq!(result, 42);
379 }
380
381 #[test]
382 fn test_spawn() {
383 let runtime = Runtime::from_config(test_config(), test_pools());
384 let handle = runtime.spawn(async { 123 });
385 let result = runtime.block_on(handle).unwrap();
386 assert_eq!(result, 123);
387 }
388
389 #[test]
390 fn test_actor_system_accessible() {
391 let runtime = Runtime::from_config(test_config(), test_pools());
392 let _system = runtime.actor_system();
393 }
394
395 #[test]
396 fn test_shutdown_drops_runtime() {
397 let runtime = Runtime::from_config(test_config(), test_pools());
398 let spawner = runtime.spawner();
399 assert!(spawner.is_alive());
400 drop(runtime);
401 assert!(!spawner.is_alive());
402 }
403}