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<()>>>,
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, mailbox::ActorRef, system::ActorConfig, 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 struct TurnActor {
410 log: Arc<sync::Mutex<Vec<&'static str>>>,
411 name: &'static str,
412 batch: Option<usize>,
413 }
414
415 #[derive(Debug)]
416 enum TurnMessage {
417 Hold(sync::mpsc::Sender<()>, sync::mpsc::Receiver<()>),
418 Kick(ActorRef<TurnMessage>),
419 Log,
420 Done(sync::mpsc::Sender<()>),
421 }
422
423 impl Actor for TurnActor {
424 type State = ();
425 type Message = TurnMessage;
426
427 fn init(&self, _ctx: &Context<Self::Message>) -> Self::State {}
428
429 fn handle(
430 &self,
431 _state: &mut Self::State,
432 msg: Self::Message,
433 _ctx: &Context<Self::Message>,
434 ) -> Directive {
435 match msg {
436 TurnMessage::Hold(started, release) => {
437 let _ = started.send(());
438 let _ = release.recv();
439 }
440 TurnMessage::Kick(other) => {
441 let _ = other.send(TurnMessage::Log);
442 }
443 TurnMessage::Log => self.log.lock().unwrap().push(self.name),
444 TurnMessage::Done(tx) => {
445 let _ = tx.send(());
446 }
447 }
448 Directive::Continue
449 }
450
451 fn config(&self) -> ActorConfig {
452 match self.batch {
453 Some(batch) => ActorConfig::new().batch_size(batch),
454 None => ActorConfig::new(),
455 }
456 }
457 }
458
459 fn turn_order(batch: Option<usize>) -> Vec<&'static str> {
460 let system = ActorSystem::new(
461 Pools::new(PoolConfig {
462 flow_threads: 1,
463 ..PoolConfig::default()
464 }),
465 Clock::Real,
466 );
467 let log = Arc::new(sync::Mutex::new(Vec::new()));
468 let actor = |name, batch| TurnActor {
469 log: Arc::clone(&log),
470 name,
471 batch,
472 };
473 let holder = system.spawn_flow("holder", actor("holder", None));
474 let busy = system.spawn_flow("busy", actor("busy", batch));
475 let other = system.spawn_flow("other", actor("other", None));
476
477 for handle in [&busy, &other] {
478 let (tx, rx) = sync::mpsc::channel();
479 handle.actor_ref().send(TurnMessage::Done(tx)).unwrap();
480 rx.recv().unwrap();
481 }
482
483 let (started_tx, started_rx) = sync::mpsc::channel();
484 let (release_tx, release_rx) = sync::mpsc::channel();
485 holder.actor_ref().send(TurnMessage::Hold(started_tx, release_rx)).unwrap();
486 started_rx.recv().unwrap();
487
488 busy.actor_ref().send(TurnMessage::Kick(other.actor_ref().clone())).unwrap();
489 for _ in 0..20 {
490 busy.actor_ref().send(TurnMessage::Log).unwrap();
491 }
492 let (busy_done_tx, busy_done_rx) = sync::mpsc::channel();
493 busy.actor_ref().send(TurnMessage::Done(busy_done_tx)).unwrap();
494 release_tx.send(()).unwrap();
495 busy_done_rx.recv().unwrap();
496
497 let (other_done_tx, other_done_rx) = sync::mpsc::channel();
498 other.actor_ref().send(TurnMessage::Done(other_done_tx)).unwrap();
499 other_done_rx.recv().unwrap();
500
501 system.shutdown();
502 system.join().unwrap();
503 let order = log.lock().unwrap().clone();
504 order
505 }
506
507 #[test]
508 fn an_actor_batch_size_holds_its_turn_for_that_many_messages() {
509 let own = turn_order(Some(64));
511 assert_eq!(
512 own.iter().position(|name| *name == "other"),
513 Some(20),
514 "a batch of 64 must finish all 20 queued messages before a newly woken actor runs: {own:?}"
515 );
516
517 let pooled = turn_order(None);
518 assert_eq!(
519 pooled.iter().position(|name| *name == "other"),
520 Some(7),
521 "without its own batch size the actor must yield after the flow pool default of 8: {pooled:?}"
522 );
523 }
524}