Skip to main content

reifydb_runtime/actor/
context.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use std::sync::{
5	Arc,
6	atomic::{AtomicBool, Ordering},
7};
8
9use reifydb_value::value::duration::Duration;
10
11#[cfg(all(not(reifydb_single_threaded), not(loom)))]
12use crate::actor::timers::Repeat;
13#[cfg(reifydb_dst)]
14use crate::actor::timers::dst as dst_timers;
15#[cfg(reifydb_target = "wasi")]
16use crate::actor::timers::wasi::{schedule_once_fn, schedule_repeat, schedule_repeat_fn};
17#[cfg(reifydb_target = "wasm")]
18use crate::actor::timers::wasm::{schedule_once_fn, schedule_repeat, schedule_repeat_fn};
19use crate::actor::{mailbox::ActorRef, system::ActorSystem, timers::TimerHandle};
20
21#[derive(Clone)]
22pub struct CancellationToken {
23	cancelled: Arc<AtomicBool>,
24	parent: Option<Arc<AtomicBool>>,
25}
26
27impl CancellationToken {
28	pub fn new() -> Self {
29		Self {
30			cancelled: Arc::new(AtomicBool::new(false)),
31			parent: None,
32		}
33	}
34
35	pub fn child_token(&self) -> Self {
36		Self {
37			cancelled: Arc::new(AtomicBool::new(false)),
38			parent: Some(Arc::clone(&self.cancelled)),
39		}
40	}
41
42	pub fn cancel(&self) {
43		self.cancelled.store(true, Ordering::SeqCst);
44	}
45
46	pub fn is_cancelled(&self) -> bool {
47		self.cancelled.load(Ordering::SeqCst) || self.parent.as_ref().is_some_and(|p| p.load(Ordering::SeqCst))
48	}
49}
50
51impl Default for CancellationToken {
52	fn default() -> Self {
53		Self::new()
54	}
55}
56
57pub struct Context<M> {
58	self_ref: ActorRef<M>,
59	system: ActorSystem,
60	cancel: CancellationToken,
61}
62
63impl<M: Send + 'static> Context<M> {
64	pub fn new(self_ref: ActorRef<M>, system: ActorSystem, cancel: CancellationToken) -> Self {
65		Self {
66			self_ref,
67			system,
68			cancel,
69		}
70	}
71
72	pub fn self_ref(&self) -> ActorRef<M> {
73		self.self_ref.clone()
74	}
75
76	pub fn system(&self) -> &ActorSystem {
77		&self.system
78	}
79
80	pub fn is_cancelled(&self) -> bool {
81		self.cancel.is_cancelled()
82	}
83
84	pub fn cancellation_token(&self) -> CancellationToken {
85		self.cancel.clone()
86	}
87}
88
89impl<M: Send + 'static> Context<M> {
90	#[cfg(all(not(reifydb_single_threaded), not(loom)))]
91	pub fn schedule_once<F: FnOnce() -> M + Send + 'static>(
92		&self,
93		delay: impl Into<Duration>,
94		factory: F,
95	) -> TimerHandle {
96		let delay = delay.into().to_std();
97		let actor_ref = self.self_ref.clone();
98		self.system.scheduler().schedule_once(delay, move || {
99			let _ = actor_ref.send(factory());
100		})
101	}
102
103	#[cfg(all(loom, not(reifydb_single_threaded)))]
104	pub fn schedule_once<F: FnOnce() -> M + Send + 'static>(
105		&self,
106		_delay: impl Into<Duration>,
107		_factory: F,
108	) -> TimerHandle {
109		panic!("actors never run under the loom backend, so no timer can fire")
110	}
111
112	#[cfg(all(reifydb_single_threaded, not(reifydb_dst)))]
113	pub fn schedule_once<F: FnOnce() -> M + Send + 'static>(
114		&self,
115		delay: impl Into<Duration>,
116		factory: F,
117	) -> TimerHandle {
118		let delay = delay.into().to_std();
119		schedule_once_fn(self.self_ref.clone(), delay, factory)
120	}
121
122	#[cfg(reifydb_dst)]
123	pub fn schedule_once<F: FnOnce() -> M + Send + 'static>(
124		&self,
125		delay: impl Into<Duration>,
126		factory: F,
127	) -> TimerHandle {
128		let delay = delay.into().to_std();
129		dst_timers::schedule_once_fn(
130			self.system.timer_heap(),
131			self.system.mock_clock(),
132			self.self_ref.clone(),
133			delay,
134			factory,
135		)
136	}
137}
138
139impl<M: Send + Sync + Clone + 'static> Context<M> {
140	#[cfg(all(not(reifydb_single_threaded), not(loom)))]
141	pub fn schedule_repeat(&self, interval: impl Into<Duration>, msg: M) -> TimerHandle {
142		let interval = interval.into().to_std();
143		let actor_ref = self.self_ref.clone();
144		self.system
145			.scheduler()
146			.schedule_repeat(interval, move || Repeat::after_send(actor_ref.send(msg.clone())))
147	}
148
149	#[cfg(all(loom, not(reifydb_single_threaded)))]
150	pub fn schedule_repeat(&self, _interval: impl Into<Duration>, _msg: M) -> TimerHandle {
151		panic!("actors never run under the loom backend, so no timer can fire")
152	}
153
154	#[cfg(all(reifydb_single_threaded, not(reifydb_dst)))]
155	pub fn schedule_repeat(&self, interval: impl Into<Duration>, msg: M) -> TimerHandle {
156		let interval = interval.into().to_std();
157		schedule_repeat(self.self_ref.clone(), interval, msg)
158	}
159
160	#[cfg(reifydb_dst)]
161	pub fn schedule_repeat(&self, interval: impl Into<Duration>, msg: M) -> TimerHandle {
162		let interval = interval.into().to_std();
163		dst_timers::schedule_repeat(
164			self.system.timer_heap(),
165			self.system.mock_clock(),
166			self.self_ref.clone(),
167			interval,
168			msg,
169		)
170	}
171}
172
173impl<M: Send + 'static> Context<M> {
174	#[cfg(all(not(reifydb_single_threaded), not(loom)))]
175	pub fn schedule_repeat_fn<F: Fn() -> M + Send + Sync + 'static>(
176		&self,
177		interval: impl Into<Duration>,
178		factory: F,
179	) -> TimerHandle {
180		let interval = interval.into().to_std();
181		let actor_ref = self.self_ref.clone();
182		self.system.scheduler().schedule_repeat(interval, move || Repeat::after_send(actor_ref.send(factory())))
183	}
184
185	#[cfg(all(loom, not(reifydb_single_threaded)))]
186	pub fn schedule_repeat_fn<F: Fn() -> M + Send + Sync + 'static>(
187		&self,
188		_interval: impl Into<Duration>,
189		_factory: F,
190	) -> TimerHandle {
191		panic!("actors never run under the loom backend, so no timer can fire")
192	}
193
194	#[cfg(all(reifydb_single_threaded, not(reifydb_dst)))]
195	pub fn schedule_repeat_fn<F: Fn() -> M + Send + Sync + 'static>(
196		&self,
197		interval: impl Into<Duration>,
198		factory: F,
199	) -> TimerHandle {
200		let interval = interval.into().to_std();
201		schedule_repeat_fn(self.self_ref.clone(), interval, factory)
202	}
203
204	#[cfg(reifydb_dst)]
205	pub fn schedule_repeat_fn<F: Fn() -> M + Send + Sync + 'static>(
206		&self,
207		interval: impl Into<Duration>,
208		factory: F,
209	) -> TimerHandle {
210		let interval = interval.into().to_std();
211		dst_timers::schedule_repeat_fn(
212			self.system.timer_heap(),
213			self.system.mock_clock(),
214			self.self_ref.clone(),
215			interval,
216			factory,
217		)
218	}
219
220	#[cfg(not(all(loom, not(reifydb_single_threaded))))]
221	pub fn schedule_tick<F: Fn(i64) -> M + Send + Sync + 'static>(
222		&self,
223		interval: impl Into<Duration>,
224		factory: F,
225	) -> TimerHandle {
226		let interval = interval.into().to_std();
227		let actor_ref = self.self_ref.clone();
228		let clock = self.system.clock().clone();
229
230		#[cfg(not(reifydb_single_threaded))]
231		{
232			self.system.scheduler().schedule_repeat(interval, move || {
233				let now = clock.now().to_nanos();
234				Repeat::after_send(actor_ref.send(factory(now)))
235			})
236		}
237
238		#[cfg(all(reifydb_single_threaded, not(reifydb_dst)))]
239		{
240			schedule_repeat_fn(actor_ref, interval, move || {
241				let now = clock.now().to_nanos();
242				factory(now)
243			})
244		}
245
246		#[cfg(reifydb_dst)]
247		{
248			dst_timers::schedule_repeat_fn(
249				self.system.timer_heap(),
250				self.system.mock_clock(),
251				actor_ref,
252				interval,
253				move || {
254					let now = clock.now().to_nanos();
255					factory(now)
256				},
257			)
258		}
259	}
260
261	#[cfg(all(loom, not(reifydb_single_threaded)))]
262	pub fn schedule_tick<F: Fn(i64) -> M + Send + Sync + 'static>(
263		&self,
264		_interval: impl Into<Duration>,
265		_factory: F,
266	) -> TimerHandle {
267		panic!("actors never run under the loom backend, so no timer can fire")
268	}
269}
270
271impl<M> Clone for Context<M> {
272	fn clone(&self) -> Self {
273		Self {
274			self_ref: self.self_ref.clone(),
275			system: self.system.clone(),
276			cancel: self.cancel.clone(),
277		}
278	}
279}