/home/noah/src/ruchy/src/runtime/actor.rs
Line | Count | Source |
1 | | #![allow(clippy::print_stdout, clippy::print_stderr)] |
2 | | //! Actor system runtime with supervision trees |
3 | | //! |
4 | | //! This module implements a robust actor system inspired by Erlang/OTP and Akka, |
5 | | //! with supervision trees for fault tolerance and message passing capabilities. |
6 | | |
7 | | use anyhow::{anyhow, Result}; |
8 | | use serde::{Deserialize, Serialize}; |
9 | | use std::collections::HashMap; |
10 | | use std::sync::{mpsc, Arc, Mutex}; |
11 | | use std::thread::{self, JoinHandle}; |
12 | | use std::time::Duration; |
13 | | |
14 | | /// Unique identifier for actors |
15 | | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] |
16 | | pub struct ActorId(pub u64); |
17 | | |
18 | | impl std::fmt::Display for ActorId { |
19 | 0 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
20 | 0 | write!(f, "actor-{}", self.0) |
21 | 0 | } |
22 | | } |
23 | | |
24 | | /// Actor reference for sending messages |
25 | | #[derive(Debug, Clone)] |
26 | | pub struct ActorRef { |
27 | | pub id: ActorId, |
28 | | pub name: String, |
29 | | sender: mpsc::Sender<ActorMessage>, |
30 | | } |
31 | | |
32 | | impl ActorRef { |
33 | | /// Send a message to this actor (fire-and-forget) |
34 | | /// |
35 | | /// # Errors |
36 | | /// |
37 | | /// Returns an error if the actor is no longer running |
38 | | /// # Errors |
39 | | /// |
40 | | /// Returns an error if the operation fails |
41 | 0 | pub fn send(&self, message: Message) -> Result<()> { |
42 | 0 | self.sender |
43 | 0 | .send(ActorMessage::UserMessage(message)) |
44 | 0 | .map_err(|_| anyhow!("Actor {} is no longer running", self.id))?; |
45 | 0 | Ok(()) |
46 | 0 | } |
47 | | |
48 | | /// Ask a message to this actor and wait for a response |
49 | | /// |
50 | | /// # Errors |
51 | | /// |
52 | | /// Returns an error if: |
53 | | /// - The actor is no longer running |
54 | | /// - The timeout expires before receiving a response |
55 | | /// # Errors |
56 | | /// |
57 | | /// Returns an error if the operation fails |
58 | 0 | pub fn ask(&self, message: Message, timeout: Duration) -> Result<Message> { |
59 | 0 | let (response_tx, response_rx) = mpsc::channel(); |
60 | | |
61 | 0 | self.sender |
62 | 0 | .send(ActorMessage::AskMessage { |
63 | 0 | message, |
64 | 0 | response: response_tx, |
65 | 0 | }) |
66 | 0 | .map_err(|_| anyhow!("Actor {} is no longer running", self.id))?; |
67 | | |
68 | 0 | response_rx |
69 | 0 | .recv_timeout(timeout) |
70 | 0 | .map_err(|_| anyhow!("Timeout waiting for response from {}", self.id)) |
71 | 0 | } |
72 | | } |
73 | | |
74 | | /// Message that can be sent between actors |
75 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
76 | | pub enum Message { |
77 | | /// System messages for actor lifecycle |
78 | | Start, |
79 | | Stop, |
80 | | Restart, |
81 | | /// User-defined messages |
82 | | User(String, Vec<MessageValue>), |
83 | | /// Error notification |
84 | | Error(String), |
85 | | /// Supervision messages |
86 | | ChildFailed(ActorId, String), |
87 | | ChildRestarted(ActorId), |
88 | | } |
89 | | |
90 | | /// Values that can be passed in messages |
91 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
92 | | pub enum MessageValue { |
93 | | String(String), |
94 | | Integer(i64), |
95 | | Float(f64), |
96 | | Bool(bool), |
97 | | List(Vec<MessageValue>), |
98 | | Map(HashMap<String, MessageValue>), |
99 | | ActorRef(ActorId), |
100 | | } |
101 | | |
102 | | /// Internal actor message envelope |
103 | | #[derive(Debug)] |
104 | | enum ActorMessage { |
105 | | UserMessage(Message), |
106 | | AskMessage { |
107 | | message: Message, |
108 | | response: mpsc::Sender<Message>, |
109 | | }, |
110 | | SystemShutdown, |
111 | | } |
112 | | |
113 | | /// Actor behavior trait |
114 | | pub trait ActorBehavior: Send + 'static { |
115 | | /// Called when the actor starts |
116 | | /// |
117 | | /// # Errors |
118 | | /// |
119 | | /// Returns an error if initialization fails |
120 | 0 | fn pre_start(&mut self, _ctx: &mut ActorContext) -> Result<()> { |
121 | 0 | Ok(()) |
122 | 0 | } |
123 | | |
124 | | /// Called when the actor stops |
125 | | /// |
126 | | /// # Errors |
127 | | /// |
128 | | /// Returns an error if cleanup fails |
129 | 0 | fn post_stop(&mut self, _ctx: &mut ActorContext) -> Result<()> { |
130 | 0 | Ok(()) |
131 | 0 | } |
132 | | |
133 | | /// Called when the actor is about to restart |
134 | | /// |
135 | | /// # Errors |
136 | | /// |
137 | | /// Returns an error if pre-restart logic fails |
138 | 0 | fn pre_restart(&mut self, _ctx: &mut ActorContext, _reason: &str) -> Result<()> { |
139 | 0 | Ok(()) |
140 | 0 | } |
141 | | |
142 | | /// Called after the actor has restarted |
143 | | /// |
144 | | /// # Errors |
145 | | /// |
146 | | /// Returns an error if post-restart logic fails |
147 | 0 | fn post_restart(&mut self, _ctx: &mut ActorContext, _reason: &str) -> Result<()> { |
148 | 0 | Ok(()) |
149 | 0 | } |
150 | | |
151 | | /// Handle incoming messages |
152 | | /// |
153 | | /// # Errors |
154 | | /// |
155 | | /// Returns an error if message processing fails |
156 | | fn receive(&mut self, message: Message, ctx: &mut ActorContext) -> Result<Option<Message>>; |
157 | | |
158 | | /// Handle actor supervision - called when a child actor fails |
159 | 0 | fn supervisor_strategy(&mut self, _child: ActorId, _reason: &str) -> SupervisorDirective { |
160 | 0 | SupervisorDirective::Restart |
161 | 0 | } |
162 | | } |
163 | | |
164 | | /// Supervisor strategy for handling child actor failures |
165 | | #[derive(Debug, Clone)] |
166 | | pub enum SupervisorDirective { |
167 | | /// Restart the failed child |
168 | | Restart, |
169 | | /// Stop the failed child |
170 | | Stop, |
171 | | /// Escalate the failure to the parent supervisor |
172 | | Escalate, |
173 | | /// Resume the child (ignore the failure) |
174 | | Resume, |
175 | | } |
176 | | |
177 | | /// Actor context provided during message handling |
178 | | pub struct ActorContext { |
179 | | pub actor_id: ActorId, |
180 | | pub actor_name: String, |
181 | | pub supervisor: Option<ActorRef>, |
182 | | pub children: HashMap<ActorId, ActorRef>, |
183 | | system: Arc<Mutex<ActorSystem>>, |
184 | | } |
185 | | |
186 | | impl ActorContext { |
187 | | /// Spawn a child actor under this actor's supervision |
188 | | /// # Errors |
189 | | /// |
190 | | /// Returns an error if the operation fails |
191 | 0 | pub fn spawn_child<B: ActorBehavior>(&mut self, name: String, behavior: B) -> Result<ActorRef> { |
192 | 0 | let mut system = self |
193 | 0 | .system |
194 | 0 | .lock() |
195 | 0 | .map_err(|_| anyhow!("Actor system mutex poisoned"))?; |
196 | 0 | let actor_ref = system.spawn_supervised(name, Box::new(behavior), Some(self.actor_id))?; |
197 | 0 | self.children.insert(actor_ref.id, actor_ref.clone()); |
198 | 0 | Ok(actor_ref) |
199 | 0 | } |
200 | | |
201 | | /// Stop a child actor |
202 | | /// # Errors |
203 | | /// |
204 | | /// Returns an error if the operation fails |
205 | 0 | pub fn stop_child(&mut self, child_id: ActorId) -> Result<()> { |
206 | 0 | if let Some(child_ref) = self.children.remove(&child_id) { |
207 | 0 | child_ref.send(Message::Stop)?; |
208 | 0 | } |
209 | 0 | Ok(()) |
210 | 0 | } |
211 | | |
212 | | /// Get reference to self |
213 | | /// |
214 | | /// # Errors |
215 | | /// |
216 | | /// Returns an error if the actor reference cannot be retrieved |
217 | | /// # Errors |
218 | | /// |
219 | | /// Returns an error if the operation fails |
220 | 0 | pub fn get_self(&self) -> Result<ActorRef> { |
221 | 0 | let system = self |
222 | 0 | .system |
223 | 0 | .lock() |
224 | 0 | .map_err(|_| anyhow!("Actor system mutex poisoned"))?; |
225 | 0 | system |
226 | 0 | .get_actor_ref(self.actor_id) |
227 | 0 | .ok_or_else(|| anyhow!("Actor not found")) |
228 | 0 | } |
229 | | |
230 | | /// Find actor by name |
231 | 0 | pub fn find_actor(&self, name: &str) -> Option<ActorRef> { |
232 | 0 | let system = self.system.lock().ok()?; |
233 | 0 | system.find_actor_by_name(name) |
234 | 0 | } |
235 | | } |
236 | | |
237 | | /// Actor runtime information |
238 | | struct ActorRuntime { |
239 | | id: ActorId, |
240 | | name: String, |
241 | | behavior: Box<dyn ActorBehavior>, |
242 | | receiver: mpsc::Receiver<ActorMessage>, |
243 | | sender: mpsc::Sender<ActorMessage>, |
244 | | supervisor: Option<ActorId>, |
245 | | children: HashMap<ActorId, ActorRef>, |
246 | | system: Arc<Mutex<ActorSystem>>, |
247 | | handle: Option<JoinHandle<()>>, |
248 | | } |
249 | | |
250 | | impl ActorRuntime { |
251 | 0 | fn new( |
252 | 0 | id: ActorId, |
253 | 0 | name: String, |
254 | 0 | behavior: Box<dyn ActorBehavior>, |
255 | 0 | supervisor: Option<ActorId>, |
256 | 0 | system: Arc<Mutex<ActorSystem>>, |
257 | 0 | ) -> Self { |
258 | 0 | let (sender, receiver) = mpsc::channel(); |
259 | | |
260 | 0 | Self { |
261 | 0 | id, |
262 | 0 | name, |
263 | 0 | behavior, |
264 | 0 | receiver, |
265 | 0 | sender, |
266 | 0 | supervisor, |
267 | 0 | children: HashMap::new(), |
268 | 0 | system, |
269 | 0 | handle: None, |
270 | 0 | } |
271 | 0 | } |
272 | | |
273 | 0 | fn start(&mut self) -> ActorRef { |
274 | 0 | let actor_ref = ActorRef { |
275 | 0 | id: self.id, |
276 | 0 | name: self.name.clone(), |
277 | 0 | sender: self.sender.clone(), |
278 | 0 | }; |
279 | | |
280 | 0 | let id = self.id; |
281 | 0 | let name = self.name.clone(); |
282 | 0 | let receiver = std::mem::replace(&mut self.receiver, mpsc::channel().1); |
283 | 0 | let mut behavior = std::mem::replace(&mut self.behavior, Box::new(DummyBehavior)); |
284 | 0 | let supervisor = self.supervisor; |
285 | 0 | let system = self.system.clone(); |
286 | 0 | let children = self.children.clone(); |
287 | | |
288 | 0 | let handle = thread::spawn(move || { |
289 | 0 | let mut ctx = ActorContext { |
290 | 0 | actor_id: id, |
291 | 0 | actor_name: name.clone(), |
292 | 0 | supervisor: supervisor.and_then(|sup_id| system.lock().ok()?.get_actor_ref(sup_id)), |
293 | 0 | children, |
294 | 0 | system: system.clone(), |
295 | | }; |
296 | | |
297 | | // Initialize actor |
298 | 0 | if let Err(e) = behavior.pre_start(&mut ctx) { |
299 | 0 | eprintln!("Actor {name} failed to start: {e}"); |
300 | 0 | return; |
301 | 0 | } |
302 | | |
303 | | // Main message loop |
304 | | loop { |
305 | 0 | match receiver.recv() { |
306 | 0 | Ok(ActorMessage::UserMessage(msg)) => { |
307 | 0 | match behavior.receive(msg, &mut ctx) { |
308 | 0 | Ok(_) => {} |
309 | 0 | Err(e) => { |
310 | 0 | eprintln!("Actor {name} error handling message: {e}"); |
311 | | // Notify supervisor of failure |
312 | 0 | if let Some(sup) = &ctx.supervisor { |
313 | 0 | let _ = sup.send(Message::ChildFailed(id, e.to_string())); |
314 | 0 | } |
315 | | } |
316 | | } |
317 | | } |
318 | 0 | Ok(ActorMessage::AskMessage { message, response }) => { |
319 | 0 | match behavior.receive(message, &mut ctx) { |
320 | 0 | Ok(Some(reply)) => { |
321 | 0 | let _ = response.send(reply); |
322 | 0 | } |
323 | 0 | Ok(None) => { |
324 | 0 | let _ = response.send(Message::Error("No response".to_string())); |
325 | 0 | } |
326 | 0 | Err(e) => { |
327 | 0 | let _ = response.send(Message::Error(e.to_string())); |
328 | | // Notify supervisor of failure |
329 | 0 | if let Some(sup) = &ctx.supervisor { |
330 | 0 | let _ = sup.send(Message::ChildFailed(id, e.to_string())); |
331 | 0 | } |
332 | | } |
333 | | } |
334 | | } |
335 | | Ok(ActorMessage::SystemShutdown) => { |
336 | 0 | break; |
337 | | } |
338 | | Err(_) => { |
339 | | // Channel closed, exit |
340 | 0 | break; |
341 | | } |
342 | | } |
343 | | } |
344 | | |
345 | | // Cleanup |
346 | 0 | let _ = behavior.post_stop(&mut ctx); |
347 | 0 | }); |
348 | | |
349 | 0 | self.handle = Some(handle); |
350 | 0 | actor_ref |
351 | 0 | } |
352 | | |
353 | 0 | fn stop(&mut self) { |
354 | 0 | if let Some(handle) = self.handle.take() { |
355 | 0 | let _ = self.sender.send(ActorMessage::SystemShutdown); |
356 | 0 | let _ = handle.join(); |
357 | 0 | } |
358 | 0 | } |
359 | | } |
360 | | |
361 | | /// Dummy behavior for placeholder |
362 | | struct DummyBehavior; |
363 | | |
364 | | impl ActorBehavior for DummyBehavior { |
365 | 0 | fn receive(&mut self, _message: Message, _ctx: &mut ActorContext) -> Result<Option<Message>> { |
366 | 0 | Ok(None) |
367 | 0 | } |
368 | | } |
369 | | |
370 | | /// Actor system managing all actors and supervision |
371 | | pub struct ActorSystem { |
372 | | actors: HashMap<ActorId, ActorRuntime>, |
373 | | actor_names: HashMap<String, ActorId>, |
374 | | next_id: u64, |
375 | | } |
376 | | |
377 | | impl ActorSystem { |
378 | | /// Create a new actor system |
379 | 0 | pub fn new() -> Arc<Mutex<Self>> { |
380 | 0 | Arc::new(Mutex::new(Self { |
381 | 0 | actors: HashMap::new(), |
382 | 0 | actor_names: HashMap::new(), |
383 | 0 | next_id: 1, |
384 | 0 | })) |
385 | 0 | } |
386 | | |
387 | | /// Spawn a new actor in the system |
388 | | /// |
389 | | /// # Errors |
390 | | /// |
391 | | /// Returns an error if an actor with the same name already exists |
392 | | /// # Errors |
393 | | /// |
394 | | /// Returns an error if the operation fails |
395 | 0 | pub fn spawn<B: ActorBehavior>(&mut self, name: String, behavior: B) -> Result<ActorRef> { |
396 | 0 | self.spawn_supervised(name, Box::new(behavior), None) |
397 | 0 | } |
398 | | |
399 | | /// Spawn a supervised actor |
400 | | /// |
401 | | /// # Errors |
402 | | /// |
403 | | /// Returns an error if: |
404 | | /// - An actor with the same name already exists |
405 | | /// - The supervisor doesn't exist (if specified) |
406 | 0 | pub fn spawn_supervised( |
407 | 0 | &mut self, |
408 | 0 | name: String, |
409 | 0 | behavior: Box<dyn ActorBehavior>, |
410 | 0 | supervisor: Option<ActorId>, |
411 | 0 | ) -> Result<ActorRef> { |
412 | 0 | if self.actor_names.contains_key(&name) { |
413 | 0 | return Err(anyhow!("Actor with name '{}' already exists", name)); |
414 | 0 | } |
415 | | |
416 | 0 | let id = ActorId(self.next_id); |
417 | 0 | self.next_id += 1; |
418 | | |
419 | 0 | let system_arc = Arc::new(Mutex::new(ActorSystem { |
420 | 0 | actors: HashMap::new(), |
421 | 0 | actor_names: HashMap::new(), |
422 | 0 | next_id: self.next_id, |
423 | 0 | })); |
424 | | |
425 | 0 | let mut runtime = ActorRuntime::new(id, name.clone(), behavior, supervisor, system_arc); |
426 | 0 | let actor_ref = runtime.start(); |
427 | | |
428 | 0 | self.actors.insert(id, runtime); |
429 | 0 | self.actor_names.insert(name, id); |
430 | | |
431 | 0 | Ok(actor_ref) |
432 | 0 | } |
433 | | |
434 | | /// Get actor reference by ID |
435 | 0 | pub fn get_actor_ref(&self, id: ActorId) -> Option<ActorRef> { |
436 | 0 | self.actors.get(&id).map(|runtime| ActorRef { |
437 | 0 | id: runtime.id, |
438 | 0 | name: runtime.name.clone(), |
439 | 0 | sender: runtime.sender.clone(), |
440 | 0 | }) |
441 | 0 | } |
442 | | |
443 | | /// Find actor by name |
444 | 0 | pub fn find_actor_by_name(&self, name: &str) -> Option<ActorRef> { |
445 | 0 | self.actor_names |
446 | 0 | .get(name) |
447 | 0 | .and_then(|&id| self.get_actor_ref(id)) |
448 | 0 | } |
449 | | |
450 | | /// Stop an actor |
451 | | /// # Errors |
452 | | /// |
453 | | /// Returns an error if the operation fails |
454 | 0 | pub fn stop_actor(&mut self, id: ActorId) -> Result<()> { |
455 | 0 | if let Some(mut runtime) = self.actors.remove(&id) { |
456 | 0 | self.actor_names.retain(|_, &mut v| v != id); |
457 | 0 | runtime.stop(); |
458 | 0 | } |
459 | 0 | Ok(()) |
460 | 0 | } |
461 | | |
462 | | /// Shutdown the entire actor system |
463 | 0 | pub fn shutdown(&mut self) { |
464 | 0 | let actor_ids: Vec<ActorId> = self.actors.keys().copied().collect(); |
465 | 0 | for id in actor_ids { |
466 | 0 | let _ = self.stop_actor(id); |
467 | 0 | } |
468 | 0 | } |
469 | | } |
470 | | |
471 | | impl Default for ActorSystem { |
472 | 0 | fn default() -> Self { |
473 | 0 | Self { |
474 | 0 | actors: HashMap::new(), |
475 | 0 | actor_names: HashMap::new(), |
476 | 0 | next_id: 1, |
477 | 0 | } |
478 | 0 | } |
479 | | } |
480 | | |
481 | | impl Clone for ActorSystem { |
482 | 0 | fn clone(&self) -> Self { |
483 | 0 | Self { |
484 | 0 | actors: HashMap::new(), |
485 | 0 | actor_names: self.actor_names.clone(), |
486 | 0 | next_id: self.next_id, |
487 | 0 | } |
488 | 0 | } |
489 | | } |
490 | | |
491 | | /// Example echo actor behavior |
492 | | pub struct EchoActor; |
493 | | |
494 | | impl ActorBehavior for EchoActor { |
495 | 0 | fn receive(&mut self, message: Message, _ctx: &mut ActorContext) -> Result<Option<Message>> { |
496 | 0 | match message { |
497 | 0 | Message::User(msg_type, values) => { |
498 | 0 | println!("Echo: {msg_type} with values: {values:?}"); |
499 | 0 | Ok(Some(Message::User(format!("Echo: {msg_type}"), values))) |
500 | | } |
501 | 0 | _ => Ok(None), |
502 | | } |
503 | 0 | } |
504 | | } |
505 | | |
506 | | /// Example supervisor actor that manages child actors |
507 | | pub struct SupervisorActor { |
508 | | restart_count: HashMap<ActorId, u32>, |
509 | | max_restarts: u32, |
510 | | } |
511 | | |
512 | | impl SupervisorActor { |
513 | | #[must_use] |
514 | 0 | pub fn new(max_restarts: u32) -> Self { |
515 | 0 | Self { |
516 | 0 | restart_count: HashMap::new(), |
517 | 0 | max_restarts, |
518 | 0 | } |
519 | 0 | } |
520 | | } |
521 | | |
522 | | impl ActorBehavior for SupervisorActor { |
523 | 0 | fn receive(&mut self, message: Message, ctx: &mut ActorContext) -> Result<Option<Message>> { |
524 | 0 | match message { |
525 | 0 | Message::ChildFailed(child_id, reason) => { |
526 | 0 | let count = self.restart_count.entry(child_id).or_insert(0); |
527 | 0 | *count += 1; |
528 | | |
529 | 0 | if *count <= self.max_restarts { |
530 | 0 | println!("Supervisor restarting child {child_id} (attempt {count}): {reason}"); |
531 | | // In a real implementation, we would restart the child here |
532 | 0 | Ok(Some(Message::ChildRestarted(child_id))) |
533 | | } else { |
534 | 0 | println!("Supervisor stopping child {child_id} after {count} failures"); |
535 | 0 | ctx.stop_child(child_id)?; |
536 | 0 | Ok(None) |
537 | | } |
538 | | } |
539 | 0 | _ => Ok(None), |
540 | | } |
541 | 0 | } |
542 | | |
543 | 0 | fn supervisor_strategy(&mut self, child: ActorId, _reason: &str) -> SupervisorDirective { |
544 | 0 | let count = self.restart_count.get(&child).unwrap_or(&0); |
545 | 0 | if *count < self.max_restarts { |
546 | 0 | SupervisorDirective::Restart |
547 | | } else { |
548 | 0 | SupervisorDirective::Stop |
549 | | } |
550 | 0 | } |
551 | | } |
552 | | |
553 | | #[cfg(test)] |
554 | | #[allow(clippy::unwrap_used)] |
555 | | #[allow(clippy::panic)] |
556 | | mod tests { |
557 | | use super::*; |
558 | | use std::time::Duration; |
559 | | |
560 | | #[test] |
561 | | fn test_actor_system_creation() { |
562 | | let system = ActorSystem::new(); |
563 | | assert!(system.lock().unwrap().actors.is_empty()); |
564 | | } |
565 | | |
566 | | #[test] |
567 | | fn test_echo_actor() { |
568 | | let system = ActorSystem::new(); |
569 | | let actor_ref = { |
570 | | let mut sys = system.lock().unwrap(); |
571 | | sys.spawn("echo".to_string(), EchoActor).unwrap() |
572 | | }; |
573 | | |
574 | | let message = Message::User( |
575 | | "test".to_string(), |
576 | | vec![MessageValue::String("hello".to_string())], |
577 | | ); |
578 | | |
579 | | let response = actor_ref.ask(message, Duration::from_millis(100)).unwrap(); |
580 | | match response { |
581 | | Message::User(msg, _) => assert!(msg.contains("Echo: test")), |
582 | | _ => panic!("Unexpected response type"), |
583 | | } |
584 | | } |
585 | | |
586 | | #[test] |
587 | | fn test_supervisor_actor() { |
588 | | let system = ActorSystem::new(); |
589 | | let supervisor_ref = { |
590 | | let mut sys = system.lock().unwrap(); |
591 | | sys.spawn("supervisor".to_string(), SupervisorActor::new(3)) |
592 | | .unwrap() |
593 | | }; |
594 | | |
595 | | let child_id = ActorId(999); |
596 | | let failure_message = Message::ChildFailed(child_id, "Test failure".to_string()); |
597 | | |
598 | | let response = supervisor_ref |
599 | | .ask(failure_message, Duration::from_millis(100)) |
600 | | .unwrap(); |
601 | | match response { |
602 | | Message::ChildRestarted(id) => assert_eq!(id, child_id), |
603 | | _ => panic!("Expected ChildRestarted message"), |
604 | | } |
605 | | } |
606 | | } |