Skip to main content

reifydb_runtime/
lib.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4#![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 tokio(&self) -> tokio_runtime::Handle {
178		self.pools.handle()
179	}
180
181	#[cfg(target_arch = "wasm32")]
182	pub fn tokio(&self) -> WasmHandle {
183		WasmHandle
184	}
185
186	#[cfg(all(reifydb_target = "host", not(reifydb_dst)))]
187	pub fn spawn<F>(&self, future: F) -> JoinHandle<F::Output>
188	where
189		F: Future + Send + 'static,
190		F::Output: Send + 'static,
191	{
192		self.pools.spawn(future)
193	}
194
195	#[cfg(target_arch = "wasm32")]
196	pub fn spawn<F>(&self, future: F) -> WasmJoinHandle<F::Output>
197	where
198		F: Future + 'static,
199		F::Output: 'static,
200	{
201		WasmJoinHandle {
202			future: Box::pin(future),
203		}
204	}
205
206	#[cfg(all(reifydb_target = "host", not(reifydb_dst)))]
207	pub fn block_on<F>(&self, future: F) -> F::Output
208	where
209		F: Future,
210	{
211		self.pools.block_on(future)
212	}
213
214	#[cfg(target_arch = "wasm32")]
215	pub fn block_on<F>(&self, _future: F) -> F::Output
216	where
217		F: Future,
218	{
219		unimplemented!("block_on not supported in WASM - use async execution instead")
220	}
221}
222
223impl Shutdown for Runtime {
224	fn shutdown(&self) -> ValueResult<()> {
225		let mut errors = ShutdownErrors::new("runtime");
226		self.system.shutdown();
227		if let Err(err) = self.system.join() {
228			errors.fail("actor system", err);
229		}
230		self.pools.shutdown();
231		errors.finish()
232	}
233}
234
235impl Drop for Runtime {
236	fn drop(&mut self) {
237		if let Err(err) = self.shutdown() {
238			if thread::panicking() {
239				eprintln!("runtime shutdown failed during unwind: {err}");
240			} else {
241				panic!("runtime shutdown failed: {err}");
242			}
243		}
244	}
245}
246
247impl fmt::Debug for Runtime {
248	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
249		f.debug_struct("Runtime").finish_non_exhaustive()
250	}
251}
252
253#[derive(Clone)]
254pub struct RuntimeHandle {
255	system: ActorSystem,
256	pools: Pools,
257	clock: Clock,
258	rng: context::rng::Rng,
259}
260
261impl RuntimeHandle {
262	pub fn actor_system(&self) -> ActorSystem {
263		self.system.clone()
264	}
265
266	pub fn spawner(&self) -> ActorSpawner {
267		self.system.spawner()
268	}
269
270	pub fn pools(&self) -> Pools {
271		self.pools.clone()
272	}
273
274	pub fn clock(&self) -> &Clock {
275		&self.clock
276	}
277
278	pub fn rng(&self) -> &context::rng::Rng {
279		&self.rng
280	}
281
282	#[cfg(all(reifydb_target = "host", not(reifydb_dst)))]
283	pub fn tokio(&self) -> tokio_runtime::Handle {
284		self.pools.handle()
285	}
286
287	#[cfg(target_arch = "wasm32")]
288	pub fn tokio(&self) -> WasmHandle {
289		WasmHandle
290	}
291
292	#[cfg(all(reifydb_target = "host", not(reifydb_dst)))]
293	pub fn spawn<F>(&self, future: F) -> JoinHandle<F::Output>
294	where
295		F: Future + Send + 'static,
296		F::Output: Send + 'static,
297	{
298		self.pools.spawn(future)
299	}
300
301	#[cfg(target_arch = "wasm32")]
302	pub fn spawn<F>(&self, future: F) -> WasmJoinHandle<F::Output>
303	where
304		F: Future + 'static,
305		F::Output: 'static,
306	{
307		WasmJoinHandle {
308			future: Box::pin(future),
309		}
310	}
311
312	#[cfg(all(reifydb_target = "host", not(reifydb_dst)))]
313	pub fn block_on<F>(&self, future: F) -> F::Output
314	where
315		F: Future,
316	{
317		self.pools.block_on(future)
318	}
319
320	#[cfg(target_arch = "wasm32")]
321	pub fn block_on<F>(&self, _future: F) -> F::Output
322	where
323		F: Future,
324	{
325		unimplemented!("block_on not supported in WASM - use async execution instead")
326	}
327}
328
329impl fmt::Debug for RuntimeHandle {
330	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
331		f.debug_struct("RuntimeHandle").finish_non_exhaustive()
332	}
333}
334
335#[cfg(all(test, not(reifydb_single_threaded)))]
336mod tests {
337	use super::*;
338
339	fn test_config() -> RuntimeConfig {
340		RuntimeConfig::default()
341	}
342
343	fn test_pools() -> PoolConfig {
344		PoolConfig {
345			coordination_threads: 2,
346			flow_threads: 2,
347			maintenance_threads: 1,
348			task_threads: 2,
349			compute_threads: 2,
350			async_threads: 2,
351		}
352	}
353
354	#[test]
355	fn test_runtime_creation() {
356		let runtime = Runtime::from_config(test_config(), test_pools());
357		let result = runtime.block_on(async { 42 });
358		assert_eq!(result, 42);
359	}
360
361	#[test]
362	fn test_spawn() {
363		let runtime = Runtime::from_config(test_config(), test_pools());
364		let handle = runtime.spawn(async { 123 });
365		let result = runtime.block_on(handle).unwrap();
366		assert_eq!(result, 123);
367	}
368
369	#[test]
370	fn test_actor_system_accessible() {
371		let runtime = Runtime::from_config(test_config(), test_pools());
372		let _system = runtime.actor_system();
373	}
374
375	#[test]
376	fn test_shutdown_drops_runtime() {
377		let runtime = Runtime::from_config(test_config(), test_pools());
378		let spawner = runtime.spawner();
379		assert!(spawner.is_alive());
380		drop(runtime);
381		assert!(!spawner.is_alive());
382	}
383}