Skip to main content

reifydb_runtime/
lib.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4//! Process-level runtime: actor system, thread pools, async executors, time and randomness, and the synchronisation
5//! primitives the rest of the workspace builds on. The `SharedRuntime` handle carries the actor system, the pool set,
6//! the clock, and the seeded RNG together so any subsystem that needs to spawn work, sleep, or generate ids gets a
7//! consistent view of the world.
8//!
9//! The crate abstracts platform differences: native targets get a tokio-backed pool, WebAssembly gets a single-task
10//! executor, the deterministic-simulation target (`reifydb_target = "dst"`) gets a virtual scheduler. All three sit
11//! behind the same `SharedRuntime` API so callers do not branch on platform.
12//!
13//! Invariant: `SharedRuntime::seeded(...)` is what produces a deterministic ReifyDB - same seed, same trace. Any
14//! source of non-determinism inside the runtime (an unmocked clock, an unseeded RNG, a pool that schedules outside
15//! the seeded executor) defeats DST replays and breaks the simulation harness.
16
17#![cfg_attr(not(debug_assertions), deny(clippy::disallowed_methods))]
18#![cfg_attr(debug_assertions, warn(clippy::disallowed_methods))]
19#![allow(clippy::tabs_in_doc_comments)]
20#![allow(dead_code)]
21
22pub mod context;
23
24pub mod hash;
25
26pub mod pool;
27
28pub mod shutdown;
29
30pub mod sync;
31
32pub mod actor;
33
34pub mod version_epoch;
35
36#[cfg(not(reifydb_target = "dst"))]
37use std::future::Future;
38
39use crate::{
40	actor::system::ActorSystem,
41	context::clock::{Clock, MockClock},
42	pool::{PoolConfig, Pools},
43	shutdown::Shutdown,
44};
45
46#[derive(Clone)]
47pub struct RuntimeConfig {
48	pub clock: Clock,
49	pub rng: context::rng::Rng,
50}
51
52impl Default for RuntimeConfig {
53	fn default() -> Self {
54		Self {
55			clock: Clock::Real,
56			rng: context::rng::Rng::default(),
57		}
58	}
59}
60
61impl RuntimeConfig {
62	pub fn seeded(mut self, seed: u64) -> Self {
63		self.clock = Clock::Mock(MockClock::from_millis(seed));
64		self.rng = context::rng::Rng::seeded(seed);
65		self
66	}
67}
68
69use std::fmt;
70#[cfg(target_arch = "wasm32")]
71use std::{
72	pin::Pin,
73	task::{Context, Poll},
74};
75
76#[cfg(target_arch = "wasm32")]
77use futures_util::future::LocalBoxFuture;
78#[cfg(all(not(target_arch = "wasm32"), not(reifydb_target = "dst")))]
79use tokio::runtime as tokio_runtime;
80#[cfg(all(not(target_arch = "wasm32"), not(reifydb_target = "dst")))]
81use tokio::task::JoinHandle;
82
83#[cfg(target_arch = "wasm32")]
84#[derive(Clone, Copy, Debug)]
85pub struct WasmHandle;
86
87#[cfg(target_arch = "wasm32")]
88pub struct WasmJoinHandle<T> {
89	future: LocalBoxFuture<'static, T>,
90}
91
92#[cfg(target_arch = "wasm32")]
93impl<T> Future for WasmJoinHandle<T> {
94	type Output = Result<T, WasmJoinError>;
95
96	fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
97		match self.future.as_mut().poll(cx) {
98			Poll::Ready(v) => Poll::Ready(Ok(v)),
99			Poll::Pending => Poll::Pending,
100		}
101	}
102}
103
104#[cfg(target_arch = "wasm32")]
105#[derive(Debug)]
106pub struct WasmJoinError;
107
108#[cfg(target_arch = "wasm32")]
109impl fmt::Display for WasmJoinError {
110	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111		write!(f, "WASM task failed")
112	}
113}
114
115#[cfg(target_arch = "wasm32")]
116use std::error::Error;
117
118#[cfg(target_arch = "wasm32")]
119impl Error for WasmJoinError {}
120
121use crate::actor::system::ActorSpawner;
122
123pub struct Runtime {
124	system: ActorSystem,
125	pools: Pools,
126	clock: Clock,
127	rng: context::rng::Rng,
128}
129
130impl Runtime {
131	pub fn from_config(config: RuntimeConfig, pools: PoolConfig) -> Self {
132		let pools = Pools::new(pools);
133		let system = ActorSystem::new(pools.clone(), config.clock.clone());
134
135		Self {
136			system,
137			pools,
138			clock: config.clock,
139			rng: config.rng,
140		}
141	}
142
143	pub fn handle(&self) -> RuntimeHandle {
144		RuntimeHandle {
145			system: self.system.clone(),
146			pools: self.pools.clone(),
147			clock: self.clock.clone(),
148			rng: self.rng.clone(),
149		}
150	}
151
152	pub fn actor_system(&self) -> ActorSystem {
153		self.system.clone()
154	}
155
156	pub fn spawner(&self) -> ActorSpawner {
157		self.system.spawner()
158	}
159
160	pub fn clock(&self) -> &Clock {
161		&self.clock
162	}
163
164	pub fn rng(&self) -> &context::rng::Rng {
165		&self.rng
166	}
167
168	#[cfg(all(not(target_arch = "wasm32"), not(reifydb_target = "dst")))]
169	pub fn tokio(&self) -> tokio_runtime::Handle {
170		self.pools.handle()
171	}
172
173	#[cfg(target_arch = "wasm32")]
174	pub fn tokio(&self) -> WasmHandle {
175		WasmHandle
176	}
177
178	#[cfg(all(not(target_arch = "wasm32"), not(reifydb_target = "dst")))]
179	pub fn spawn<F>(&self, future: F) -> JoinHandle<F::Output>
180	where
181		F: Future + Send + 'static,
182		F::Output: Send + 'static,
183	{
184		self.pools.spawn(future)
185	}
186
187	#[cfg(target_arch = "wasm32")]
188	pub fn spawn<F>(&self, future: F) -> WasmJoinHandle<F::Output>
189	where
190		F: Future + 'static,
191		F::Output: 'static,
192	{
193		WasmJoinHandle {
194			future: Box::pin(future),
195		}
196	}
197
198	#[cfg(all(not(target_arch = "wasm32"), not(reifydb_target = "dst")))]
199	pub fn block_on<F>(&self, future: F) -> F::Output
200	where
201		F: Future,
202	{
203		self.pools.block_on(future)
204	}
205
206	#[cfg(target_arch = "wasm32")]
207	pub fn block_on<F>(&self, _future: F) -> F::Output
208	where
209		F: Future,
210	{
211		unimplemented!("block_on not supported in WASM - use async execution instead")
212	}
213}
214
215impl Shutdown for Runtime {
216	fn shutdown(&self) {
217		self.system.shutdown();
218		let _ = self.system.join();
219		self.pools.shutdown();
220	}
221}
222
223impl Drop for Runtime {
224	fn drop(&mut self) {
225		self.shutdown();
226	}
227}
228
229impl fmt::Debug for Runtime {
230	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
231		f.debug_struct("Runtime").finish_non_exhaustive()
232	}
233}
234
235#[derive(Clone)]
236pub struct RuntimeHandle {
237	system: ActorSystem,
238	pools: Pools,
239	clock: Clock,
240	rng: context::rng::Rng,
241}
242
243impl RuntimeHandle {
244	pub fn actor_system(&self) -> ActorSystem {
245		self.system.clone()
246	}
247
248	pub fn spawner(&self) -> ActorSpawner {
249		self.system.spawner()
250	}
251
252	pub fn pools(&self) -> Pools {
253		self.pools.clone()
254	}
255
256	pub fn clock(&self) -> &Clock {
257		&self.clock
258	}
259
260	pub fn rng(&self) -> &context::rng::Rng {
261		&self.rng
262	}
263
264	#[cfg(all(not(target_arch = "wasm32"), not(reifydb_target = "dst")))]
265	pub fn tokio(&self) -> tokio_runtime::Handle {
266		self.pools.handle()
267	}
268
269	#[cfg(target_arch = "wasm32")]
270	pub fn tokio(&self) -> WasmHandle {
271		WasmHandle
272	}
273
274	#[cfg(all(not(target_arch = "wasm32"), not(reifydb_target = "dst")))]
275	pub fn spawn<F>(&self, future: F) -> JoinHandle<F::Output>
276	where
277		F: Future + Send + 'static,
278		F::Output: Send + 'static,
279	{
280		self.pools.spawn(future)
281	}
282
283	#[cfg(target_arch = "wasm32")]
284	pub fn spawn<F>(&self, future: F) -> WasmJoinHandle<F::Output>
285	where
286		F: Future + 'static,
287		F::Output: 'static,
288	{
289		WasmJoinHandle {
290			future: Box::pin(future),
291		}
292	}
293
294	#[cfg(all(not(target_arch = "wasm32"), not(reifydb_target = "dst")))]
295	pub fn block_on<F>(&self, future: F) -> F::Output
296	where
297		F: Future,
298	{
299		self.pools.block_on(future)
300	}
301
302	#[cfg(target_arch = "wasm32")]
303	pub fn block_on<F>(&self, _future: F) -> F::Output
304	where
305		F: Future,
306	{
307		unimplemented!("block_on not supported in WASM - use async execution instead")
308	}
309}
310
311impl fmt::Debug for RuntimeHandle {
312	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
313		f.debug_struct("RuntimeHandle").finish_non_exhaustive()
314	}
315}
316
317#[cfg(all(test, not(reifydb_single_threaded)))]
318mod tests {
319	use super::*;
320
321	fn test_config() -> RuntimeConfig {
322		RuntimeConfig::default()
323	}
324
325	fn test_pools() -> PoolConfig {
326		PoolConfig {
327			async_threads: 2,
328			system_threads: 2,
329			query_threads: 2,
330			commit_threads: 2,
331			background_threads: 1,
332		}
333	}
334
335	#[test]
336	fn test_runtime_creation() {
337		let runtime = Runtime::from_config(test_config(), test_pools());
338		let result = runtime.block_on(async { 42 });
339		assert_eq!(result, 42);
340	}
341
342	#[test]
343	fn test_spawn() {
344		let runtime = Runtime::from_config(test_config(), test_pools());
345		let handle = runtime.spawn(async { 123 });
346		let result = runtime.block_on(handle).unwrap();
347		assert_eq!(result, 123);
348	}
349
350	#[test]
351	fn test_actor_system_accessible() {
352		let runtime = Runtime::from_config(test_config(), test_pools());
353		let _system = runtime.actor_system();
354	}
355
356	#[test]
357	fn test_shutdown_drops_runtime() {
358		let runtime = Runtime::from_config(test_config(), test_pools());
359		let spawner = runtime.spawner();
360		assert!(spawner.is_alive());
361		drop(runtime);
362		assert!(!spawner.is_alive());
363	}
364}