reifydb_runtime/actor/system/host/
mod.rs1#![allow(clippy::disallowed_types)]
5
6mod pool;
7
8use std::{
9 any::Any,
10 error, fmt,
11 fmt::{Debug, Formatter},
12 mem,
13 sync::{Arc, OnceLock, Weak},
14 time,
15 time::Duration,
16};
17
18use crossbeam_channel::{Receiver, RecvTimeoutError as CcRecvTimeoutError};
19
20use crate::{
21 actor::{
22 context::CancellationToken, system::host::pool::PoolActorHandle, timers::scheduler::SchedulerHandle,
23 traits::Actor,
24 },
25 context::clock::Clock,
26 pool::{
27 PoolConfig, Pools,
28 actor_pool::{EPHEMERAL_BATCH_SIZE, Schedule},
29 },
30 sync::mutex::Mutex,
31};
32
33static TESTING_ROOT: OnceLock<ActorSystem> = OnceLock::new();
34
35struct ActorSystemInner {
36 cancel: CancellationToken,
37 scheduler: SchedulerHandle,
38 clock: Clock,
39 pools: Pools,
40 wakers: Mutex<Vec<Arc<dyn Fn() + Send + Sync>>>,
41 keepalive: Mutex<Vec<Box<dyn Any + Send + Sync>>>,
42 done_rxs: Mutex<Vec<Receiver<()>>>,
43 children: Mutex<Vec<ActorSystem>>,
44}
45
46#[derive(Clone)]
47pub struct ActorSystem {
48 inner: Arc<ActorSystemInner>,
49}
50
51impl ActorSystem {
52 pub fn new(pools: Pools, clock: Clock) -> Self {
53 let scheduler = SchedulerHandle::new();
54
55 Self {
56 inner: Arc::new(ActorSystemInner {
57 cancel: CancellationToken::new(),
58 scheduler,
59 clock,
60 pools,
61 wakers: Mutex::new(Vec::new()),
62 keepalive: Mutex::new(Vec::new()),
63 done_rxs: Mutex::new(Vec::new()),
64 children: Mutex::new(Vec::new()),
65 }),
66 }
67 }
68
69 pub fn testing(clock: Clock) -> Self {
70 TESTING_ROOT.get_or_init(|| Self::new(Pools::new(PoolConfig::default()), Clock::Real)).scope_with(clock)
71 }
72
73 pub fn scope(&self) -> Self {
74 self.scope_with(self.inner.clock.clone())
75 }
76
77 fn scope_with(&self, clock: Clock) -> Self {
78 let child = Self {
79 inner: Arc::new(ActorSystemInner {
80 cancel: self.inner.cancel.child_token(),
81 scheduler: self.inner.scheduler.shared(),
82 clock,
83 pools: self.inner.pools.clone(),
84 wakers: Mutex::new(Vec::new()),
85 keepalive: Mutex::new(Vec::new()),
86 done_rxs: Mutex::new(Vec::new()),
87 children: Mutex::new(Vec::new()),
88 }),
89 };
90 self.inner.children.lock().push(child.clone());
91 child
92 }
93
94 pub fn pools(&self) -> Pools {
95 self.inner.pools.clone()
96 }
97
98 pub fn spawner(&self) -> ActorSpawner {
99 ActorSpawner {
100 inner: Arc::downgrade(&self.inner),
101 clock: self.inner.clock.clone(),
102 }
103 }
104
105 pub fn cancellation_token(&self) -> CancellationToken {
106 self.inner.cancel.clone()
107 }
108
109 pub fn is_cancelled(&self) -> bool {
110 self.inner.cancel.is_cancelled()
111 }
112
113 pub fn shutdown(&self) {
114 self.inner.cancel.cancel();
115
116 {
117 let mut children = self.inner.children.lock();
118 for child in children.iter() {
119 child.shutdown();
120 }
121 children.clear();
122 }
123
124 let wakers = mem::take(&mut *self.inner.wakers.lock());
125 for waker in &wakers {
126 waker();
127 }
128 drop(wakers);
129
130 self.inner.scheduler.shutdown();
131
132 self.inner.keepalive.lock().clear();
133 }
134
135 pub(crate) fn register_waker(&self, f: Arc<dyn Fn() + Send + Sync>) {
136 self.inner.wakers.lock().push(f);
137 }
138
139 pub(crate) fn register_keepalive(&self, cell: Box<dyn Any + Send + Sync>) {
140 self.inner.keepalive.lock().push(cell);
141 }
142
143 pub(crate) fn register_done_rx(&self, rx: Receiver<()>) {
144 self.inner.done_rxs.lock().push(rx);
145 }
146
147 pub fn join(&self) -> Result<(), JoinError> {
148 self.join_timeout(Duration::from_secs(5))
149 }
150
151 #[allow(clippy::disallowed_methods)]
152 pub fn join_timeout(&self, timeout: Duration) -> Result<(), JoinError> {
153 let deadline = time::Instant::now() + timeout;
154 let rxs: Vec<_> = mem::take(&mut *self.inner.done_rxs.lock());
155 for rx in rxs {
156 let remaining = deadline.saturating_duration_since(time::Instant::now());
157 match rx.recv_timeout(remaining) {
158 Ok(()) => {}
159 Err(CcRecvTimeoutError::Disconnected) => {}
160 Err(CcRecvTimeoutError::Timeout) => {
161 return Err(JoinError::new("timed out waiting for actors to stop"));
162 }
163 }
164 }
165 Ok(())
166 }
167
168 pub fn scheduler(&self) -> &SchedulerHandle {
169 &self.inner.scheduler
170 }
171
172 pub fn clock(&self) -> &Clock {
173 &self.inner.clock
174 }
175
176 pub fn spawn_coordination<A: Actor>(&self, name: &str, actor: A) -> ActorHandle<A::Message>
177 where
178 A::State: Send,
179 {
180 let group = self.inner.pools.actor_pool().coordination();
181 pool::spawn_on_schedule(self, name, actor, Schedule::Pinned(group.assign()), group.batch_size())
182 }
183
184 pub fn spawn_flow<A: Actor>(&self, name: &str, actor: A) -> ActorHandle<A::Message>
185 where
186 A::State: Send,
187 {
188 let group = self.inner.pools.actor_pool().flow();
189 pool::spawn_on_schedule(self, name, actor, Schedule::Pinned(group.assign()), group.batch_size())
190 }
191
192 pub fn spawn_maintenance<A: Actor>(&self, name: &str, actor: A) -> ActorHandle<A::Message>
193 where
194 A::State: Send,
195 {
196 let group = self.inner.pools.actor_pool().maintenance();
197 pool::spawn_on_schedule(self, name, actor, Schedule::Pinned(group.assign()), group.batch_size())
198 }
199
200 pub fn spawn_ephemeral<A: Actor>(&self, name: &str, actor: A) -> ActorHandle<A::Message>
201 where
202 A::State: Send,
203 {
204 pool::spawn_on_schedule(self, name, actor, self.inner.pools.task_injector(), EPHEMERAL_BATCH_SIZE)
205 }
206}
207
208impl Debug for ActorSystem {
209 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
210 f.debug_struct("ActorSystem").field("cancelled", &self.is_cancelled()).finish_non_exhaustive()
211 }
212}
213
214#[derive(Clone)]
215pub struct ActorSpawner {
216 inner: Weak<ActorSystemInner>,
217 clock: Clock,
218}
219
220impl ActorSpawner {
221 fn system(&self) -> ActorSystem {
222 ActorSystem {
223 inner: self.inner.upgrade().expect("runtime already shut down: cannot spawn actor"),
224 }
225 }
226
227 pub fn clock(&self) -> &Clock {
228 &self.clock
229 }
230
231 pub fn pools(&self) -> Pools {
232 self.system().pools()
233 }
234
235 pub fn is_alive(&self) -> bool {
236 self.inner.strong_count() > 0
237 }
238
239 pub fn cancellation_token(&self) -> Option<CancellationToken> {
240 self.inner.upgrade().map(|inner| inner.cancel.clone())
241 }
242
243 pub fn scope(&self) -> ActorSpawner {
244 self.system().scope().spawner()
245 }
246
247 pub fn shutdown(&self) {
248 if let Some(inner) = self.inner.upgrade() {
249 ActorSystem {
250 inner,
251 }
252 .shutdown();
253 }
254 }
255
256 pub fn spawn_coordination<A: Actor>(&self, name: &str, actor: A) -> ActorHandle<A::Message>
257 where
258 A::State: Send,
259 {
260 self.system().spawn_coordination(name, actor)
261 }
262
263 pub fn spawn_flow<A: Actor>(&self, name: &str, actor: A) -> ActorHandle<A::Message>
264 where
265 A::State: Send,
266 {
267 self.system().spawn_flow(name, actor)
268 }
269
270 pub fn spawn_maintenance<A: Actor>(&self, name: &str, actor: A) -> ActorHandle<A::Message>
271 where
272 A::State: Send,
273 {
274 self.system().spawn_maintenance(name, actor)
275 }
276
277 pub fn spawn_ephemeral<A: Actor>(&self, name: &str, actor: A) -> ActorHandle<A::Message>
278 where
279 A::State: Send,
280 {
281 self.system().spawn_ephemeral(name, actor)
282 }
283}
284
285impl Debug for ActorSpawner {
286 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
287 f.debug_struct("ActorSpawner").field("alive", &self.is_alive()).finish_non_exhaustive()
288 }
289}
290
291pub type ActorHandle<M> = PoolActorHandle<M>;
292
293#[derive(Debug)]
294pub struct JoinError {
295 message: String,
296}
297
298impl JoinError {
299 pub fn new(message: impl Into<String>) -> Self {
300 Self {
301 message: message.into(),
302 }
303 }
304}
305
306impl fmt::Display for JoinError {
307 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
308 write!(f, "actor join failed: {}", self.message)
309 }
310}
311
312impl error::Error for JoinError {}
313
314#[cfg(test)]
315mod tests {
316 use std::sync;
317
318 use super::*;
319 use crate::{
320 actor::{context::Context, traits::Directive},
321 pool::{PoolConfig, Pools},
322 };
323
324 fn test_system() -> ActorSystem {
325 let pools = Pools::new(PoolConfig::default());
326 ActorSystem::new(pools, Clock::Real)
327 }
328
329 struct CounterActor;
330
331 #[derive(Debug)]
332 enum CounterMessage {
333 Inc,
334 Get(sync::mpsc::Sender<i64>),
335 Stop,
336 }
337
338 impl Actor for CounterActor {
339 type State = i64;
340 type Message = CounterMessage;
341
342 fn init(&self, _ctx: &Context<Self::Message>) -> Self::State {
343 0
344 }
345
346 fn handle(
347 &self,
348 state: &mut Self::State,
349 msg: Self::Message,
350 _ctx: &Context<Self::Message>,
351 ) -> Directive {
352 match msg {
353 CounterMessage::Inc => *state += 1,
354 CounterMessage::Get(tx) => {
355 let _ = tx.send(*state);
356 }
357 CounterMessage::Stop => return Directive::Stop,
358 }
359 Directive::Continue
360 }
361 }
362
363 #[test]
364 fn test_spawn_and_send() {
365 let system = test_system();
366 let handle = system.spawn_coordination("counter", CounterActor);
367
368 let actor_ref = handle.actor_ref().clone();
369 actor_ref.send(CounterMessage::Inc).unwrap();
370 actor_ref.send(CounterMessage::Inc).unwrap();
371 actor_ref.send(CounterMessage::Inc).unwrap();
372
373 let (tx, rx) = sync::mpsc::channel();
374 actor_ref.send(CounterMessage::Get(tx)).unwrap();
375
376 let value = rx.recv().unwrap();
377 assert_eq!(value, 3);
378
379 actor_ref.send(CounterMessage::Stop).unwrap();
380 handle.join().unwrap();
381 }
382
383 #[test]
384 fn test_shutdown_join() {
385 let system = test_system();
386
387 for i in 0..5 {
388 system.spawn_coordination(&format!("counter-{i}"), CounterActor);
389 }
390
391 system.shutdown();
393 system.join().unwrap();
394 }
395
396 #[test]
397 fn test_shutdown_stops_the_timer_scheduler() {
398 let system = test_system();
400 system.shutdown();
401
402 let (tx, rx) = sync::mpsc::channel();
403 system.scheduler().schedule_once(Duration::from_millis(5), move || {
404 let _ = tx.send(());
405 });
406
407 assert!(rx.recv_timeout(Duration::from_millis(200)).is_err());
408 }
409}