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