1use std::future::Future;
12use std::sync::Arc;
13use std::sync::atomic::AtomicBool;
14use std::sync::atomic::AtomicUsize;
15use std::sync::atomic::Ordering;
16use std::time::Duration;
17
18use async_trait::async_trait;
19use derivative::Derivative;
20use serde::Deserialize;
21use serde::Serialize;
22use tokio::sync::watch;
23use tokio::time;
24use typeuri::Named;
25use uuid::Uuid;
26
27use crate::Actor;
28use crate::ActorAddr;
29use crate::ActorHandle;
30use crate::ActorRef;
31use crate::Context;
32use crate::Endpoint;
33use crate::EndpointLocation;
34use crate::Handler;
35use crate::Instance;
36use crate::Message;
37use crate::OncePortRef;
38use crate::Port;
39use crate::PortRef;
40use crate::RemoteMessage;
41use crate::StatusMessage;
42use crate::actor::ActorStatus;
43use crate::actor::Referable;
44use crate::context;
45use crate::mailbox::MailboxError;
46use crate::mailbox::OncePortHandle;
47use crate::mailbox::PortHandle;
48use crate::mailbox::PortReceiver;
49use crate::ordering::Sequencer;
50use crate::proc::DeliveryProgressResponse;
51use crate::supervision::local_fence;
52
53const DEFAULT_INITIAL_DELAY: Duration = Duration::from_secs(2);
54const DEFAULT_POLL_INTERVAL: Duration = Duration::from_secs(1);
55const DEFAULT_REQUEST_TIMEOUT: Duration = Duration::from_secs(5);
56const DEFAULT_DELIVERY_TIMEOUT: Duration = Duration::from_secs(30);
57
58#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Named)]
60pub enum MonitorStatus {
61 Checking,
63 Alive(ActorStatus),
65 Failed(MonitorFailure),
67}
68wirevalue::register_type!(MonitorStatus);
69
70#[derive(
72 thiserror::Error,
73 Debug,
74 Clone,
75 Serialize,
76 Deserialize,
77 PartialEq,
78 Eq,
79 Named
80)]
81pub enum MonitorFailure {
82 #[error("monitored actor {actor_id} is gone")]
84 ActorGone {
85 actor_id: ActorAddr,
87 },
88 #[error("monitored actor {actor_id} stopped: {status}")]
90 ActorStopped {
91 actor_id: ActorAddr,
93 status: ActorStatus,
95 },
96 #[error("monitored actor {actor_id} failed: {status}")]
98 ActorFailed {
99 actor_id: ActorAddr,
101 status: ActorStatus,
103 },
104 #[error("status request to monitored actor {actor_id} timed out after {timeout_millis}ms")]
106 StatusRequestTimedOut {
107 actor_id: ActorAddr,
109 timeout_millis: u64,
111 },
112 #[error("status reply from monitored actor {actor_id} closed")]
114 StatusReplyClosed {
115 actor_id: ActorAddr,
117 },
118 #[error(
120 "delivery progress to monitored actor {actor_id} from {from} stalled for {timeout:?}: largest_sent={largest_sent}, largest_dequeueable={largest_dequeueable}"
121 )]
122 DeliveryProgressStalled {
123 actor_id: ActorAddr,
125 from: ActorAddr,
127 largest_sent: u64,
129 largest_dequeueable: u64,
131 timeout: Duration,
133 },
134 #[error(
136 "delivery progress request to monitored actor {actor_id} from {from} timed out after {timeout:?}"
137 )]
138 DeliveryProgressRequestTimedOut {
139 actor_id: ActorAddr,
141 from: ActorAddr,
143 timeout: Duration,
145 },
146 #[error("monitor for actor {actor_id} stopped before reporting a failure")]
148 MonitorStopped {
149 actor_id: ActorAddr,
151 },
152}
153wirevalue::register_type!(MonitorFailure);
154
155impl MonitorFailure {
156 pub fn actor_id(&self) -> &ActorAddr {
158 match self {
159 MonitorFailure::ActorGone { actor_id }
160 | MonitorFailure::ActorStopped { actor_id, .. }
161 | MonitorFailure::ActorFailed { actor_id, .. }
162 | MonitorFailure::StatusRequestTimedOut { actor_id, .. }
163 | MonitorFailure::StatusReplyClosed { actor_id }
164 | MonitorFailure::DeliveryProgressStalled { actor_id, .. }
165 | MonitorFailure::DeliveryProgressRequestTimedOut { actor_id, .. }
166 | MonitorFailure::MonitorStopped { actor_id } => actor_id,
167 }
168 }
169}
170
171#[derive(
173 thiserror::Error,
174 Debug,
175 Clone,
176 Serialize,
177 Deserialize,
178 Derivative,
179 Named
180)]
181#[derivative(PartialEq, Eq)]
182#[error("synthetic supervision event for {subject}: {failure}")]
183pub struct SyntheticSupervision {
184 pub subject: ActorAddr,
186 pub failure: Box<MonitorFailure>,
188 #[serde(skip, default = "local_fence")]
189 #[derivative(PartialEq = "ignore")]
190 pub(crate) local_fence: Arc<AtomicBool>,
191}
192wirevalue::register_type!(SyntheticSupervision);
193
194#[derive(Debug)]
196pub struct ActorMonitor {
197 inner: Option<MonitorInner>,
198}
199
200#[derive(Debug)]
202pub struct ActorSupervisor {
203 inner: Option<MonitorInner>,
204}
205
206#[derive(Debug)]
207struct MonitorInner {
208 target: ActorAddr,
209 handle: ActorHandle<MonitorActor>,
210 status: watch::Receiver<MonitorStatus>,
211 cancelled: Arc<AtomicBool>,
212 activations: Arc<AtomicUsize>,
213}
214
215#[derive(Debug)]
217#[must_use = "the monitor goes dormant once its last activation is dropped"]
218pub struct MonitorActivation {
219 activations: Arc<AtomicUsize>,
220}
221
222impl Drop for MonitorActivation {
223 fn drop(&mut self) {
224 self.activations.fetch_sub(1, Ordering::AcqRel);
227 }
228}
229
230#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Named)]
232pub enum EndpointId {
233 Handler,
235 Port(Port),
237}
238wirevalue::register_type!(EndpointId);
239
240impl EndpointId {
241 fn for_location(location: &EndpointLocation) -> Self {
242 match location {
243 EndpointLocation::Actor(_) | EndpointLocation::Local { .. } => Self::Handler,
244 EndpointLocation::Port(port) => Self::Port(port.port()),
245 }
246 }
247
248 fn largest_sent(&self, sequencer: &Sequencer, actor: &ActorAddr) -> u64 {
249 match self {
250 Self::Handler => sequencer.last_sent_to_actor(actor),
251 Self::Port(port) => sequencer
252 .last_sent(&actor.port_addr(port.clone()))
253 .unwrap_or_default(),
254 }
255 }
256}
257
258#[derive(Debug, Clone)]
259struct DeliveryMonitor {
260 from: ActorAddr,
261 sequencer: Sequencer,
262 endpoint: EndpointId,
263 timeout: Duration,
264 last_largest_dequeueable: u64,
265 last_progress_at: Option<time::Instant>,
266}
267
268impl DeliveryMonitor {
269 fn new(from: ActorAddr, sequencer: Sequencer, endpoint: EndpointId, timeout: Duration) -> Self {
270 Self {
271 from,
272 sequencer,
273 endpoint,
274 timeout,
275 last_largest_dequeueable: 0,
276 last_progress_at: None,
277 }
278 }
279}
280
281pub trait MonitorableEndpoint {
283 fn monitor<C>(&self, cx: &C) -> ActorMonitor
285 where
286 C: context::Actor,
287 {
288 let location = self.monitored_location();
289 let target = location.actor_addr();
290 ActorMonitor::spawn_with_delivery(
291 cx,
292 target,
293 DeliveryMonitor::new(
294 context::Mailbox::mailbox(cx).actor_addr().clone(),
295 cx.instance().sequencer().clone(),
296 EndpointId::for_location(&location),
297 DEFAULT_DELIVERY_TIMEOUT,
298 ),
299 )
300 }
301
302 fn monitored_location(&self) -> EndpointLocation;
304}
305
306impl<T> MonitorableEndpoint for &T
307where
308 T: MonitorableEndpoint + ?Sized,
309{
310 fn monitor<C>(&self, cx: &C) -> ActorMonitor
311 where
312 C: context::Actor,
313 {
314 (*self).monitor(cx)
315 }
316
317 fn monitored_location(&self) -> EndpointLocation {
318 (*self).monitored_location()
319 }
320}
321
322impl<A> MonitorableEndpoint for ActorHandle<A>
323where
324 A: Actor,
325{
326 fn monitored_location(&self) -> EndpointLocation {
327 EndpointLocation::Actor(self.actor_addr().clone())
328 }
329}
330
331impl<A> MonitorableEndpoint for ActorRef<A>
332where
333 A: Referable,
334{
335 fn monitored_location(&self) -> EndpointLocation {
336 EndpointLocation::Actor(self.actor_addr().clone())
337 }
338}
339
340impl<M> MonitorableEndpoint for PortHandle<M>
341where
342 M: Message,
343{
344 fn monitored_location(&self) -> EndpointLocation {
345 self.location().into()
346 }
347}
348
349impl<M> MonitorableEndpoint for OncePortHandle<M>
350where
351 M: Message,
352{
353 fn monitored_location(&self) -> EndpointLocation {
354 EndpointLocation::Port(self.port_addr().clone())
355 }
356}
357
358impl<M> MonitorableEndpoint for PortRef<M>
359where
360 M: RemoteMessage,
361{
362 fn monitored_location(&self) -> EndpointLocation {
363 EndpointLocation::Port(self.port_addr().clone())
364 }
365}
366
367impl<M> MonitorableEndpoint for OncePortRef<M>
368where
369 M: RemoteMessage,
370{
371 fn monitored_location(&self) -> EndpointLocation {
372 EndpointLocation::Port(self.port_addr().clone())
373 }
374}
375
376impl ActorMonitor {
377 pub fn spawn_persistent<C>(cx: &C, target: ActorAddr) -> Self
381 where
382 C: context::Actor,
383 {
384 Self::spawn_with_timings(
385 cx,
386 target,
387 DEFAULT_INITIAL_DELAY,
388 DEFAULT_POLL_INTERVAL,
389 DEFAULT_REQUEST_TIMEOUT,
390 None,
391 true,
392 )
393 }
394
395 pub fn spawn_dormant<C>(cx: &C, target: ActorAddr) -> Self
403 where
404 C: context::Actor,
405 {
406 Self::spawn_with_timings(
407 cx,
408 target,
409 DEFAULT_INITIAL_DELAY,
410 DEFAULT_POLL_INTERVAL,
411 DEFAULT_REQUEST_TIMEOUT,
412 None,
413 false,
414 )
415 }
416
417 fn spawn_with_delivery<C>(cx: &C, target: ActorAddr, delivery: DeliveryMonitor) -> Self
418 where
419 C: context::Actor,
420 {
421 Self::spawn_with_timings(
422 cx,
423 target,
424 DEFAULT_INITIAL_DELAY,
425 DEFAULT_POLL_INTERVAL,
426 DEFAULT_REQUEST_TIMEOUT,
427 Some(delivery),
428 true,
429 )
430 }
431
432 fn spawn_with_timings<C>(
433 cx: &C,
434 target: ActorAddr,
435 initial_delay: Duration,
436 poll_interval: Duration,
437 request_timeout: Duration,
438 delivery: Option<DeliveryMonitor>,
439 persistent: bool,
440 ) -> Self
441 where
442 C: context::Actor,
443 {
444 let cancelled = Arc::new(AtomicBool::new(false));
445 let activations = Arc::new(AtomicUsize::new(usize::from(persistent)));
448 let (status_tx, status) = watch::channel(MonitorStatus::Checking);
449 let handle = cx.spawn_with_label(
450 "monitor",
451 MonitorActor {
452 target: target.clone(),
453 initial_delay,
454 poll_interval,
455 request_timeout,
456 status_tx,
457 cancelled: cancelled.clone(),
458 state: MonitorState::Idle,
459 supervised: false,
460 delivery,
461 terminal_status_subscription: None,
462 activations: activations.clone(),
463 },
464 );
465 Self {
466 inner: Some(MonitorInner {
467 target,
468 handle,
469 status,
470 cancelled,
471 activations,
472 }),
473 }
474 }
475
476 pub fn target(&self) -> &ActorAddr {
478 &self.inner().target
479 }
480
481 pub fn status(&self) -> MonitorStatus {
483 self.inner().status.borrow().clone()
484 }
485
486 async fn wait_for_failure(&self) -> MonitorFailure {
487 let target = self.inner().target.clone();
488 let mut status = self.inner().status.clone();
489 loop {
490 if let MonitorStatus::Failed(failure) = &*status.borrow() {
491 return failure.clone();
492 }
493 if status.changed().await.is_err() {
494 return MonitorFailure::MonitorStopped { actor_id: target };
495 }
496 }
497 }
498
499 pub fn activate(&self, cx: &impl context::Actor) -> MonitorActivation {
509 let inner = self.inner();
510 if inner.activations.fetch_add(1, Ordering::AcqRel) == 0 {
511 inner.handle.post(cx, MonitorCommand::Activate);
512 }
513 MonitorActivation {
514 activations: inner.activations.clone(),
515 }
516 }
517
518 pub fn into_supervisor<C>(mut self, cx: &C) -> ActorSupervisor
520 where
521 C: context::Actor,
522 {
523 let inner = self.inner.take().expect("monitor inner should be present");
524 inner.handle.post(cx, MonitorCommand::Supervise);
525 ActorSupervisor { inner: Some(inner) }
526 }
527
528 pub async fn guard<F>(&self, fut: F) -> Result<F::Output, MonitorFailure>
530 where
531 F: Future,
532 {
533 tokio::pin!(fut);
534 tokio::select! {
535 result = fut => Ok(result),
536 failure = self.wait_for_failure() => Err(failure),
537 }
538 }
539
540 pub async fn recv<M>(
542 &self,
543 receiver: &mut PortReceiver<M>,
544 ) -> Result<Result<M, MailboxError>, MonitorFailure>
545 where
546 M: Message,
547 {
548 self.guard(receiver.recv()).await
549 }
550
551 fn inner(&self) -> &MonitorInner {
552 self.inner
553 .as_ref()
554 .expect("monitor inner should be present")
555 }
556}
557
558impl Drop for ActorMonitor {
559 fn drop(&mut self) {
560 if let Some(inner) = self.inner.take() {
561 inner.cancelled.store(true, Ordering::Release);
562 let _ = inner.handle.stop("monitor dropped");
563 }
564 }
565}
566
567impl Drop for ActorSupervisor {
568 fn drop(&mut self) {
569 if let Some(inner) = self.inner.take() {
570 inner.cancelled.store(true, Ordering::Release);
571 let _ = inner.handle.stop("supervisor dropped");
572 }
573 }
574}
575
576#[derive(Debug)]
577struct MonitorActor {
578 target: ActorAddr,
579 initial_delay: Duration,
580 poll_interval: Duration,
581 request_timeout: Duration,
582 status_tx: watch::Sender<MonitorStatus>,
583 cancelled: Arc<AtomicBool>,
584 state: MonitorState,
585 supervised: bool,
586 delivery: Option<DeliveryMonitor>,
587 terminal_status_subscription: Option<Uuid>,
588 activations: Arc<AtomicUsize>,
589}
590
591#[derive(Debug)]
593enum MonitorState {
594 Idle,
596 PollAfter { deadline: time::Instant },
598 Polling,
600 Failed(Box<MonitorFailure>),
602}
603
604#[derive(Debug)]
607struct MonitorTick;
608
609#[derive(Debug)]
610struct MonitorPollActor {
611 target: ActorAddr,
612 request_timeout: Duration,
613 monitor: ActorHandle<MonitorActor>,
614 state: MonitorPollState,
615}
616
617#[derive(Debug)]
618enum MonitorPollState {
619 Start {
620 delivery: Option<DeliveryPollRequest>,
621 },
622 WaitingForStatus {
623 delivery: Option<PendingDeliveryPoll>,
624 _reply: PortHandle<Option<ActorStatus>>,
625 },
626 WaitingForDelivery {
627 status: Option<ActorStatus>,
628 delivery: DeliveryPollRequest,
629 _reply: PortHandle<DeliveryProgressResponse>,
630 },
631 Done,
632}
633
634#[derive(Debug)]
635struct PendingDeliveryPoll {
636 request: DeliveryPollRequest,
637 reply: PortHandle<DeliveryProgressResponse>,
638}
639
640#[derive(Debug, Clone)]
641struct DeliveryPollRequest {
642 from: ActorAddr,
643 sequencer: Sequencer,
644 endpoint: EndpointId,
645}
646
647#[cfg(test)]
648#[derive(Debug)]
649struct MonitorProbe {
650 reply: crate::OncePortRef<()>,
651}
652
653#[derive(Debug, Clone, Serialize, Deserialize, Named)]
654enum MonitorCommand {
655 Supervise,
656 Activate,
657}
658wirevalue::register_type!(MonitorCommand);
659
660#[derive(Debug)]
661enum MonitorPollResult {
662 Status {
663 status: Option<ActorStatus>,
664 delivery: Option<DeliveryPollResult>,
665 },
666 TimedOut,
667 DeliveryTimedOut {
668 from: ActorAddr,
669 },
670}
671
672#[derive(Debug)]
673struct MonitorPollTimeout;
674
675#[derive(Debug)]
676struct MonitorStatusReply(Option<ActorStatus>);
677
678#[derive(Debug)]
679struct MonitorTerminalStatus(Option<ActorStatus>);
680
681#[derive(Debug)]
682struct MonitorDeliveryReply(DeliveryProgressResponse);
683
684#[derive(Debug)]
685struct DeliveryPollResult {
686 from: ActorAddr,
687 largest_sent: u64,
688 response: DeliveryProgressResponse,
689}
690
691#[async_trait]
692impl Actor for MonitorActor {
693 async fn init(&mut self, this: &Instance<Self>) -> anyhow::Result<()> {
694 let subscriber = this
695 .port::<MonitorTerminalStatus>()
696 .contramap(MonitorTerminalStatus)
697 .bind();
698 let subscription_id = Uuid::new_v4();
699 let mut status_port = self.target.status_port();
700 status_port.return_undeliverable(false);
701 status_port.post(
702 this,
703 StatusMessage::SubscribeTerminal {
704 subscription_id,
705 subscriber,
706 },
707 );
708 self.terminal_status_subscription = Some(subscription_id);
709 self.reconcile(this, time::Instant::now())
710 }
711
712 async fn cleanup(
713 &mut self,
714 this: &Instance<Self>,
715 _err: Option<&crate::actor::ActorError>,
716 ) -> anyhow::Result<()> {
717 let Some(subscription_id) = self.terminal_status_subscription.take() else {
718 return Ok(());
719 };
720 let mut status_port = self.target.status_port();
721 status_port.return_undeliverable(false);
722 status_port.post(this, StatusMessage::UnsubscribeTerminal { subscription_id });
723 Ok(())
724 }
725}
726
727#[async_trait]
728impl Actor for MonitorPollActor {
729 async fn init(&mut self, this: &Instance<Self>) -> anyhow::Result<()> {
730 self.request_status(this);
731 Ok(())
732 }
733}
734
735#[async_trait]
736impl Handler<MonitorStatusReply> for MonitorPollActor {
737 async fn handle(
738 &mut self,
739 cx: &Context<Self>,
740 MonitorStatusReply(status): MonitorStatusReply,
741 ) -> anyhow::Result<()> {
742 let MonitorPollState::WaitingForStatus { delivery, .. } =
743 std::mem::replace(&mut self.state, MonitorPollState::Done)
744 else {
745 panic!("monitor poll actor received status while not waiting for status");
746 };
747 match delivery {
748 Some(delivery) => {
749 self.state = MonitorPollState::WaitingForDelivery {
750 status,
751 delivery: delivery.request,
752 _reply: delivery.reply,
753 };
754 Ok(())
755 }
756 None => self.finish(
757 cx,
758 MonitorPollResult::Status {
759 status,
760 delivery: None,
761 },
762 ),
763 }
764 }
765}
766
767#[async_trait]
768impl Handler<MonitorDeliveryReply> for MonitorPollActor {
769 async fn handle(
770 &mut self,
771 cx: &Context<Self>,
772 MonitorDeliveryReply(response): MonitorDeliveryReply,
773 ) -> anyhow::Result<()> {
774 let MonitorPollState::WaitingForDelivery {
775 status, delivery, ..
776 } = std::mem::replace(&mut self.state, MonitorPollState::Done)
777 else {
778 panic!("monitor poll actor received delivery progress while not waiting for it");
779 };
780 let delivery = DeliveryPollResult {
781 largest_sent: delivery
782 .endpoint
783 .largest_sent(&delivery.sequencer, &self.target),
784 from: delivery.from,
785 response,
786 };
787 self.finish(
788 cx,
789 MonitorPollResult::Status {
790 status,
791 delivery: Some(delivery),
792 },
793 )
794 }
795}
796
797#[async_trait]
798impl Handler<MonitorPollTimeout> for MonitorPollActor {
799 async fn handle(
800 &mut self,
801 cx: &Context<Self>,
802 _timeout: MonitorPollTimeout,
803 ) -> anyhow::Result<()> {
804 let result = match &self.state {
805 MonitorPollState::WaitingForStatus { .. } => Some(MonitorPollResult::TimedOut),
806 MonitorPollState::WaitingForDelivery { delivery, .. } => {
807 Some(MonitorPollResult::DeliveryTimedOut {
808 from: delivery.from.clone(),
809 })
810 }
811 _ => None,
812 };
813
814 match result {
815 Some(result) => self.finish(cx, result),
816 None => Ok(()),
817 }
818 }
819}
820
821impl MonitorPollActor {
822 fn request_status(&mut self, this: &Instance<Self>) {
823 let MonitorPollState::Start { delivery } =
824 std::mem::replace(&mut self.state, MonitorPollState::Done)
825 else {
826 panic!("monitor poll actor must start in the start state");
827 };
828 let reply = this
829 .port::<MonitorStatusReply>()
830 .contramap(MonitorStatusReply);
831 let reply_ref = reply.bind().into_once();
832 let (delivery_request, delivery) = match delivery {
833 Some(delivery) => {
834 let delivery_reply = this
835 .port::<MonitorDeliveryReply>()
836 .contramap(MonitorDeliveryReply);
837 let delivery_reply_ref = delivery_reply.bind().into_once();
838 (
839 Some((
840 delivery.sequencer.session_id(),
841 delivery.endpoint.clone(),
842 delivery_reply_ref,
843 )),
844 Some(PendingDeliveryPoll {
845 request: delivery,
846 reply: delivery_reply,
847 }),
848 )
849 }
850 None => (None, None),
851 };
852 self.state = MonitorPollState::WaitingForStatus {
853 delivery,
854 _reply: reply,
855 };
856 self.target.status_port().post(
857 this,
858 StatusMessage::GetStatus {
859 reply: reply_ref,
860 delivery: delivery_request,
861 },
862 );
863 this.post_after(this, MonitorPollTimeout, self.request_timeout);
864 }
865
866 fn finish(&mut self, cx: &Context<Self>, result: MonitorPollResult) -> anyhow::Result<()> {
867 self.state = MonitorPollState::Done;
868 self.monitor.post(cx, result);
869 cx.exit("poll complete").map_err(anyhow::Error::from)
870 }
871}
872
873#[async_trait]
874impl Handler<MonitorTerminalStatus> for MonitorActor {
875 async fn handle(
876 &mut self,
877 cx: &Context<Self>,
878 MonitorTerminalStatus(status): MonitorTerminalStatus,
879 ) -> anyhow::Result<()> {
880 if matches!(self.state, MonitorState::Failed(_)) {
881 return Ok(());
882 }
883 let failure = status.map_or_else(
884 || MonitorFailure::ActorGone {
885 actor_id: self.target.clone(),
886 },
887 |status| {
888 self.classify_failure(status)
889 .expect("terminal status notification must contain a terminal status")
890 },
891 );
892 self.record_failure(failure);
893 self.reconcile(cx, time::Instant::now())
894 }
895}
896
897#[async_trait]
898impl Handler<MonitorTick> for MonitorActor {
899 async fn handle(&mut self, cx: &Context<Self>, _message: MonitorTick) -> anyhow::Result<()> {
900 self.reconcile(cx, time::Instant::now())
901 }
902}
903
904#[async_trait]
905impl Handler<MonitorPollResult> for MonitorActor {
906 async fn handle(
907 &mut self,
908 cx: &Context<Self>,
909 message: MonitorPollResult,
910 ) -> anyhow::Result<()> {
911 if !matches!(self.state, MonitorState::Polling) {
912 return Ok(());
913 }
914
915 let now = time::Instant::now();
916 let poll_result = match message {
917 MonitorPollResult::Status { status, delivery } => {
918 let Some(status) = status else {
919 self.record_failure(MonitorFailure::ActorGone {
920 actor_id: self.target.clone(),
921 });
922 return self.reconcile(cx, now);
923 };
924
925 if let Some(failure) = self.classify_failure(status.clone()) {
926 Err(failure)
927 } else if let Some(delivery) = delivery {
928 if matches!(delivery.response, DeliveryProgressResponse::ActorGone) {
929 Err(MonitorFailure::ActorGone {
930 actor_id: self.target.clone(),
931 })
932 } else if matches!(delivery.response, DeliveryProgressResponse::Incomplete) {
933 self.status_tx.send_replace(MonitorStatus::Alive(status));
934 Ok(())
935 } else if let Some(failure) = self.classify_delivery_failure(delivery, now) {
936 Err(failure)
937 } else {
938 self.status_tx.send_replace(MonitorStatus::Alive(status));
939 Ok(())
940 }
941 } else {
942 self.status_tx.send_replace(MonitorStatus::Alive(status));
943 Ok(())
944 }
945 }
946 MonitorPollResult::TimedOut => Err(MonitorFailure::StatusRequestTimedOut {
947 actor_id: self.target.clone(),
948 timeout_millis: self.request_timeout.as_millis() as u64,
949 }),
950 MonitorPollResult::DeliveryTimedOut { from } => {
951 Err(MonitorFailure::DeliveryProgressRequestTimedOut {
952 actor_id: self.target.clone(),
953 from,
954 timeout: self.request_timeout,
955 })
956 }
957 };
958
959 match poll_result {
960 Ok(()) => {
961 self.poll_after(cx, now, self.poll_interval);
962 Ok(())
963 }
964 Err(failure) => {
965 self.record_failure(failure);
966 self.reconcile(cx, now)
967 }
968 }
969 }
970}
971
972#[cfg(test)]
973#[async_trait]
974impl Handler<MonitorProbe> for MonitorActor {
975 async fn handle(&mut self, cx: &Context<Self>, message: MonitorProbe) -> anyhow::Result<()> {
976 message.reply.post(cx, ());
977 Ok(())
978 }
979}
980
981#[cfg(test)]
982#[async_trait]
983impl Handler<MonitorProbe> for MonitorPollActor {
984 async fn handle(&mut self, cx: &Context<Self>, message: MonitorProbe) -> anyhow::Result<()> {
985 message.reply.post(cx, ());
986 Ok(())
987 }
988}
989
990#[async_trait]
991impl Handler<MonitorCommand> for MonitorActor {
992 async fn handle(&mut self, cx: &Context<Self>, message: MonitorCommand) -> anyhow::Result<()> {
993 if matches!(message, MonitorCommand::Supervise) {
994 self.supervised = true;
995 }
996 self.reconcile(cx, time::Instant::now())
997 }
998}
999
1000impl MonitorActor {
1001 fn is_active(&self) -> bool {
1002 self.activations.load(Ordering::Acquire) > 0
1003 }
1004
1005 fn reconcile(&mut self, this: &Instance<Self>, now: time::Instant) -> anyhow::Result<()> {
1008 match &self.state {
1009 MonitorState::Idle => {
1010 if self.is_active() {
1011 self.poll_after(this, now, self.initial_delay);
1012 }
1013 Ok(())
1014 }
1015 MonitorState::PollAfter { deadline } => {
1016 if now < *deadline {
1017 return Ok(());
1018 }
1019 if self.is_active() {
1020 self.start_poll(this);
1021 } else {
1022 self.state = MonitorState::Idle;
1023 }
1024 Ok(())
1025 }
1026 MonitorState::Polling => Ok(()),
1027 MonitorState::Failed(failure)
1028 if self.supervised && !self.cancelled.load(Ordering::Acquire) =>
1029 {
1030 self.fail_supervised((**failure).clone())
1031 }
1032 MonitorState::Failed(_) => Ok(()),
1033 }
1034 }
1035
1036 fn poll_after(&mut self, this: &Instance<Self>, now: time::Instant, delay: Duration) {
1037 let deadline = now + delay;
1038 self.state = MonitorState::PollAfter { deadline };
1039 this.post_after(
1040 this,
1041 MonitorTick,
1042 deadline.saturating_duration_since(time::Instant::now()),
1043 );
1044 }
1045
1046 fn start_poll(&mut self, this: &Instance<Self>) {
1047 self.state = MonitorState::Polling;
1048 this.spawn_with_label(
1049 "monitor_poll",
1050 MonitorPollActor {
1051 target: self.target.clone(),
1052 request_timeout: self.request_timeout,
1053 monitor: this.handle(),
1054 state: MonitorPollState::Start {
1055 delivery: self.delivery.as_ref().map(|delivery| DeliveryPollRequest {
1056 from: delivery.from.clone(),
1057 sequencer: delivery.sequencer.clone(),
1058 endpoint: delivery.endpoint.clone(),
1059 }),
1060 },
1061 },
1062 );
1063 }
1064
1065 fn classify_failure(&self, status: ActorStatus) -> Option<MonitorFailure> {
1066 match status {
1067 ActorStatus::Stopped(_) => Some(MonitorFailure::ActorStopped {
1068 actor_id: self.target.clone(),
1069 status,
1070 }),
1071 ActorStatus::Failed(_)
1072 | ActorStatus::Stopping(crate::actor::ActorStoppingReason::Zombie(_))
1073 | ActorStatus::Unknown => Some(MonitorFailure::ActorFailed {
1074 actor_id: self.target.clone(),
1075 status,
1076 }),
1077 ActorStatus::Created
1078 | ActorStatus::Initializing
1079 | ActorStatus::Client
1080 | ActorStatus::Idle
1081 | ActorStatus::Processing(_, _)
1082 | ActorStatus::Stopping(_) => None,
1083 }
1084 }
1085
1086 fn classify_delivery_failure(
1087 &mut self,
1088 result: DeliveryPollResult,
1089 now: time::Instant,
1090 ) -> Option<MonitorFailure> {
1091 let delivery = self
1092 .delivery
1093 .as_mut()
1094 .expect("delivery poll result requires delivery monitor state");
1095 let DeliveryProgressResponse::Progress(progress) = result.response else {
1096 return None;
1097 };
1098
1099 let largest_dequeueable = progress.largest_dequeueable_sequence;
1100 let is_caught_up = largest_dequeueable >= result.largest_sent;
1101 let made_progress = largest_dequeueable > delivery.last_largest_dequeueable;
1102
1103 if is_caught_up || made_progress {
1104 delivery.last_largest_dequeueable = largest_dequeueable;
1105 delivery.last_progress_at = Some(now);
1106 return None;
1107 }
1108
1109 if largest_dequeueable < result.largest_sent {
1110 let last_progress_at = delivery.last_progress_at.get_or_insert(now);
1111 if now.duration_since(*last_progress_at) >= delivery.timeout {
1112 return Some(MonitorFailure::DeliveryProgressStalled {
1113 actor_id: self.target.clone(),
1114 from: result.from,
1115 largest_sent: result.largest_sent,
1116 largest_dequeueable,
1117 timeout: delivery.timeout,
1118 });
1119 }
1120 }
1121
1122 None
1123 }
1124
1125 fn record_failure(&mut self, failure: MonitorFailure) {
1126 self.state = MonitorState::Failed(Box::new(failure.clone()));
1127 self.status_tx.send_replace(MonitorStatus::Failed(failure));
1128 }
1129
1130 fn fail_supervised(&self, failure: MonitorFailure) -> anyhow::Result<()> {
1131 anyhow::bail!(crate::actor::ActorErrorKind::SyntheticSupervision(
1132 Box::new(SyntheticSupervision {
1133 subject: self.target.clone(),
1134 failure: Box::new(failure),
1135 local_fence: self.cancelled.clone(),
1136 },)
1137 ))
1138 }
1139}
1140
1141#[cfg(test)]
1142mod tests {
1143 use std::time::Duration;
1144
1145 use async_trait::async_trait;
1146 use tokio::time;
1147
1148 use super::*;
1149 use crate as hyperactor;
1150 use crate::Proc;
1151 use crate::actor::ActorErrorKind;
1152 use crate::config;
1153 use crate::ordering::DeliveryProgress;
1154 use crate::port::Port;
1155 use crate::supervision::ActorSupervisionEvent;
1156
1157 #[derive(Debug, typeuri::Named)]
1158 struct TestActor;
1159
1160 #[async_trait]
1161 impl Actor for TestActor {}
1162
1163 impl crate::actor::Referable for TestActor {}
1164
1165 #[derive(Debug, Clone, Serialize, Deserialize, Named)]
1166 struct DeliveryTestMsg;
1167
1168 #[derive(Debug)]
1169 #[hyperactor::export(handlers = [DeliveryTestMsg])]
1170 struct DeliveryTestActor;
1171
1172 #[async_trait]
1173 impl Actor for DeliveryTestActor {}
1174
1175 #[async_trait]
1176 impl Handler<DeliveryTestMsg> for DeliveryTestActor {
1177 async fn handle(
1178 &mut self,
1179 _cx: &Context<Self>,
1180 _message: DeliveryTestMsg,
1181 ) -> anyhow::Result<()> {
1182 Ok(())
1183 }
1184 }
1185
1186 #[derive(Debug)]
1187 struct SupervisorActor {
1188 target: ActorAddr,
1189 ready: Option<crate::OncePortRef<ActorAddr>>,
1190 events: crate::PortRef<ActorSupervisionEvent>,
1191 supervisor: Option<ActorSupervisor>,
1192 }
1193
1194 #[derive(Debug)]
1195 struct ConvertFailedMonitorActor {
1196 target: ActorAddr,
1197 ready: Option<crate::OncePortRef<ActorAddr>>,
1198 events: crate::PortRef<ActorSupervisionEvent>,
1199 supervisor: Option<ActorSupervisor>,
1200 }
1201
1202 #[derive(Debug)]
1203 struct DropSupervisorActor {
1204 target: ActorAddr,
1205 ready: Option<crate::OncePortRef<ActorAddr>>,
1206 events: crate::PortRef<ActorSupervisionEvent>,
1207 }
1208
1209 #[derive(Debug)]
1210 struct DropQueuedSupervisorActor {
1211 target: ActorHandle<TestActor>,
1212 ready: Option<crate::OncePortRef<()>>,
1213 events: crate::PortRef<ActorSupervisionEvent>,
1214 }
1215
1216 #[async_trait]
1217 impl Actor for SupervisorActor {
1218 async fn init(&mut self, this: &Instance<Self>) -> anyhow::Result<()> {
1219 let monitor = ActorMonitor::spawn_with_timings(
1220 this,
1221 self.target.clone(),
1222 Duration::ZERO,
1223 Duration::from_millis(10),
1224 Duration::from_millis(50),
1225 None,
1226 true,
1227 );
1228 let monitor_id = monitor
1229 .inner
1230 .as_ref()
1231 .expect("monitor inner should be present")
1232 .handle
1233 .actor_addr()
1234 .clone();
1235 self.supervisor = Some(monitor.into_supervisor(this));
1236 self.ready
1237 .take()
1238 .expect("ready port should be present")
1239 .post(this, monitor_id);
1240 Ok(())
1241 }
1242
1243 async fn handle_supervision_event(
1244 &mut self,
1245 this: &Instance<Self>,
1246 event: &ActorSupervisionEvent,
1247 ) -> anyhow::Result<bool> {
1248 self.events.post(this, event.clone());
1249 Ok(true)
1250 }
1251 }
1252
1253 #[async_trait]
1254 impl Actor for ConvertFailedMonitorActor {
1255 async fn init(&mut self, this: &Instance<Self>) -> anyhow::Result<()> {
1256 let monitor = ActorMonitor::spawn_with_timings(
1257 this,
1258 self.target.clone(),
1259 Duration::ZERO,
1260 Duration::from_millis(10),
1261 Duration::from_millis(50),
1262 None,
1263 true,
1264 );
1265 let monitor_id = monitor
1266 .inner
1267 .as_ref()
1268 .expect("monitor inner should be present")
1269 .handle
1270 .actor_addr()
1271 .clone();
1272 let _failure = monitor.wait_for_failure().await;
1273 self.supervisor = Some(monitor.into_supervisor(this));
1274 self.ready
1275 .take()
1276 .expect("ready port should be present")
1277 .post(this, monitor_id);
1278 Ok(())
1279 }
1280
1281 async fn handle_supervision_event(
1282 &mut self,
1283 this: &Instance<Self>,
1284 event: &ActorSupervisionEvent,
1285 ) -> anyhow::Result<bool> {
1286 self.events.post(this, event.clone());
1287 Ok(true)
1288 }
1289 }
1290
1291 #[async_trait]
1292 impl Actor for DropSupervisorActor {
1293 async fn init(&mut self, this: &Instance<Self>) -> anyhow::Result<()> {
1294 let monitor = ActorMonitor::spawn_with_timings(
1295 this,
1296 self.target.clone(),
1297 Duration::ZERO,
1298 Duration::from_millis(10),
1299 Duration::from_millis(50),
1300 None,
1301 true,
1302 );
1303 let monitor_id = monitor
1304 .inner
1305 .as_ref()
1306 .expect("monitor inner should be present")
1307 .handle
1308 .actor_addr()
1309 .clone();
1310 drop(monitor.into_supervisor(this));
1311 self.ready
1312 .take()
1313 .expect("ready port should be present")
1314 .post(this, monitor_id);
1315 Ok(())
1316 }
1317
1318 async fn handle_supervision_event(
1319 &mut self,
1320 this: &Instance<Self>,
1321 event: &ActorSupervisionEvent,
1322 ) -> anyhow::Result<bool> {
1323 self.events.post(this, event.clone());
1324 Ok(true)
1325 }
1326 }
1327
1328 #[async_trait]
1329 impl Actor for DropQueuedSupervisorActor {
1330 async fn init(&mut self, this: &Instance<Self>) -> anyhow::Result<()> {
1331 let monitor = ActorMonitor::spawn_with_timings(
1332 this,
1333 self.target.actor_addr().clone(),
1334 Duration::ZERO,
1335 Duration::from_millis(10),
1336 Duration::from_millis(50),
1337 None,
1338 true,
1339 );
1340 let supervisor = monitor.into_supervisor(this);
1341 let mut status = supervisor
1342 .inner
1343 .as_ref()
1344 .expect("supervisor inner should be present")
1345 .status
1346 .clone();
1347
1348 self.target.drain_and_stop("done").unwrap();
1349
1350 loop {
1351 if matches!(*status.borrow(), MonitorStatus::Failed(_)) {
1352 break;
1353 }
1354 status.changed().await?;
1355 }
1356
1357 drop(supervisor);
1358 self.ready
1359 .take()
1360 .expect("ready port should be present")
1361 .post(this, ());
1362 Ok(())
1363 }
1364
1365 async fn handle_supervision_event(
1366 &mut self,
1367 this: &Instance<Self>,
1368 event: &ActorSupervisionEvent,
1369 ) -> anyhow::Result<bool> {
1370 self.events.post(this, event.clone());
1371 Ok(true)
1372 }
1373 }
1374
1375 fn short_monitor(client: &crate::Client, target: ActorAddr) -> ActorMonitor {
1376 ActorMonitor::spawn_with_timings(
1377 client,
1378 target,
1379 Duration::ZERO,
1380 Duration::from_millis(10),
1381 Duration::from_millis(50),
1382 None,
1383 true,
1384 )
1385 }
1386
1387 fn short_delivery_monitor(client: &crate::Client, target: ActorAddr) -> ActorMonitor {
1388 ActorMonitor::spawn_with_timings(
1389 client,
1390 target.clone(),
1391 Duration::ZERO,
1392 Duration::from_millis(10),
1393 Duration::from_millis(50),
1394 Some(DeliveryMonitor::new(
1395 context::Mailbox::mailbox(client).actor_addr().clone(),
1396 client.sequencer().clone(),
1397 EndpointId::Handler,
1398 Duration::from_millis(50),
1399 )),
1400 true,
1401 )
1402 }
1403
1404 fn monitor_actor_for_delivery(target: ActorAddr, delivery: DeliveryMonitor) -> MonitorActor {
1405 let (status_tx, _status_rx) = watch::channel(MonitorStatus::Checking);
1406 MonitorActor {
1407 target,
1408 initial_delay: Duration::ZERO,
1409 poll_interval: Duration::from_millis(10),
1410 request_timeout: Duration::from_millis(50),
1411 status_tx,
1412 cancelled: Arc::new(AtomicBool::new(false)),
1413 state: MonitorState::Idle,
1414 supervised: false,
1415 delivery: Some(delivery),
1416 terminal_status_subscription: None,
1417 activations: Arc::new(AtomicUsize::new(1)),
1418 }
1419 }
1420
1421 async fn wait_for_alive(monitor: &ActorMonitor) -> ActorStatus {
1422 let deadline = time::Instant::now() + Duration::from_secs(5);
1423 loop {
1424 if let MonitorStatus::Alive(status) = monitor.status() {
1425 return status;
1426 }
1427 assert!(
1428 time::Instant::now() < deadline,
1429 "timed out waiting for monitor to report alive"
1430 );
1431 time::sleep(Duration::from_millis(10)).await;
1432 }
1433 }
1434
1435 #[tokio::test]
1436 async fn test_monitor_reports_alive_actor() {
1437 let proc = Proc::isolated();
1438 let client = proc.client("client");
1439 let handle = proc.spawn(TestActor);
1440 let monitor = short_monitor(&client, handle.actor_addr().clone());
1441
1442 assert!(matches!(
1443 wait_for_alive(&monitor).await,
1444 ActorStatus::Idle | ActorStatus::Processing(_, _)
1445 ));
1446 }
1447
1448 #[tokio::test]
1449 async fn test_monitor_reports_nonexistent_actor() {
1450 let config = hyperactor_config::global::lock();
1451 let _config_guard = config.override_key(
1452 crate::config::PENDING_ACTOR_DELIVERY_TIMEOUT,
1453 Duration::ZERO,
1454 );
1455 let proc = Proc::isolated();
1456 let client = proc.client("client");
1457 let missing = proc.proc_addr().actor_addr("missing");
1458 let monitor = short_monitor(&client, missing.clone());
1459
1460 assert_eq!(
1461 monitor.wait_for_failure().await,
1462 MonitorFailure::ActorGone { actor_id: missing }
1463 );
1464 }
1465
1466 #[tokio::test]
1467 async fn test_monitor_fallback_poll_respects_initial_delay() {
1468 let proc = Proc::isolated();
1469 let client = proc.client("client");
1470 let unreachable =
1471 crate::ProcAddr::instance(crate::channel::ChannelAddr::Local(1234), "gone")
1472 .actor_addr("actor");
1473 let monitor = ActorMonitor::spawn_with_timings(
1474 &client,
1475 unreachable.clone(),
1476 Duration::from_millis(100),
1477 Duration::from_millis(10),
1478 Duration::from_millis(50),
1479 None,
1480 true,
1481 );
1482
1483 assert!(
1484 time::timeout(Duration::from_millis(20), monitor.wait_for_failure())
1485 .await
1486 .is_err()
1487 );
1488 assert_eq!(
1489 monitor.wait_for_failure().await,
1490 MonitorFailure::StatusRequestTimedOut {
1491 actor_id: unreachable,
1492 timeout_millis: 50,
1493 }
1494 );
1495 }
1496
1497 #[test]
1498 fn test_default_initial_delay_is_two_seconds() {
1499 assert_eq!(DEFAULT_INITIAL_DELAY, Duration::from_secs(2));
1500 }
1501
1502 #[tokio::test]
1503 async fn test_dropping_monitor_before_first_tick_is_noop() {
1504 let proc = Proc::isolated();
1505 let client = proc.client("client");
1506 let missing = proc.proc_addr().actor_addr("missing");
1507 let monitor = ActorMonitor::spawn_with_timings(
1508 &client,
1509 missing,
1510 Duration::from_millis(200),
1511 Duration::from_millis(10),
1512 Duration::from_millis(50),
1513 None,
1514 true,
1515 );
1516 let mut status = monitor
1517 .inner
1518 .as_ref()
1519 .expect("monitor inner should be present")
1520 .status
1521 .clone();
1522
1523 drop(monitor);
1524
1525 time::timeout(Duration::from_secs(1), async {
1526 while status.changed().await.is_ok() {}
1527 })
1528 .await
1529 .expect("monitor actor should stop when monitor handle is dropped");
1530 assert_eq!(*status.borrow(), MonitorStatus::Checking);
1531
1532 time::sleep(Duration::from_millis(250)).await;
1533 assert_eq!(*status.borrow(), MonitorStatus::Checking);
1534 }
1535
1536 #[tokio::test]
1537 async fn test_monitor_reports_stopped_actor() {
1538 let proc = Proc::isolated();
1539 let client = proc.client("client");
1540 let handle = proc.spawn(TestActor);
1541 let actor_id = handle.actor_addr().clone();
1542 let monitor = short_monitor(&client, actor_id.clone());
1543
1544 handle.drain_and_stop("done").unwrap();
1545
1546 assert_eq!(
1547 monitor.wait_for_failure().await,
1548 MonitorFailure::ActorStopped {
1549 actor_id,
1550 status: ActorStatus::Stopped("done".to_string()),
1551 }
1552 );
1553 }
1554
1555 #[tokio::test]
1556 async fn test_default_monitor_uses_terminal_status_notification() {
1557 let proc = Proc::isolated();
1558 let client = proc.client("client");
1559 let handle = proc.spawn(TestActor);
1560 let actor_id = handle.actor_addr().clone();
1561 let monitor = ActorMonitor::spawn_persistent(&client, actor_id.clone());
1562 let mut monitor_actor_status = monitor
1563 .inner
1564 .as_ref()
1565 .expect("monitor inner should be present")
1566 .handle
1567 .status();
1568 monitor_actor_status
1569 .wait_for(|status| matches!(status, ActorStatus::Idle))
1570 .await
1571 .expect("monitor actor should finish initialization");
1572 assert_eq!(
1573 proc.terminal_status_subscriber_count(handle.actor_addr().id()),
1574 1,
1575 "monitor initialization should register its status subscriber"
1576 );
1577
1578 handle.drain_and_stop("done").unwrap();
1579
1580 assert_eq!(
1581 time::timeout(Duration::from_millis(200), monitor.wait_for_failure())
1582 .await
1583 .expect("terminal status notification should beat the initial poll delay"),
1584 MonitorFailure::ActorStopped {
1585 actor_id,
1586 status: ActorStatus::Stopped("done".to_string()),
1587 }
1588 );
1589 }
1590
1591 #[tokio::test]
1592 async fn test_dormant_monitor_observes_terminal_status() {
1593 let proc = Proc::isolated();
1594 let client = proc.client("client");
1595 let handle = proc.spawn(TestActor);
1596 let actor_id = handle.actor_addr().clone();
1597 let monitor = ActorMonitor::spawn_dormant(&client, actor_id.clone());
1598 monitor
1599 .inner()
1600 .handle
1601 .status()
1602 .wait_for(|status| matches!(status, ActorStatus::Idle))
1603 .await
1604 .expect("monitor actor should finish initialization");
1605
1606 handle.drain_and_stop("done").unwrap();
1607
1608 let expected = MonitorFailure::ActorStopped {
1609 actor_id,
1610 status: ActorStatus::Stopped("done".to_string()),
1611 };
1612 assert_eq!(
1613 time::timeout(Duration::from_secs(1), monitor.wait_for_failure())
1614 .await
1615 .expect("dormant monitor should still receive terminal status"),
1616 expected
1617 );
1618
1619 let _activation = monitor.activate(&client);
1622 assert_eq!(
1623 time::timeout(Duration::from_millis(100), monitor.wait_for_failure())
1624 .await
1625 .expect("failed monitor should resolve without waiting for a poll"),
1626 expected
1627 );
1628 }
1629
1630 #[tokio::test]
1631 async fn test_monitor_polls_only_while_active() {
1632 let proc = Proc::isolated();
1633 let client = proc.client("client");
1634 let unreachable =
1635 crate::ProcAddr::instance(crate::channel::ChannelAddr::Local(1234), "gone")
1636 .actor_addr("actor");
1637 let monitor = ActorMonitor::spawn_with_timings(
1638 &client,
1639 unreachable.clone(),
1640 Duration::from_millis(100),
1641 Duration::from_millis(10),
1642 Duration::from_millis(50),
1643 None,
1644 false,
1645 );
1646
1647 monitor.inner().handle.post(&client, MonitorTick);
1648
1649 assert!(
1652 time::timeout(Duration::from_millis(300), monitor.wait_for_failure())
1653 .await
1654 .is_err(),
1655 "dormant monitor should not poll"
1656 );
1657
1658 let activation = monitor.activate(&client);
1661 monitor.inner().handle.post(&client, MonitorTick);
1662 time::sleep(Duration::from_millis(20)).await;
1663 drop(activation);
1664 assert!(
1665 time::timeout(Duration::from_millis(300), monitor.wait_for_failure())
1666 .await
1667 .is_err(),
1668 "monitor should not poll after its last activation is released"
1669 );
1670
1671 let _activation = monitor.activate(&client);
1672 assert_eq!(
1673 time::timeout(Duration::from_secs(5), monitor.wait_for_failure())
1674 .await
1675 .expect("reactivated monitor should poll"),
1676 MonitorFailure::StatusRequestTimedOut {
1677 actor_id: unreachable,
1678 timeout_millis: 50,
1679 }
1680 );
1681 }
1682
1683 #[tokio::test]
1684 async fn test_monitor_guard_returns_operation_success() {
1685 let proc = Proc::isolated();
1686 let client = proc.client("client");
1687 let handle = proc.spawn(TestActor);
1688 let monitor = short_monitor(&client, handle.actor_addr().clone());
1689
1690 let result: Result<Result<u64, &'static str>, MonitorFailure> =
1691 monitor.guard(async { Ok(123u64) }).await;
1692
1693 assert_eq!(result, Ok(Ok(123)));
1694 }
1695
1696 #[tokio::test]
1697 async fn test_monitor_guard_returns_monitor_failure() {
1698 let config = hyperactor_config::global::lock();
1699 let _config_guard = config.override_key(
1700 crate::config::PENDING_ACTOR_DELIVERY_TIMEOUT,
1701 Duration::ZERO,
1702 );
1703 let proc = Proc::isolated();
1704 let client = proc.client("client");
1705 let missing = proc.proc_addr().actor_addr("missing");
1706 let monitor = short_monitor(&client, missing.clone());
1707
1708 let result: Result<Result<(), &'static str>, MonitorFailure> =
1709 monitor.guard(std::future::pending()).await;
1710
1711 assert_eq!(result, Err(MonitorFailure::ActorGone { actor_id: missing }));
1712 }
1713
1714 #[tokio::test]
1715 async fn test_actor_ref_is_monitorable() {
1716 let proc = Proc::isolated();
1717 let client = proc.client("client");
1718 let handle = proc.spawn(TestActor);
1719 let actor_ref = ActorRef::<TestActor>::attest(handle.actor_addr().clone());
1720 let monitor = actor_ref.monitor(&client);
1721
1722 assert_eq!(monitor.target(), handle.actor_addr());
1723 assert!(matches!(
1724 wait_for_alive(&monitor).await,
1725 ActorStatus::Idle | ActorStatus::Processing(_, _)
1726 ));
1727 }
1728
1729 #[tokio::test]
1730 async fn test_ports_are_monitorable_by_owner_actor() {
1731 let proc = Proc::isolated();
1732 let client = proc.client("client");
1733 let (port, _rx) = client.open_port::<u64>();
1734 let port_ref = port.bind();
1735 let (once_port, _once_rx) = client.open_once_port::<u64>();
1736 let once_port_ref = once_port.bind();
1737
1738 assert_eq!(port.monitor(&client).target(), client.self_addr());
1739 assert_eq!(port_ref.monitor(&client).target(), client.self_addr());
1740 assert_eq!(once_port_ref.monitor(&client).target(), client.self_addr());
1741 }
1742
1743 #[tokio::test]
1744 async fn test_monitor_recv_returns_message() {
1745 let proc = Proc::isolated();
1746 let client = proc.client("client");
1747 let handle = proc.spawn(TestActor);
1748 let monitor = handle.monitor(&client);
1749 let (port, mut rx) = client.open_port::<u64>();
1750
1751 port.post(&client, 123);
1752
1753 assert!(matches!(monitor.recv(&mut rx).await, Ok(Ok(123))));
1754 }
1755
1756 #[tokio::test]
1757 async fn test_monitor_recv_returns_monitor_failure() {
1758 let proc = Proc::isolated();
1759 let client = proc.client("client");
1760 let handle = proc.spawn(TestActor);
1761 let actor_id = handle.actor_addr().clone();
1762 let monitor = handle.monitor(&client);
1763 let (_port, mut rx) = client.open_port::<u64>();
1764
1765 handle.drain_and_stop("done").unwrap();
1766
1767 match monitor.recv(&mut rx).await {
1768 Err(MonitorFailure::ActorStopped {
1769 actor_id: failed_actor_id,
1770 status,
1771 }) => {
1772 assert_eq!(failed_actor_id, actor_id);
1773 assert_eq!(status, ActorStatus::Stopped("done".to_string()));
1774 }
1775 other => panic!("expected monitor failure, got {other:?}"),
1776 }
1777 }
1778
1779 #[test]
1780 fn test_delivery_progress_advancing_while_behind_does_not_fail() {
1781 let proc = Proc::isolated();
1782 let target = proc.proc_addr().actor_addr("target");
1783 let from = proc.proc_addr().actor_addr("sender");
1784 let delivery = DeliveryMonitor::new(
1785 from.clone(),
1786 Sequencer::new(uuid::Uuid::now_v7()),
1787 EndpointId::Handler,
1788 Duration::from_millis(50),
1789 );
1790 let mut monitor = monitor_actor_for_delivery(target, delivery);
1791 let delivery = monitor
1792 .delivery
1793 .as_mut()
1794 .expect("delivery monitor should be present");
1795 let now = time::Instant::now();
1796 delivery.last_largest_dequeueable = 1;
1797 delivery.last_progress_at = Some(now - Duration::from_millis(100));
1798
1799 assert!(
1800 monitor
1801 .classify_delivery_failure(
1802 DeliveryPollResult {
1803 from,
1804 largest_sent: 10,
1805 response: DeliveryProgressResponse::Progress(DeliveryProgress {
1806 largest_dequeueable_sequence: 2,
1807 }),
1808 },
1809 now
1810 )
1811 .is_none()
1812 );
1813 }
1814
1815 #[test]
1816 fn test_delivery_progress_increasing_sent_only_does_not_reset_stall() {
1817 let proc = Proc::isolated();
1818 let target = proc.proc_addr().actor_addr("target");
1819 let from = proc.proc_addr().actor_addr("sender");
1820 let delivery = DeliveryMonitor::new(
1821 from.clone(),
1822 Sequencer::new(uuid::Uuid::now_v7()),
1823 EndpointId::Handler,
1824 Duration::from_millis(50),
1825 );
1826 let mut monitor = monitor_actor_for_delivery(target.clone(), delivery);
1827 let delivery = monitor
1828 .delivery
1829 .as_mut()
1830 .expect("delivery monitor should be present");
1831 let now = time::Instant::now();
1832 delivery.last_largest_dequeueable = 1;
1833 delivery.last_progress_at = Some(now - Duration::from_millis(100));
1834
1835 assert_eq!(
1836 monitor.classify_delivery_failure(
1837 DeliveryPollResult {
1838 from: from.clone(),
1839 largest_sent: 10,
1840 response: DeliveryProgressResponse::Progress(DeliveryProgress {
1841 largest_dequeueable_sequence: 1,
1842 }),
1843 },
1844 now
1845 ),
1846 Some(MonitorFailure::DeliveryProgressStalled {
1847 actor_id: target,
1848 from,
1849 largest_sent: 10,
1850 largest_dequeueable: 1,
1851 timeout: Duration::from_millis(50),
1852 })
1853 );
1854 }
1855
1856 #[tokio::test]
1857 async fn test_healthy_bound_port_delivery_does_not_stall() {
1858 let config = hyperactor_config::global::lock();
1859 let _g = config.override_key(config::ENABLE_DEST_ACTOR_REORDERING_BUFFER, true);
1860
1861 let proc = Proc::isolated();
1862 let client = proc.client("client");
1863 let (port, mut rx) = client.open_port::<u64>();
1864 let port_ref = port.bind();
1865 let delivery_timeout = Duration::from_millis(50);
1866
1867 let monitor = ActorMonitor::spawn_with_timings(
1868 &client,
1869 port_ref.port_addr().actor_addr(),
1870 Duration::ZERO,
1871 Duration::from_millis(10),
1872 Duration::from_millis(50),
1873 Some(DeliveryMonitor::new(
1874 context::Mailbox::mailbox(&client).actor_addr().clone(),
1875 client.sequencer().clone(),
1876 EndpointId::Port(port_ref.port_addr().port()),
1877 delivery_timeout,
1878 )),
1879 true,
1880 );
1881
1882 port_ref.post(&client, 123);
1883 assert_eq!(rx.recv().await.expect("port delivery should succeed"), 123);
1884
1885 if let Ok(failure) = time::timeout(delivery_timeout * 3, monitor.wait_for_failure()).await {
1886 panic!("healthy port delivery was reported as failed: {failure:?}");
1887 }
1888 }
1889
1890 #[tokio::test]
1891 async fn test_monitor_reports_delivery_progress_stalled() {
1892 let config = hyperactor_config::global::lock();
1893 let _g = config.override_key(config::ENABLE_DEST_ACTOR_REORDERING_BUFFER, true);
1894
1895 let proc = Proc::isolated();
1896 let client = proc.client("client");
1897 let handle = proc.spawn(DeliveryTestActor);
1898 let target = handle.actor_addr().clone();
1899 let _actor_ref: ActorRef<DeliveryTestActor> = handle.bind();
1900 let handler_port = target.port_addr(Port::handler::<DeliveryTestMsg>());
1901
1902 let _ = client.sequencer().assign_seq(&handler_port);
1903 let monitor = short_delivery_monitor(&client, target.clone());
1904 handle.post(&client, DeliveryTestMsg);
1905
1906 assert_eq!(
1907 monitor.wait_for_failure().await,
1908 MonitorFailure::DeliveryProgressStalled {
1909 actor_id: target,
1910 from: context::Mailbox::mailbox(&client).actor_addr().clone(),
1911 largest_sent: 2,
1912 largest_dequeueable: 0,
1913 timeout: Duration::from_millis(50),
1914 }
1915 );
1916 }
1917
1918 #[tokio::test]
1919 async fn test_monitor_skips_delivery_progress_when_reordering_is_disabled() {
1920 let config = hyperactor_config::global::lock();
1921 let _g = config.override_key(config::ENABLE_DEST_ACTOR_REORDERING_BUFFER, false);
1922
1923 let proc = Proc::isolated();
1924 let client = proc.client("client");
1925 let handle = proc.spawn(DeliveryTestActor);
1926 let target = handle.actor_addr().clone();
1927 let _actor_ref: ActorRef<DeliveryTestActor> = handle.bind();
1928 let monitor = short_delivery_monitor(&client, target);
1929
1930 handle.post(&client, DeliveryTestMsg);
1931 let _ = wait_for_alive(&monitor).await;
1932 time::sleep(Duration::from_millis(100)).await;
1933
1934 assert!(matches!(monitor.status(), MonitorStatus::Alive(_)));
1935 }
1936
1937 #[tokio::test]
1938 async fn test_monitor_times_out_when_status_proc_is_unreachable() {
1939 let proc = Proc::isolated();
1940 let client = proc.client("client");
1941 let unreachable =
1942 crate::ProcAddr::instance(crate::channel::ChannelAddr::Local(1234), "gone")
1943 .actor_addr("actor");
1944 let monitor = short_monitor(&client, unreachable.clone());
1945
1946 assert_eq!(
1947 monitor.wait_for_failure().await,
1948 MonitorFailure::StatusRequestTimedOut {
1949 actor_id: unreachable,
1950 timeout_millis: 50,
1951 }
1952 );
1953 }
1954
1955 #[tokio::test]
1956 async fn test_monitor_actor_ignores_tick_while_poll_is_pending() {
1957 let proc = Proc::isolated();
1958 let client = proc.client("client");
1959 let unreachable =
1960 crate::ProcAddr::instance(crate::channel::ChannelAddr::Local(1234), "gone")
1961 .actor_addr("actor");
1962 let monitor = ActorMonitor::spawn_with_timings(
1963 &client,
1964 unreachable,
1965 Duration::ZERO,
1966 Duration::from_millis(10),
1967 Duration::from_secs(5),
1968 None,
1969 true,
1970 );
1971
1972 time::sleep(Duration::from_millis(20)).await;
1973 let (reply, reply_rx) = client.open_once_port();
1974 let handle = &monitor
1975 .inner
1976 .as_ref()
1977 .expect("monitor inner should be present")
1978 .handle;
1979 handle.post(&client, MonitorTick);
1980 handle.post(
1981 &client,
1982 MonitorProbe {
1983 reply: reply.bind(),
1984 },
1985 );
1986
1987 time::timeout(Duration::from_millis(100), reply_rx.recv())
1988 .await
1989 .expect("monitor actor should ignore a tick while polling")
1990 .expect("probe reply should arrive");
1991 }
1992
1993 #[tokio::test]
1994 async fn test_monitor_poll_actor_remains_responsive_and_stops_while_request_is_pending() {
1995 let proc = Proc::isolated();
1996 let client = proc.client("client");
1997 let unreachable =
1998 crate::ProcAddr::instance(crate::channel::ChannelAddr::Local(1234), "gone")
1999 .actor_addr("actor");
2000 let monitor = ActorMonitor::spawn_with_timings(
2001 &client,
2002 unreachable.clone(),
2003 Duration::from_secs(60),
2004 Duration::from_millis(10),
2005 Duration::from_secs(5),
2006 None,
2007 true,
2008 );
2009 let poll = proc.spawn(MonitorPollActor {
2010 target: unreachable,
2011 request_timeout: Duration::from_secs(5),
2012 monitor: monitor
2013 .inner
2014 .as_ref()
2015 .expect("monitor inner should be present")
2016 .handle
2017 .clone(),
2018 state: MonitorPollState::Start { delivery: None },
2019 });
2020
2021 time::sleep(Duration::from_millis(20)).await;
2022 let (reply, reply_rx) = client.open_once_port();
2023 poll.post(
2024 &client,
2025 MonitorProbe {
2026 reply: reply.bind(),
2027 },
2028 );
2029 time::timeout(Duration::from_millis(100), reply_rx.recv())
2030 .await
2031 .expect("monitor poll actor should remain responsive")
2032 .expect("probe reply should arrive");
2033
2034 poll.stop("test complete").unwrap();
2035 time::timeout(Duration::from_millis(100), poll)
2036 .await
2037 .expect("monitor poll actor should stop with a pending request");
2038 }
2039
2040 #[tokio::test]
2041 async fn test_supervised_monitor_reports_synthetic_supervision() {
2042 let proc = Proc::isolated();
2043 let client = proc.client("client");
2044 let target = proc.spawn(TestActor);
2045 let target_id = target.actor_addr().clone();
2046 let (ready, ready_rx) = client.open_once_port();
2047 let (events, mut event_rx) = client.open_port();
2048 let supervisor = proc.spawn(SupervisorActor {
2049 target: target_id.clone(),
2050 ready: Some(ready.bind()),
2051 events: events.bind(),
2052 supervisor: None,
2053 });
2054
2055 let monitor_id = ready_rx.recv().await.unwrap();
2056 target.drain_and_stop("done").unwrap();
2057
2058 let event = event_rx.recv().await.unwrap();
2059 assert_eq!(event.actor_id, monitor_id);
2060 let ActorStatus::Failed(ActorErrorKind::SyntheticSupervision(synthetic)) =
2061 event.actor_status
2062 else {
2063 panic!("expected synthetic supervision event");
2064 };
2065 assert_eq!(synthetic.subject, target_id);
2066 assert!(matches!(
2067 *synthetic.failure,
2068 MonitorFailure::ActorStopped {
2069 status: ActorStatus::Stopped(_),
2070 ..
2071 }
2072 ));
2073 supervisor.drain_and_stop("test complete").unwrap();
2074 }
2075
2076 #[tokio::test]
2077 async fn test_failed_monitor_reports_synthetic_supervision_after_conversion() {
2078 let config = hyperactor_config::global::lock();
2079 let _config_guard = config.override_key(
2080 crate::config::PENDING_ACTOR_DELIVERY_TIMEOUT,
2081 Duration::ZERO,
2082 );
2083 let proc = Proc::isolated();
2084 let client = proc.client("client");
2085 let target_id = proc.proc_addr().actor_addr("missing");
2086 let (ready, ready_rx) = client.open_once_port();
2087 let (events, mut event_rx) = client.open_port();
2088 let supervisor = proc.spawn(ConvertFailedMonitorActor {
2089 target: target_id.clone(),
2090 ready: Some(ready.bind()),
2091 events: events.bind(),
2092 supervisor: None,
2093 });
2094
2095 let monitor_id = ready_rx.recv().await.unwrap();
2096
2097 let event = event_rx.recv().await.unwrap();
2098 assert_eq!(event.actor_id, monitor_id);
2099 let ActorStatus::Failed(ActorErrorKind::SyntheticSupervision(synthetic)) =
2100 event.actor_status
2101 else {
2102 panic!("expected synthetic supervision event");
2103 };
2104 assert_eq!(synthetic.subject, target_id);
2105 assert!(matches!(
2106 *synthetic.failure,
2107 MonitorFailure::ActorGone { .. }
2108 ));
2109 supervisor.drain_and_stop("test complete").unwrap();
2110 }
2111
2112 #[tokio::test]
2113 async fn test_dropping_supervisor_disables_synthetic_supervision() {
2114 let proc = Proc::isolated();
2115 let client = proc.client("client");
2116 let target = proc.spawn(TestActor);
2117 let (ready, ready_rx) = client.open_once_port();
2118 let (events, mut event_rx) = client.open_port();
2119 let supervisor = proc.spawn(DropSupervisorActor {
2120 target: target.actor_addr().clone(),
2121 ready: Some(ready.bind()),
2122 events: events.bind(),
2123 });
2124
2125 let monitor_id = ready_rx.recv().await.unwrap();
2126
2127 let stop_event = event_rx.recv().await.unwrap();
2128 assert_eq!(stop_event.actor_id, monitor_id);
2129 assert!(matches!(stop_event.actor_status, ActorStatus::Stopped(_)));
2130
2131 target.drain_and_stop("done").unwrap();
2132
2133 assert!(
2134 time::timeout(Duration::from_millis(200), event_rx.recv())
2135 .await
2136 .is_err()
2137 );
2138 supervisor.drain_and_stop("test complete").unwrap();
2139 }
2140
2141 #[tokio::test]
2142 async fn test_dropping_supervisor_drops_queued_synthetic_supervision() {
2143 let proc = Proc::isolated();
2144 let client = proc.client("client");
2145 let target = proc.spawn(TestActor);
2146 let (ready, ready_rx) = client.open_once_port();
2147 let (events, mut event_rx) = client.open_port();
2148 let supervisor = proc.spawn(DropQueuedSupervisorActor {
2149 target,
2150 ready: Some(ready.bind()),
2151 events: events.bind(),
2152 });
2153
2154 ready_rx.recv().await.unwrap();
2155
2156 assert!(
2157 time::timeout(Duration::from_millis(200), event_rx.recv())
2158 .await
2159 .is_err()
2160 );
2161 supervisor.drain_and_stop("test complete").unwrap();
2162 }
2163}