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