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 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}