1#![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<Result<(), String>>>>,
43 children: Mutex<Vec<ActorSystem>>,
44 parent: Weak<ActorSystemInner>,
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 parent: Weak::new(),
67 }),
68 }
69 }
70
71 pub fn testing(clock: Clock) -> Self {
72 TESTING_ROOT.get_or_init(|| Self::new(Pools::new(PoolConfig::default()), Clock::Real)).scope_with(clock)
73 }
74
75 pub fn scope(&self) -> Self {
76 self.scope_with(self.inner.clock.clone())
77 }
78
79 fn scope_with(&self, clock: Clock) -> Self {
80 let cancel = self.inner.cancel.child_token();
81 let child = Self {
82 inner: Arc::new(ActorSystemInner {
83 scheduler: self.inner.scheduler.shared(cancel.clone()),
84 cancel,
85 clock,
86 pools: self.inner.pools.clone(),
87 wakers: Mutex::new(Vec::new()),
88 keepalive: Mutex::new(Vec::new()),
89 done_rxs: Mutex::new(Vec::new()),
90 children: Mutex::new(Vec::new()),
91 parent: Arc::downgrade(&self.inner),
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 let children = mem::take(&mut *self.inner.children.lock());
121 for child in &children {
122 child.shutdown();
123 }
124 drop(children);
125
126 let wakers = mem::take(&mut *self.inner.wakers.lock());
127 for waker in &wakers {
128 waker();
129 }
130 drop(wakers);
131
132 self.inner.scheduler.shutdown();
133
134 self.inner.keepalive.lock().clear();
135
136 if let Some(parent) = self.inner.parent.upgrade() {
137 parent.children.lock().retain(|child| !Arc::ptr_eq(&child.inner, &self.inner));
138 }
139 }
140
141 pub(crate) fn register_waker(&self, f: Arc<dyn Fn() + Send + Sync>) {
142 self.inner.wakers.lock().push(f);
143 }
144
145 pub(crate) fn register_keepalive(&self, cell: Box<dyn Any + Send + Sync>) {
146 self.inner.keepalive.lock().push(cell);
147 }
148
149 pub(crate) fn register_done_rx(&self, rx: Receiver<Result<(), String>>) {
150 self.inner.done_rxs.lock().push(rx);
151 }
152
153 pub fn join(&self) -> Result<(), JoinError> {
154 self.join_timeout(Duration::from_secs(5))
155 }
156
157 #[allow(clippy::disallowed_methods)]
158 pub fn join_timeout(&self, timeout: Duration) -> Result<(), JoinError> {
159 let deadline = time::Instant::now() + timeout;
160 let rxs: Vec<_> = mem::take(&mut *self.inner.done_rxs.lock());
161 for rx in rxs {
162 let remaining = deadline.saturating_duration_since(time::Instant::now());
163 match rx.recv_timeout(remaining) {
164 Ok(Ok(())) => {}
165 Ok(Err(name)) => {
166 return Err(JoinError::new(format!("actor {name} panicked")));
167 }
168 Err(CcRecvTimeoutError::Disconnected) => {}
169 Err(CcRecvTimeoutError::Timeout) => {
170 return Err(JoinError::new("timed out waiting for actors to stop"));
171 }
172 }
173 }
174 Ok(())
175 }
176
177 pub fn scheduler(&self) -> &SchedulerHandle {
178 &self.inner.scheduler
179 }
180
181 pub fn clock(&self) -> &Clock {
182 &self.inner.clock
183 }
184
185 pub fn spawn_coordination<A: Actor>(&self, name: &str, actor: A) -> ActorHandle<A::Message>
186 where
187 A::State: Send,
188 {
189 let group = self.inner.pools.actor_pool().coordination();
190 pool::spawn_on_schedule(self, name, actor, Schedule::Pinned(group.assign()), group.batch_size())
191 }
192
193 pub fn spawn_flow<A: Actor>(&self, name: &str, actor: A) -> ActorHandle<A::Message>
194 where
195 A::State: Send,
196 {
197 let group = self.inner.pools.actor_pool().flow();
198 pool::spawn_on_schedule(self, name, actor, Schedule::Pinned(group.assign()), group.batch_size())
199 }
200
201 pub fn spawn_maintenance<A: Actor>(&self, name: &str, actor: A) -> ActorHandle<A::Message>
202 where
203 A::State: Send,
204 {
205 let group = self.inner.pools.actor_pool().maintenance();
206 pool::spawn_on_schedule(self, name, actor, Schedule::Pinned(group.assign()), group.batch_size())
207 }
208
209 pub fn spawn_ephemeral<A: Actor>(&self, name: &str, actor: A) -> ActorHandle<A::Message>
210 where
211 A::State: Send,
212 {
213 pool::spawn_on_schedule(self, name, actor, self.inner.pools.task_injector(), EPHEMERAL_BATCH_SIZE)
214 }
215}
216
217impl Debug for ActorSystem {
218 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
219 f.debug_struct("ActorSystem").field("cancelled", &self.is_cancelled()).finish_non_exhaustive()
220 }
221}
222
223#[derive(Clone)]
224pub struct ActorSpawner {
225 inner: Weak<ActorSystemInner>,
226 clock: Clock,
227}
228
229impl ActorSpawner {
230 fn system(&self) -> ActorSystem {
231 ActorSystem {
232 inner: self.inner.upgrade().expect("runtime already shut down: cannot spawn actor"),
233 }
234 }
235
236 pub fn clock(&self) -> &Clock {
237 &self.clock
238 }
239
240 pub fn pools(&self) -> Pools {
241 self.system().pools()
242 }
243
244 pub fn is_alive(&self) -> bool {
245 self.inner.strong_count() > 0
246 }
247
248 pub fn cancellation_token(&self) -> Option<CancellationToken> {
249 self.inner.upgrade().map(|inner| inner.cancel.clone())
250 }
251
252 pub fn scope(&self) -> ActorSpawner {
253 self.system().scope().spawner()
254 }
255
256 pub fn shutdown(&self) {
257 if let Some(inner) = self.inner.upgrade() {
258 ActorSystem {
259 inner,
260 }
261 .shutdown();
262 }
263 }
264
265 pub fn spawn_coordination<A: Actor>(&self, name: &str, actor: A) -> ActorHandle<A::Message>
266 where
267 A::State: Send,
268 {
269 self.system().spawn_coordination(name, actor)
270 }
271
272 pub fn spawn_flow<A: Actor>(&self, name: &str, actor: A) -> ActorHandle<A::Message>
273 where
274 A::State: Send,
275 {
276 self.system().spawn_flow(name, actor)
277 }
278
279 pub fn spawn_maintenance<A: Actor>(&self, name: &str, actor: A) -> ActorHandle<A::Message>
280 where
281 A::State: Send,
282 {
283 self.system().spawn_maintenance(name, actor)
284 }
285
286 pub fn spawn_ephemeral<A: Actor>(&self, name: &str, actor: A) -> ActorHandle<A::Message>
287 where
288 A::State: Send,
289 {
290 self.system().spawn_ephemeral(name, actor)
291 }
292}
293
294impl Debug for ActorSpawner {
295 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
296 f.debug_struct("ActorSpawner").field("alive", &self.is_alive()).finish_non_exhaustive()
297 }
298}
299
300pub type ActorHandle<M> = PoolActorHandle<M>;
301
302#[derive(Debug)]
303pub struct JoinError {
304 message: String,
305}
306
307impl JoinError {
308 pub fn new(message: impl Into<String>) -> Self {
309 Self {
310 message: message.into(),
311 }
312 }
313}
314
315impl fmt::Display for JoinError {
316 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
317 write!(f, "actor join failed: {}", self.message)
318 }
319}
320
321impl error::Error for JoinError {}
322
323#[cfg(test)]
324mod tests {
325 use std::sync;
326
327 use super::*;
328 use crate::{
329 actor::{context::Context, mailbox::ActorRef, system::ActorConfig, traits::Directive},
330 pool::{PoolConfig, Pools},
331 };
332
333 fn test_system() -> ActorSystem {
334 let pools = Pools::new(PoolConfig::default());
335 ActorSystem::new(pools, Clock::Real)
336 }
337
338 struct CounterActor;
339
340 #[derive(Debug)]
341 enum CounterMessage {
342 Inc,
343 Get(sync::mpsc::Sender<i64>),
344 Stop,
345 }
346
347 impl Actor for CounterActor {
348 type State = i64;
349 type Message = CounterMessage;
350
351 fn init(&self, _ctx: &Context<Self::Message>) -> Self::State {
352 0
353 }
354
355 fn handle(
356 &self,
357 state: &mut Self::State,
358 msg: Self::Message,
359 _ctx: &Context<Self::Message>,
360 ) -> Directive {
361 match msg {
362 CounterMessage::Inc => *state += 1,
363 CounterMessage::Get(tx) => {
364 let _ = tx.send(*state);
365 }
366 CounterMessage::Stop => return Directive::Stop,
367 }
368 Directive::Continue
369 }
370 }
371
372 #[test]
373 fn test_spawn_and_send() {
374 let system = test_system();
375 let handle = system.spawn_coordination("counter", CounterActor);
376
377 let actor_ref = handle.actor_ref().clone();
378 actor_ref.send(CounterMessage::Inc).unwrap();
379 actor_ref.send(CounterMessage::Inc).unwrap();
380 actor_ref.send(CounterMessage::Inc).unwrap();
381
382 let (tx, rx) = sync::mpsc::channel();
383 actor_ref.send(CounterMessage::Get(tx)).unwrap();
384
385 let value = rx.recv().unwrap();
386 assert_eq!(value, 3);
387
388 actor_ref.send(CounterMessage::Stop).unwrap();
389 handle.join().unwrap();
390 }
391
392 #[test]
393 fn test_shutdown_join() {
394 let system = test_system();
395
396 for i in 0..5 {
397 system.spawn_coordination(&format!("counter-{i}"), CounterActor);
398 }
399
400 system.shutdown();
402 system.join().unwrap();
403 }
404
405 #[test]
406 fn test_shutdown_stops_the_timer_scheduler() {
407 let system = test_system();
409 system.shutdown();
410
411 let (tx, rx) = sync::mpsc::channel();
412 system.scheduler().schedule_once(Duration::from_millis(5), move || {
413 let _ = tx.send(());
414 });
415
416 assert!(rx.recv_timeout(Duration::from_millis(200)).is_err());
417 }
418
419 #[test]
420 fn a_scope_that_shuts_down_leaves_its_parent_but_a_live_scope_still_stops_with_it() {
421 let root = test_system();
423 let stopped = root.scope();
424 let live = root.scope();
425 assert_eq!(root.inner.children.lock().len(), 2, "precondition: both scopes are registered");
426
427 stopped.shutdown();
428 let children = root.inner.children.lock().clone();
429 assert_eq!(children.len(), 1, "a stopped scope must leave its parent's list");
430 assert!(Arc::ptr_eq(&children[0].inner, &live.inner), "only the stopped scope may leave");
431 drop(children);
432
433 root.shutdown();
434 assert!(live.is_cancelled(), "a parent shutdown must still stop every live scope");
435 assert!(root.inner.children.lock().is_empty(), "the parent must hold no scope after its shutdown");
436 }
437 struct TurnActor {
438 log: Arc<sync::Mutex<Vec<&'static str>>>,
439 name: &'static str,
440 batch: Option<usize>,
441 }
442
443 #[derive(Debug)]
444 enum TurnMessage {
445 Hold(sync::mpsc::Sender<()>, sync::mpsc::Receiver<()>),
446 Kick(ActorRef<TurnMessage>),
447 Log,
448 Done(sync::mpsc::Sender<()>),
449 }
450
451 impl Actor for TurnActor {
452 type State = ();
453 type Message = TurnMessage;
454
455 fn init(&self, _ctx: &Context<Self::Message>) -> Self::State {}
456
457 fn handle(
458 &self,
459 _state: &mut Self::State,
460 msg: Self::Message,
461 _ctx: &Context<Self::Message>,
462 ) -> Directive {
463 match msg {
464 TurnMessage::Hold(started, release) => {
465 let _ = started.send(());
466 let _ = release.recv();
467 }
468 TurnMessage::Kick(other) => {
469 let _ = other.send(TurnMessage::Log);
470 }
471 TurnMessage::Log => self.log.lock().unwrap().push(self.name),
472 TurnMessage::Done(tx) => {
473 let _ = tx.send(());
474 }
475 }
476 Directive::Continue
477 }
478
479 fn config(&self) -> ActorConfig {
480 match self.batch {
481 Some(batch) => ActorConfig::new().batch_size(batch),
482 None => ActorConfig::new(),
483 }
484 }
485 }
486
487 fn turn_order(batch: Option<usize>) -> Vec<&'static str> {
488 let system = ActorSystem::new(
489 Pools::new(PoolConfig {
490 flow_threads: 1,
491 ..PoolConfig::default()
492 }),
493 Clock::Real,
494 );
495 let log = Arc::new(sync::Mutex::new(Vec::new()));
496 let actor = |name, batch| TurnActor {
497 log: Arc::clone(&log),
498 name,
499 batch,
500 };
501 let holder = system.spawn_flow("holder", actor("holder", None));
502 let busy = system.spawn_flow("busy", actor("busy", batch));
503 let other = system.spawn_flow("other", actor("other", None));
504
505 for handle in [&busy, &other] {
506 let (tx, rx) = sync::mpsc::channel();
507 handle.actor_ref().send(TurnMessage::Done(tx)).unwrap();
508 rx.recv().unwrap();
509 }
510
511 let (started_tx, started_rx) = sync::mpsc::channel();
512 let (release_tx, release_rx) = sync::mpsc::channel();
513 holder.actor_ref().send(TurnMessage::Hold(started_tx, release_rx)).unwrap();
514 started_rx.recv().unwrap();
515
516 busy.actor_ref().send(TurnMessage::Kick(other.actor_ref().clone())).unwrap();
517 for _ in 0..20 {
518 busy.actor_ref().send(TurnMessage::Log).unwrap();
519 }
520 let (busy_done_tx, busy_done_rx) = sync::mpsc::channel();
521 busy.actor_ref().send(TurnMessage::Done(busy_done_tx)).unwrap();
522 release_tx.send(()).unwrap();
523 busy_done_rx.recv().unwrap();
524
525 let (other_done_tx, other_done_rx) = sync::mpsc::channel();
526 other.actor_ref().send(TurnMessage::Done(other_done_tx)).unwrap();
527 other_done_rx.recv().unwrap();
528
529 system.shutdown();
530 system.join().unwrap();
531 log.lock().unwrap().clone()
532 }
533
534 #[test]
535 fn an_actor_batch_size_holds_its_turn_for_that_many_messages() {
536 let own = turn_order(Some(64));
538 assert_eq!(
539 own.iter().position(|name| *name == "other"),
540 Some(20),
541 "a batch of 64 must finish all 20 queued messages before a newly woken actor runs: {own:?}"
542 );
543
544 let pooled = turn_order(None);
545 assert_eq!(
546 pooled.iter().position(|name| *name == "other"),
547 Some(7),
548 "without its own batch size the actor must yield after the flow pool default of 8: {pooled:?}"
549 );
550 }
551
552 struct PanicActor;
553
554 impl Actor for PanicActor {
555 type State = ();
556 type Message = sync::mpsc::Sender<()>;
557
558 fn init(&self, _ctx: &Context<Self::Message>) -> Self::State {}
559
560 fn handle(
561 &self,
562 _state: &mut Self::State,
563 reached: Self::Message,
564 _ctx: &Context<Self::Message>,
565 ) -> Directive {
566 let _ = reached.send(());
567 panic!("a pool actor crashed while handling a message");
568 }
569 }
570
571 #[test]
572 fn a_pool_actor_that_panics_fails_the_join_instead_of_passing_shutdown_as_clean() {
573 let system = test_system();
576 let crashed = system.spawn_flow("crashed", PanicActor);
577 let (reached_tx, reached_rx) = sync::mpsc::channel();
578 crashed.actor_ref().send(reached_tx).unwrap();
579 reached_rx.recv().unwrap();
580
581 system.shutdown();
582
583 assert!(system.join().is_err(), "a crashed pool actor must fail the join");
584 }
585
586 #[test]
587 fn a_pool_actor_that_panics_fails_its_handle_join_after_shutdown() {
588 let system = test_system();
591 let crashed = system.spawn_flow("crashed", PanicActor);
592 let (reached_tx, reached_rx) = sync::mpsc::channel();
593 crashed.actor_ref().send(reached_tx).unwrap();
594 reached_rx.recv().unwrap();
595
596 system.shutdown();
597
598 assert!(crashed.join().is_err(), "a crashed pool actor must fail its handle join");
599 }
600}