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 pool;
25
26pub mod shutdown;
27
28pub mod sync;
29
30pub mod actor;
31
32pub mod version_epoch;
33
34#[cfg(not(reifydb_target = "dst"))]
35use std::future::Future;
36
37use crate::{
38	actor::system::ActorSystem,
39	context::clock::{Clock, MockClock},
40	pool::{PoolConfig, Pools},
41	shutdown::Shutdown,
42};
43
44#[derive(Clone)]
45pub struct RuntimeConfig {
46	pub clock: Clock,
47	pub rng: context::rng::Rng,
48}
49
50impl Default for RuntimeConfig {
51	fn default() -> Self {
52		Self {
53			clock: Clock::Real,
54			rng: context::rng::Rng::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
67use std::fmt;
68#[cfg(target_arch = "wasm32")]
69use std::{
70	pin::Pin,
71	task::{Context, Poll},
72};
73
74#[cfg(target_arch = "wasm32")]
75use futures_util::future::LocalBoxFuture;
76#[cfg(all(not(target_arch = "wasm32"), not(reifydb_target = "dst")))]
77use tokio::runtime as tokio_runtime;
78#[cfg(all(not(target_arch = "wasm32"), not(reifydb_target = "dst")))]
79use tokio::task::JoinHandle;
80
81#[cfg(target_arch = "wasm32")]
82#[derive(Clone, Copy, Debug)]
83pub struct WasmHandle;
84
85#[cfg(target_arch = "wasm32")]
86pub struct WasmJoinHandle<T> {
87	future: LocalBoxFuture<'static, T>,
88}
89
90#[cfg(target_arch = "wasm32")]
91impl<T> Future for WasmJoinHandle<T> {
92	type Output = Result<T, WasmJoinError>;
93
94	fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
95		match self.future.as_mut().poll(cx) {
96			Poll::Ready(v) => Poll::Ready(Ok(v)),
97			Poll::Pending => Poll::Pending,
98		}
99	}
100}
101
102#[cfg(target_arch = "wasm32")]
103#[derive(Debug)]
104pub struct WasmJoinError;
105
106#[cfg(target_arch = "wasm32")]
107impl fmt::Display for WasmJoinError {
108	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109		write!(f, "WASM task failed")
110	}
111}
112
113#[cfg(target_arch = "wasm32")]
114use std::error::Error;
115
116#[cfg(target_arch = "wasm32")]
117impl Error for WasmJoinError {}
118
119use crate::actor::system::ActorSpawner;
120
121pub struct Runtime {
122	system: ActorSystem,
123	pools: Pools,
124	clock: Clock,
125	rng: context::rng::Rng,
126}
127
128impl Runtime {
129	pub fn from_config(config: RuntimeConfig, pools: PoolConfig) -> Self {
130		let pools = Pools::new(pools);
131		let system = ActorSystem::new(pools.clone(), config.clock.clone());
132
133		Self {
134			system,
135			pools,
136			clock: config.clock,
137			rng: config.rng,
138		}
139	}
140
141	pub fn handle(&self) -> RuntimeHandle {
142		RuntimeHandle {
143			system: self.system.clone(),
144			pools: self.pools.clone(),
145			clock: self.clock.clone(),
146			rng: self.rng.clone(),
147		}
148	}
149
150	pub fn actor_system(&self) -> ActorSystem {
151		self.system.clone()
152	}
153
154	pub fn spawner(&self) -> ActorSpawner {
155		self.system.spawner()
156	}
157
158	pub fn clock(&self) -> &Clock {
159		&self.clock
160	}
161
162	pub fn rng(&self) -> &context::rng::Rng {
163		&self.rng
164	}
165
166	#[cfg(all(not(target_arch = "wasm32"), not(reifydb_target = "dst")))]
167	pub fn tokio(&self) -> tokio_runtime::Handle {
168		self.pools.handle()
169	}
170
171	#[cfg(target_arch = "wasm32")]
172	pub fn tokio(&self) -> WasmHandle {
173		WasmHandle
174	}
175
176	#[cfg(all(not(target_arch = "wasm32"), not(reifydb_target = "dst")))]
177	pub fn spawn<F>(&self, future: F) -> JoinHandle<F::Output>
178	where
179		F: Future + Send + 'static,
180		F::Output: Send + 'static,
181	{
182		self.pools.spawn(future)
183	}
184
185	#[cfg(target_arch = "wasm32")]
186	pub fn spawn<F>(&self, future: F) -> WasmJoinHandle<F::Output>
187	where
188		F: Future + 'static,
189		F::Output: 'static,
190	{
191		WasmJoinHandle {
192			future: Box::pin(future),
193		}
194	}
195
196	#[cfg(all(not(target_arch = "wasm32"), not(reifydb_target = "dst")))]
197	pub fn block_on<F>(&self, future: F) -> F::Output
198	where
199		F: Future,
200	{
201		self.pools.block_on(future)
202	}
203
204	#[cfg(target_arch = "wasm32")]
205	pub fn block_on<F>(&self, _future: F) -> F::Output
206	where
207		F: Future,
208	{
209		unimplemented!("block_on not supported in WASM - use async execution instead")
210	}
211}
212
213impl Shutdown for Runtime {
214	fn shutdown(&self) {
215		self.system.shutdown();
216		let _ = self.system.join();
217		self.pools.shutdown();
218	}
219}
220
221impl Drop for Runtime {
222	fn drop(&mut self) {
223		self.shutdown();
224	}
225}
226
227impl fmt::Debug for Runtime {
228	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
229		f.debug_struct("Runtime").finish_non_exhaustive()
230	}
231}
232
233#[derive(Clone)]
234pub struct RuntimeHandle {
235	system: ActorSystem,
236	pools: Pools,
237	clock: Clock,
238	rng: context::rng::Rng,
239}
240
241impl RuntimeHandle {
242	pub fn actor_system(&self) -> ActorSystem {
243		self.system.clone()
244	}
245
246	pub fn spawner(&self) -> ActorSpawner {
247		self.system.spawner()
248	}
249
250	pub fn pools(&self) -> Pools {
251		self.pools.clone()
252	}
253
254	pub fn clock(&self) -> &Clock {
255		&self.clock
256	}
257
258	pub fn rng(&self) -> &context::rng::Rng {
259		&self.rng
260	}
261
262	#[cfg(all(not(target_arch = "wasm32"), not(reifydb_target = "dst")))]
263	pub fn tokio(&self) -> tokio_runtime::Handle {
264		self.pools.handle()
265	}
266
267	#[cfg(target_arch = "wasm32")]
268	pub fn tokio(&self) -> WasmHandle {
269		WasmHandle
270	}
271
272	#[cfg(all(not(target_arch = "wasm32"), not(reifydb_target = "dst")))]
273	pub fn spawn<F>(&self, future: F) -> JoinHandle<F::Output>
274	where
275		F: Future + Send + 'static,
276		F::Output: Send + 'static,
277	{
278		self.pools.spawn(future)
279	}
280
281	#[cfg(target_arch = "wasm32")]
282	pub fn spawn<F>(&self, future: F) -> WasmJoinHandle<F::Output>
283	where
284		F: Future + 'static,
285		F::Output: 'static,
286	{
287		WasmJoinHandle {
288			future: Box::pin(future),
289		}
290	}
291
292	#[cfg(all(not(target_arch = "wasm32"), not(reifydb_target = "dst")))]
293	pub fn block_on<F>(&self, future: F) -> F::Output
294	where
295		F: Future,
296	{
297		self.pools.block_on(future)
298	}
299
300	#[cfg(target_arch = "wasm32")]
301	pub fn block_on<F>(&self, _future: F) -> F::Output
302	where
303		F: Future,
304	{
305		unimplemented!("block_on not supported in WASM - use async execution instead")
306	}
307}
308
309impl fmt::Debug for RuntimeHandle {
310	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
311		f.debug_struct("RuntimeHandle").finish_non_exhaustive()
312	}
313}
314
315#[cfg(all(test, not(reifydb_single_threaded)))]
316mod tests {
317	use super::*;
318
319	fn test_config() -> RuntimeConfig {
320		RuntimeConfig::default()
321	}
322
323	fn test_pools() -> PoolConfig {
324		PoolConfig {
325			async_threads: 2,
326			system_threads: 2,
327			query_threads: 2,
328			commit_threads: 2,
329			background_threads: 1,
330		}
331	}
332
333	#[test]
334	fn test_runtime_creation() {
335		let runtime = Runtime::from_config(test_config(), test_pools());
336		let result = runtime.block_on(async { 42 });
337		assert_eq!(result, 42);
338	}
339
340	#[test]
341	fn test_spawn() {
342		let runtime = Runtime::from_config(test_config(), test_pools());
343		let handle = runtime.spawn(async { 123 });
344		let result = runtime.block_on(handle).unwrap();
345		assert_eq!(result, 123);
346	}
347
348	#[test]
349	fn test_actor_system_accessible() {
350		let runtime = Runtime::from_config(test_config(), test_pools());
351		let _system = runtime.actor_system();
352	}
353
354	#[test]
355	fn test_shutdown_drops_runtime() {
356		let runtime = Runtime::from_config(test_config(), test_pools());
357		let spawner = runtime.spawner();
358		assert!(spawner.is_alive());
359		drop(runtime);
360		assert!(!spawner.is_alive());
361	}
362}