1use std::fmt;
38use std::fmt::Debug;
39use std::fmt::Write;
40use std::sync::Arc;
41use std::sync::atomic::AtomicBool;
42use std::sync::atomic::Ordering;
43use std::time::SystemTime;
44
45use derivative::Derivative;
46use hyperactor_config::Flattrs;
47use indenter::indented;
48use serde::Deserialize;
49use serde::Serialize;
50
51use crate::ActorAddr;
52use crate::actor::ActorErrorKind;
53use crate::actor::ActorStatus;
54
55#[derive(Clone, Debug, Derivative, Serialize, Deserialize, typeuri::Named)]
57#[derivative(PartialEq, Eq)]
58pub struct ActorSupervisionEvent {
59 pub actor_id: ActorAddr,
61 pub display_name: Option<String>,
63 #[derivative(PartialEq = "ignore")]
65 pub occurred_at: SystemTime,
66 pub actor_status: ActorStatus,
68 #[derivative(PartialEq = "ignore")]
70 pub message_headers: Option<Flattrs>,
71 #[serde(default)]
76 #[derivative(PartialEq = "ignore")]
77 pub labels: Flattrs,
78 #[serde(skip, default = "local_fence")]
79 #[derivative(PartialEq = "ignore")]
80 pub(crate) local_fence: Arc<AtomicBool>,
81}
82wirevalue::register_type!(ActorSupervisionEvent);
83
84pub(crate) fn local_fence() -> Arc<AtomicBool> {
85 Arc::new(AtomicBool::new(false))
86}
87
88impl ActorSupervisionEvent {
89 pub fn new(
91 actor_id: impl Into<ActorAddr>,
92 display_name: Option<String>,
93 actor_status: ActorStatus,
94 message_headers: Option<Flattrs>,
95 ) -> Self {
96 Self {
97 actor_id: actor_id.into(),
98 display_name,
99 occurred_at: std::time::SystemTime::now(),
100 actor_status,
101 message_headers,
102 labels: Flattrs::new(),
103 local_fence: local_fence(),
104 }
105 }
106
107 pub fn with_labels(mut self, labels: Flattrs) -> Self {
109 self.labels = labels;
110 self
111 }
112
113 pub(crate) fn with_local_fence(mut self, local_fence: Arc<AtomicBool>) -> Self {
114 self.local_fence = local_fence;
115 self
116 }
117
118 pub(crate) fn is_locally_cancelled(&self) -> bool {
119 self.local_fence.load(Ordering::Acquire)
120 }
121
122 fn actor_name(&self) -> String {
123 self.display_name.clone().unwrap_or_else(|| {
124 if self.actor_id.is_root() {
125 format!("{},{}", self.actor_id.proc_addr(), self.actor_id.log_name())
126 } else {
127 self.actor_id.to_string()
128 }
129 })
130 }
131
132 pub fn caused_by(&self) -> &ActorSupervisionEvent {
136 let mut event = self;
137 while let ActorStatus::Failed(ActorErrorKind::UnhandledSupervisionEvent(inner)) =
138 &event.actor_status
139 {
140 event = inner;
141 }
142 event
143 }
144
145 pub fn actually_failing_actor(&self) -> Option<&ActorSupervisionEvent> {
152 if !self.is_error() {
153 return None;
154 }
155 Some(self.caused_by())
156 }
157
158 pub fn is_error(&self) -> bool {
160 self.actor_status.is_failed()
161 }
162
163 pub fn failure_report(&self) -> Option<String> {
166 if !self.is_error() {
167 return None;
168 }
169 let mut output = String::new();
170 self.write_failure_report(&mut output)
171 .expect("writing to String cannot fail");
172 Some(output)
173 }
174
175 fn write_failure_report(&self, f: &mut String) -> fmt::Result {
176 let mut current = self;
177 let mut last_unhandled: Option<&ActorSupervisionEvent> = None;
178 while let ActorStatus::Failed(ActorErrorKind::UnhandledSupervisionEvent(inner)) =
179 ¤t.actor_status
180 {
181 last_unhandled = Some(current);
182 current = inner;
183 }
184
185 if !current.actor_status.is_failed() {
186 let parent = last_unhandled.expect(
187 "top-level event is a failure but leaf is not; \
188 chain must contain an UnhandledSupervisionEvent",
189 );
190 writeln!(
191 f,
192 "The actor {} failed because it did not handle a supervision event \
193 from its child. The event was:",
194 parent.actor_name()
195 )?;
196 return write!(indented(f).with_str(" "), "{}", current);
197 }
198
199 writeln!(
200 f,
201 "The actor {} and all its descendants have failed:",
202 current.actor_name()
203 )?;
204 match ¤t.actor_status {
205 ActorStatus::Failed(ActorErrorKind::ErrorDuringHandlingSupervision(msg, child)) => {
206 writeln!(indented(f).with_str(" "), "{}", msg.trim_end())?;
207 writeln!(f, "This error occurred while handling another failure:")?;
208 let child_report = child
209 .failure_report()
210 .expect("child of ErrorDuringHandlingSupervision is always a failure");
211 write!(indented(f).with_str(" "), "{}", child_report)
212 }
213 ActorStatus::Failed(err) => write!(indented(f).with_str(" "), "{}", err),
214 _ => unreachable!("current.is_failed() was true"),
215 }
216 }
217}
218
219impl std::error::Error for ActorSupervisionEvent {}
220
221impl fmt::Display for ActorSupervisionEvent {
222 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
223 let name = self.actor_name();
224 match &self.actor_status {
225 ActorStatus::Failed(
226 err @ (ActorErrorKind::Generic(_) | ActorErrorKind::Aborted(_)),
227 ) => {
228 writeln!(f, "Supervision event: actor {} failed:", name)?;
229 write!(indented(f).with_str(" "), "{}", err)
230 }
231 ActorStatus::Failed(ActorErrorKind::UnhandledSupervisionEvent(child)) => {
232 writeln!(
233 f,
234 "Supervision event: actor {} failed because it did not handle \
235 a supervision event from its child. The child's event was:",
236 name
237 )?;
238 write!(indented(f).with_str(" "), "{}", child)
239 }
240 ActorStatus::Failed(ActorErrorKind::ErrorDuringHandlingSupervision(msg, child)) => {
241 writeln!(f, "Supervision event: actor {} failed:", name)?;
242 writeln!(indented(f).with_str(" "), "{}", msg.trim_end())?;
243 writeln!(
244 f,
245 "This error occurred while handling a supervision event from \
246 its child. The event was:"
247 )?;
248 write!(indented(f).with_str(" "), "{}", child)
249 }
250 ActorStatus::Stopped(_)
251 if self
252 .actor_id
253 .label()
254 .is_some_and(|l| l.as_str() == "host_agent" || l.as_str() == "proc_agent") =>
255 {
256 let addr = self.actor_id.proc_addr().addr().to_string();
257 write!(
258 f,
259 "Supervision event: the process {} owned by actor {} became unresponsive \
260 and is assumed dead, check the log on the host for details",
261 addr,
262 self.actor_name()
263 )
264 }
265 status => {
266 writeln!(f, "Supervision event: actor {} has status:", name)?;
267 write!(indented(f).with_str(" "), "{}", status)
268 }
269 }
270 }
271}
272
273#[cfg(test)]
274mod tests {
275 use super::*;
276 use crate::ProcAddr;
277 use crate::actor::ActorErrorKind;
278 use crate::actor::ActorStatus;
279 use crate::channel::ChannelAddr;
280
281 fn test_event(name: &str, status: ActorStatus) -> ActorSupervisionEvent {
282 let proc_id = ProcAddr::singleton(ChannelAddr::Local(0), "test_proc");
283 ActorSupervisionEvent::new(
284 proc_id.actor_addr(name),
285 Some(name.to_string()),
286 status,
287 None,
288 )
289 }
290
291 fn test_event_with_addr(
292 name: &str,
293 addr: ChannelAddr,
294 status: ActorStatus,
295 ) -> ActorSupervisionEvent {
296 let proc_id = ProcAddr::singleton(addr, "test_proc");
297 ActorSupervisionEvent::new(proc_id.actor_addr(name), None, status, None)
298 }
299
300 fn generic(name: &str, msg: &str) -> ActorSupervisionEvent {
301 test_event(
302 name,
303 ActorStatus::Failed(ActorErrorKind::Generic(msg.to_string())),
304 )
305 }
306
307 fn aborted(name: &str, msg: &str) -> ActorSupervisionEvent {
308 test_event(
309 name,
310 ActorStatus::Failed(ActorErrorKind::Aborted(msg.to_string())),
311 )
312 }
313
314 fn unhandled(name: &str, child: ActorSupervisionEvent) -> ActorSupervisionEvent {
315 test_event(
316 name,
317 ActorStatus::Failed(ActorErrorKind::UnhandledSupervisionEvent(Box::new(child))),
318 )
319 }
320
321 fn error_during(name: &str, msg: &str, child: ActorSupervisionEvent) -> ActorSupervisionEvent {
322 test_event(
323 name,
324 ActorStatus::Failed(ActorErrorKind::ErrorDuringHandlingSupervision(
325 msg.to_string(),
326 Box::new(child),
327 )),
328 )
329 }
330
331 fn stopped(name: &str, reason: &str) -> ActorSupervisionEvent {
332 test_event(name, ActorStatus::Stopped(reason.to_string()))
333 }
334
335 #[test]
338 fn test_display_generic() {
339 let e = generic("actor_a", "something went wrong");
340 assert_eq!(
341 format!("{}", e),
342 "Supervision event: actor actor_a failed:\n\
343 \x20 something went wrong"
344 );
345 }
346
347 #[test]
348 fn test_display_aborted() {
349 let e = aborted("actor_a", "user requested");
350 assert_eq!(
351 format!("{}", e),
352 "Supervision event: actor actor_a failed:\n\
353 \x20 actor explicitly aborted due to: user requested"
354 );
355 }
356
357 #[test]
358 fn test_display_unhandled_with_generic_child() {
359 let child = generic("child", "child error");
360 let parent = unhandled("parent", child);
361 assert_eq!(
362 format!("{}", parent),
363 "Supervision event: actor parent failed because it did not handle \
364 a supervision event from its child. The child's event was:\n\
365 \x20 Supervision event: actor child failed:\n\
366 \x20 child error"
367 );
368 }
369
370 #[test]
371 fn test_display_error_during_handling() {
372 let child = generic("child", "child error");
373 let parent = error_during("parent", "handler crashed", child);
374 assert_eq!(
375 format!("{}", parent),
376 "Supervision event: actor parent failed:\n\
377 \x20 handler crashed\n\
378 This error occurred while handling a supervision event from \
379 its child. The event was:\n\
380 \x20 Supervision event: actor child failed:\n\
381 \x20 child error"
382 );
383 }
384
385 #[test]
386 fn test_display_stopped() {
387 let e = stopped("actor_a", "done");
388 assert_eq!(
389 format!("{}", e),
390 "Supervision event: actor actor_a has status:\n\
391 \x20 stopped: done"
392 );
393 }
394
395 #[test]
396 fn test_display_deep_nesting() {
397 let leaf = generic("leaf", "root cause");
398 let mid = unhandled("mid", leaf);
399 let top = unhandled("top", mid);
400 let output = format!("{}", top);
401 assert!(output.contains("actor top failed because"));
402 assert!(output.contains(" Supervision event: actor mid failed because"));
403 assert!(output.contains(" Supervision event: actor leaf failed:"));
404 assert!(output.contains(" root cause"));
405 }
406
407 #[test]
408 fn test_display_unhandled_stopped_child() {
409 let child = stopped("child", "process exited");
410 let parent = unhandled("parent", child);
411 assert_eq!(
412 format!("{}", parent),
413 "Supervision event: actor parent failed because it did not handle \
414 a supervision event from its child. The child's event was:\n\
415 \x20 Supervision event: actor child has status:\n\
416 \x20 stopped: process exited"
417 );
418 }
419
420 #[test]
423 fn test_failure_report_generic() {
424 let e = generic("actor_a", "boom");
425 assert_eq!(
426 e.failure_report().unwrap(),
427 "The actor actor_a and all its descendants have failed:\n\
428 \x20 boom"
429 );
430 }
431
432 #[test]
433 fn test_failure_report_aborted() {
434 let e = aborted("actor_a", "user requested");
435 assert_eq!(
436 e.failure_report().unwrap(),
437 "The actor actor_a and all its descendants have failed:\n\
438 \x20 actor explicitly aborted due to: user requested"
439 );
440 }
441
442 #[test]
443 fn test_failure_report_unhandled_chain_to_generic() {
444 let leaf = generic("leaf", "root cause");
445 let mid = unhandled("mid", leaf);
446 let top = unhandled("top", mid);
447 assert_eq!(
448 top.failure_report().unwrap(),
449 "The actor leaf and all its descendants have failed:\n\
450 \x20 root cause"
451 );
452 }
453
454 #[test]
455 fn test_failure_report_unhandled_chain_to_stopped() {
456 let leaf = stopped("some_actor", "process exited");
457 let mid = unhandled("mid", leaf);
458 let top = unhandled("top", mid);
459 let report = top.failure_report().unwrap();
460 assert_eq!(
461 report,
462 "The actor mid failed because it did not handle a supervision event \
463 from its child. The event was:\n\
464 \x20 Supervision event: actor some_actor has status:\n\
465 \x20 stopped: process exited"
466 );
467 }
468
469 #[test]
470 fn test_failure_report_unhandled_chain_to_stopped_proc_agent() {
471 let leaf = test_event_with_addr(
472 "proc_agent",
473 ChannelAddr::Local(99),
474 ActorStatus::Stopped("process exited".to_string()),
475 );
476 let mid = unhandled("mid", leaf);
477 let top = unhandled("top", mid);
478 let report = top.failure_report().unwrap();
479 assert!(
480 report.contains("did not handle a supervision event"),
481 "got: {}",
482 report
483 );
484 assert!(
485 report.contains("process local:99 owned by actor") && report.contains("unresponsive"),
486 "got: {}",
487 report
488 );
489 }
490
491 #[test]
492 fn test_failure_report_error_during_handling() {
493 let child = generic("child", "original error");
494 let parent = error_during("parent", "handler failed", child);
495 assert_eq!(
496 parent.failure_report().unwrap(),
497 "The actor parent and all its descendants have failed:\n\
498 \x20 handler failed\n\
499 This error occurred while handling another failure:\n\
500 \x20 The actor child and all its descendants have failed:\n\
501 \x20 original error"
502 );
503 }
504
505 #[test]
506 fn test_failure_report_error_during_handling_nested() {
507 let leaf = generic("leaf", "root cause");
508 let mid = error_during("mid", "mid failed", leaf);
509 let top = error_during("top", "top failed", mid);
510 let report = top.failure_report().unwrap();
511 assert!(report.starts_with(
512 "The actor top and all its descendants have failed:\n\
513 \x20 top failed\n\
514 This error occurred while handling another failure:\n\
515 \x20 The actor mid and all its descendants have failed:\n\
516 \x20 mid failed\n\
517 \x20 This error occurred while handling another failure:\n\
518 \x20 The actor leaf and all its descendants have failed:\n\
519 \x20 root cause"
520 ));
521 }
522
523 #[test]
524 fn test_failure_report_unhandled_to_error_during_handling() {
525 let leaf = generic("leaf", "root cause");
526 let handler_err = error_during("handler", "while handling", leaf);
527 let top = unhandled("top", handler_err);
528 let report = top.failure_report().unwrap();
529 assert!(report.contains("The actor handler and all its descendants have failed:"));
530 assert!(report.contains("while handling"));
531 assert!(report.contains("root cause"));
532 }
533
534 #[test]
535 fn test_failure_report_none_on_non_failure() {
536 let e = stopped("actor_a", "done");
537 assert!(e.failure_report().is_none());
538 }
539
540 #[test]
541 fn test_failure_report_direct_generic_no_chain() {
542 let e = generic("solo", "direct error");
543 assert_eq!(
544 e.failure_report().unwrap(),
545 "The actor solo and all its descendants have failed:\n\
546 \x20 direct error"
547 );
548 }
549
550 #[test]
551 fn test_display_host_agent_stopped() {
552 let e = test_event_with_addr(
553 "host_agent",
554 ChannelAddr::Local(42),
555 ActorStatus::Stopped("gone".to_string()),
556 );
557 let output = format!("{}", e);
558 assert!(
559 output.contains("process local:42 owned by actor") && output.contains("unresponsive"),
560 "got: {}",
561 output
562 );
563 }
564
565 #[test]
566 fn test_display_proc_agent_stopped() {
567 let e = test_event_with_addr(
568 "proc_agent",
569 ChannelAddr::Local(7),
570 ActorStatus::Stopped("dead".to_string()),
571 );
572 let output = format!("{}", e);
573 assert!(
574 output.contains("process local:7 owned by actor") && output.contains("unresponsive"),
575 "got: {}",
576 output
577 );
578 }
579
580 #[test]
581 fn test_display_error_during_handling_trim_end() {
582 let child = generic("child", "child error");
583 let parent = error_during("parent", "msg with trailing newline\n", child);
584 let output = format!("{}", parent);
585 assert!(
586 output.contains(" msg with trailing newline\nThis error occurred"),
587 "writeln! should trim trailing newline from msg: {}",
588 output
589 );
590 }
591
592 #[test]
593 fn test_failure_report_error_during_handling_trim_end() {
594 let child = generic("child", "child error");
595 let parent = error_during("parent", "msg with trailing newline\n", child);
596 let report = parent.failure_report().unwrap();
597 assert!(
598 report.contains(" msg with trailing newline\nThis error occurred"),
599 "writeln! should trim trailing newline from msg: {}",
600 report
601 );
602 }
603
604 #[test]
609 fn test_sv1_actually_failing_actor_returns_stopped_child() {
610 let proc_id = ProcAddr::singleton(ChannelAddr::Local(0), "test_proc");
611 let child_id = proc_id.actor_addr("proc_agent");
612 let parent_id = proc_id.actor_addr("controller");
613
614 let child_event = ActorSupervisionEvent::new(
615 child_id.clone(),
616 Some("proc_agent".into()),
617 ActorStatus::Stopped("host died".into()),
618 None,
619 );
620 let parent_event = ActorSupervisionEvent::new(
621 parent_id,
622 Some("controller".into()),
623 ActorStatus::Failed(ActorErrorKind::UnhandledSupervisionEvent(Box::new(
624 child_event,
625 ))),
626 None,
627 );
628
629 let root = parent_event
631 .actually_failing_actor()
632 .expect("parent_event is a failure");
633 assert_eq!(root.actor_id, child_id);
634 assert!(
635 matches!(root.actor_status, ActorStatus::Stopped(_)),
636 "root cause should be the stopped child, got: {:?}",
637 root.actor_status,
638 );
639 }
640}