1#![allow(dead_code)] use std::any::TypeId;
14use std::borrow::Cow;
15use std::fmt;
16use std::fmt::Debug;
17use std::future::Future;
18use std::future::IntoFuture;
19use std::ops::Deref;
20use std::pin::Pin;
21use std::sync::Arc;
22use std::time::SystemTime;
23
24use async_trait::async_trait;
25use enum_as_inner::EnumAsInner;
26use futures::FutureExt;
27use futures::future::BoxFuture;
28use serde::Deserialize;
29use serde::Serialize;
30use tokio::sync::watch;
31use tokio::task::JoinHandle;
32use typeuri::Named;
33
34use crate as hyperactor; use crate::ActorAddr;
36use crate::ActorEnvironment;
37use crate::ActorRef;
38use crate::Addr;
39#[cfg(test)]
40use crate::Client;
41use crate::Data;
42use crate::EndpointLocation;
43use crate::Message;
44use crate::RemoteMessage;
45use crate::context;
46use crate::endpoint::Endpoint;
47use crate::mailbox::DeliveryFailure;
48use crate::mailbox::DeliveryFailureKind;
49use crate::mailbox::ExpiredDelivery;
50use crate::mailbox::InvalidReference;
51use crate::mailbox::MailboxError;
52use crate::mailbox::MailboxSenderError;
53use crate::mailbox::MessageEnvelope;
54use crate::mailbox::PortHandle;
55use crate::mailbox::TransportFailureReason;
56use crate::mailbox::Undeliverable;
57use crate::mailbox::UndeliverableReason;
58use crate::proc::Context;
59use crate::proc::HandlerPorts;
60use crate::proc::Instance;
61use crate::proc::InstanceCell;
62use crate::proc::Proc;
63use crate::supervision::ActorSupervisionEvent;
64
65pub mod remote;
66
67#[derive(
69 Clone,
70 Copy,
71 Debug,
72 Serialize,
73 Deserialize,
74 PartialEq,
75 Eq,
76 typeuri::Named
77)]
78pub enum StopMode {
79 Stop,
81 DrainAndStop,
83}
84wirevalue::register_type!(StopMode);
85
86#[async_trait]
94pub trait Actor: Sized + Send + 'static {
95 async fn init(&mut self, _this: &Instance<Self>) -> Result<(), anyhow::Error> {
100 Ok(())
102 }
103
104 async fn handle_stop(
113 &mut self,
114 this: &Instance<Self>,
115 mode: StopMode,
116 reason: &str,
117 ) -> Result<(), anyhow::Error> {
118 handle_stop(this, mode, reason)
119 }
120
121 async fn cleanup(
132 &mut self,
133 _this: &Instance<Self>,
134 _err: Option<&ActorError>,
135 ) -> Result<(), anyhow::Error> {
136 Ok(())
138 }
139
140 #[hyperactor::instrument_infallible]
144 fn spawn_server_task<F>(future: F) -> JoinHandle<F::Output>
145 where
146 F: Future + Send + 'static,
147 F::Output: Send + 'static,
148 {
149 tokio::spawn(future)
150 }
151
152 async fn handle_supervision_event(
154 &mut self,
155 _this: &Instance<Self>,
156 event: &ActorSupervisionEvent,
157 ) -> Result<bool, anyhow::Error> {
158 Ok(!event.is_error())
161 }
162
163 async fn handle_delivery_failure_event(
165 &mut self,
166 cx: &Instance<Self>,
167 undeliverable: Undeliverable<MessageEnvelope>,
168 ) -> Result<(), anyhow::Error> {
169 handle_delivery_failure_event(self, cx, undeliverable).await
170 }
171
172 async fn handle_undeliverable_message(
174 &mut self,
175 cx: &Instance<Self>,
176 reason: UndeliverableReason,
177 undeliverable: Undeliverable<MessageEnvelope>,
178 ) -> Result<(), anyhow::Error> {
179 handle_undeliverable_message(cx, reason, undeliverable)
180 }
181
182 async fn handle_invalid_reference(
184 &mut self,
185 cx: &Instance<Self>,
186 invalid: InvalidReference,
187 undeliverable: Undeliverable<MessageEnvelope>,
188 ) -> Result<(), anyhow::Error> {
189 handle_invalid_reference(cx, invalid, undeliverable)
190 }
191
192 async fn handle_expired_delivery(
194 &mut self,
195 cx: &Instance<Self>,
196 expired: ExpiredDelivery,
197 undeliverable: Undeliverable<MessageEnvelope>,
198 ) -> Result<(), anyhow::Error> {
199 handle_expired_delivery(cx, expired, undeliverable)
200 }
201
202 fn display_name(&self) -> Option<String> {
206 None
207 }
208}
209
210pub async fn handle_delivery_failure_event<A: Actor>(
214 actor: &mut A,
215 cx: &Instance<A>,
216 undeliverable: Undeliverable<MessageEnvelope>,
217) -> Result<(), anyhow::Error> {
218 match undeliverable
219 .root_delivery_failure()
220 .map(|failure| failure.kind.clone())
221 {
222 Some(DeliveryFailureKind::InvalidReference(invalid)) => {
223 actor
224 .handle_invalid_reference(cx, invalid, undeliverable)
225 .await
226 }
227 Some(DeliveryFailureKind::Expired(expired)) => {
228 actor
229 .handle_expired_delivery(cx, expired, undeliverable)
230 .await
231 }
232 Some(DeliveryFailureKind::Undeliverable(reason)) => {
233 actor
234 .handle_undeliverable_message(cx, reason, undeliverable)
235 .await
236 }
237 None => anyhow::bail!(undeliverable.into_error()),
238 }
239}
240
241fn delivery_failure_event_target(undeliverable: &Undeliverable<MessageEnvelope>) -> Addr {
242 match undeliverable {
243 Undeliverable::Returned(envelope) => envelope.dest().clone().into(),
244 Undeliverable::Report(report) => match &report.dest {
245 EndpointLocation::Actor(actor) => actor.clone().into(),
246 EndpointLocation::Port(port) => port.clone().into(),
247 EndpointLocation::Local { actor, .. } => actor.clone().into(),
248 },
249 }
250}
251
252pub fn handle_undeliverable_message<A: Actor>(
256 _cx: &Instance<A>,
257 reason: UndeliverableReason,
258 undeliverable: Undeliverable<MessageEnvelope>,
259) -> Result<(), anyhow::Error> {
260 if undeliverable_reason_fails_actor(&reason) {
261 anyhow::bail!(undeliverable.into_error());
262 }
263 Ok(())
264}
265
266fn undeliverable_reason_fails_actor(reason: &UndeliverableReason) -> bool {
267 matches!(
268 reason,
269 UndeliverableReason::Transport(transport)
270 if matches!(
271 &transport.reason,
272 TransportFailureReason::OversizedFrame { .. }
273 )
274 )
275}
276
277pub fn handle_invalid_reference<A: Actor>(
281 _cx: &Instance<A>,
282 _invalid: InvalidReference,
283 undeliverable: Undeliverable<MessageEnvelope>,
284) -> Result<(), anyhow::Error> {
285 anyhow::bail!(undeliverable.into_error())
286}
287
288pub fn handle_expired_delivery<A: Actor>(
292 _cx: &Instance<A>,
293 _expired: ExpiredDelivery,
294 undeliverable: Undeliverable<MessageEnvelope>,
295) -> Result<(), anyhow::Error> {
296 anyhow::bail!(undeliverable.into_error())
297}
298
299pub fn handle_stop<A: Actor>(
303 this: &Instance<A>,
304 mode: StopMode,
305 reason: &str,
306) -> Result<(), anyhow::Error> {
307 this.close();
310 match mode {
311 StopMode::Stop => this.exit(reason).map_err(anyhow::Error::from),
312 StopMode::DrainAndStop => this.exit_after_drain(reason).map_err(anyhow::Error::from),
313 }
314}
315
316#[async_trait]
319impl Actor for () {}
320
321impl Referable for () {}
322
323impl Binds<()> for () {
324 fn bind(_ports: &HandlerPorts<Self>) {
325 }
327}
328
329#[derive(Clone, Copy, Debug, Eq, PartialEq)]
331pub enum MessageStatusReporting {
332 Immediate,
334 Deferred,
336}
337
338#[async_trait]
340pub trait Handler<M>: Actor {
341 fn message_status_reporting() -> MessageStatusReporting {
343 MessageStatusReporting::Immediate
344 }
345
346 async fn handle(&mut self, cx: &Context<Self>, message: M) -> Result<(), anyhow::Error>;
348}
349
350#[async_trait]
360impl<A: Actor> Handler<crate::introspect::IntrospectMessage> for A {
361 async fn handle(
362 &mut self,
363 _cx: &Context<Self>,
364 _message: crate::introspect::IntrospectMessage,
365 ) -> Result<(), anyhow::Error> {
366 unimplemented!("introspect message handler should not be called directly")
367 }
368}
369
370#[cfg(test)]
371#[derive(Debug)]
372enum DeliveryFailurePolicy {
373 InvalidReference,
374 Expired,
375 Undeliverable,
376}
377
378#[cfg(test)]
379fn delivery_failure_policy(message: &Undeliverable<MessageEnvelope>) -> DeliveryFailurePolicy {
380 match message.root_delivery_failure().map(|failure| &failure.kind) {
381 Some(DeliveryFailureKind::InvalidReference(_)) => DeliveryFailurePolicy::InvalidReference,
382 Some(DeliveryFailureKind::Expired(_)) => DeliveryFailurePolicy::Expired,
383 Some(DeliveryFailureKind::Undeliverable(_)) | None => DeliveryFailurePolicy::Undeliverable,
384 }
385}
386
387struct DeliveryFailureLogFields {
388 sender: ActorAddr,
389 dest: EndpointLocation,
390 error: DeliveryFailureLogError,
391}
392
393impl DeliveryFailureLogFields {
394 fn new(message: &Undeliverable<MessageEnvelope>) -> Self {
395 match message {
396 Undeliverable::Returned(envelope) => Self {
397 sender: envelope.sender().clone(),
398 dest: EndpointLocation::Port(envelope.dest().clone()),
399 error: DeliveryFailureLogError::DeliveryFailures(
400 envelope.delivery_failures().to_vec(),
401 ),
402 },
403 Undeliverable::Report(report) => Self {
404 sender: report.sender.clone(),
405 dest: report.dest.clone(),
406 error: DeliveryFailureLogError::DeliveryFailures(report.delivery_failures.clone()),
407 },
408 }
409 }
410}
411
412enum DeliveryFailureLogError {
413 DeliveryFailures(Vec<DeliveryFailure>),
414}
415
416impl fmt::Display for DeliveryFailureLogError {
417 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
418 match self {
419 Self::DeliveryFailures(failures) => {
420 if failures.is_empty() {
421 return write!(f, "<none>");
422 }
423 for (index, failure) in failures.iter().enumerate() {
424 if index > 0 {
425 write!(f, "; ")?;
426 }
427 write!(f, "{}", failure)?;
428 }
429 Ok(())
430 }
431 }
432 }
433}
434
435#[async_trait]
438impl<A: Actor> Handler<Undeliverable<MessageEnvelope>> for A {
439 async fn handle(
440 &mut self,
441 cx: &Context<Self>,
442 message: Undeliverable<MessageEnvelope>,
443 ) -> Result<(), anyhow::Error> {
444 let log_fields = (tracing::enabled!(tracing::Level::DEBUG)
445 || tracing::enabled!(tracing::Level::ERROR))
446 .then(|| DeliveryFailureLogFields::new(&message));
447 let result = self.handle_delivery_failure_event(cx, message).await;
448 match result {
449 Ok(_) => {
450 if let Some(log_fields) = log_fields {
451 tracing::debug!(
452 actor_id = %cx.self_addr(),
453 name = "undeliverable_message_handled",
454 sender = %log_fields.sender,
455 dest = %log_fields.dest,
456 error = %log_fields.error,
457 );
458 }
459 Ok(())
460 }
461 Err(e) => {
462 if let Some(log_fields) = log_fields {
463 tracing::error!(
464 actor_id = %cx.self_addr(),
465 name = "undeliverable_message",
466 sender = %log_fields.sender,
467 dest = %log_fields.dest,
468 error = %log_fields.error,
469 handler_error = %e,
470 );
471 } else {
472 tracing::error!(
473 actor_id = %cx.self_addr(),
474 name = "undeliverable_message",
475 handler_error = %e,
476 );
477 }
478 Err(e)
479 }
480 }
481 }
482}
483
484#[async_trait]
500pub trait RemoteSpawn: Actor + Referable + Binds<Self> {
501 type Params: RemoteMessage;
503
504 async fn new(params: Self::Params, environment: &ActorEnvironment) -> anyhow::Result<Self>;
508
509 fn gspawn_root_bind(
514 proc: &Proc,
515 uid: crate::id::Uid,
516 serialized_params: Data,
517 environment: ActorEnvironment,
518 ) -> Pin<Box<dyn Future<Output = Result<ActorAddr, anyhow::Error>> + Send>> {
519 let proc = proc.clone();
520 Box::pin(async move {
521 let params =
522 bincode::serde::decode_from_slice(&serialized_params, bincode::config::legacy())
523 .map(|(v, _)| v)?;
524 let actor = Self::new(params, &environment).await?;
525 let handle = proc.spawn_bound_with_uid_in_environment(uid, actor, environment)?;
526 Ok(handle.bind::<Self>().into_actor_addr())
538 })
539 }
540
541 fn gspawn_child(
546 proc: &Proc,
547 parent: InstanceCell,
548 uid: crate::id::Uid,
549 serialized_params: Data,
550 environment: ActorEnvironment,
551 ) -> Pin<Box<dyn Future<Output = Result<AnyActorHandle, anyhow::Error>> + Send>> {
552 let proc = proc.clone();
553 Box::pin(async move {
554 let params =
555 bincode::serde::decode_from_slice(&serialized_params, bincode::config::legacy())
556 .map(|(v, _)| v)?;
557 let actor = Self::new(params, &environment).await?;
558 let handle =
559 proc.spawn_child_bound_with_uid_in_environment(parent, uid, actor, environment)?;
560 Ok(handle.into_any())
561 })
562 }
563
564 fn get_type_id() -> TypeId {
566 TypeId::of::<Self>()
567 }
568}
569
570#[async_trait]
573impl<A: Actor + Referable + Binds<Self> + Default> RemoteSpawn for A {
574 type Params = ();
575
576 async fn new(_params: Self::Params, _environment: &ActorEnvironment) -> anyhow::Result<Self> {
577 Ok(Default::default())
578 }
579}
580
581#[derive(Debug)]
584pub struct ActorError {
585 pub actor_id: Box<ActorAddr>,
587 pub kind: Box<ActorErrorKind>,
589}
590
591#[derive(thiserror::Error, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
593pub enum ActorErrorKind {
594 #[error("{0}")]
596 Generic(String),
597
598 #[error("{0} while handling {1}")]
600 ErrorDuringHandlingSupervision(String, Box<ActorSupervisionEvent>),
601
602 #[error("{0}")]
604 UnhandledSupervisionEvent(Box<ActorSupervisionEvent>),
605
606 #[error("{0}")]
608 SyntheticSupervision(Box<crate::monitor::SyntheticSupervision>),
609
610 #[error("actor explicitly aborted due to: {0}")]
612 Aborted(String),
613
614 #[error("signal channel closed")]
617 SignalChannelClosed,
618}
619
620impl ActorErrorKind {
621 pub fn processing(err: anyhow::Error) -> Self {
624 err.downcast::<ActorErrorKind>()
629 .unwrap_or_else(|err| Self::Generic(err.to_string()))
630 }
631
632 pub fn panic(err: anyhow::Error) -> Self {
634 Self::Generic(format!("panic: {}", err))
635 }
636
637 pub fn init(err: anyhow::Error) -> Self {
639 Self::Generic(format!("initialization error: {}", err))
640 }
641
642 pub fn cleanup(err: anyhow::Error) -> Self {
644 Self::Generic(format!("cleanup error: {}", err))
645 }
646
647 pub fn mailbox(err: MailboxError) -> Self {
649 Self::Generic(err.to_string())
650 }
651
652 pub fn mailbox_sender(err: MailboxSenderError) -> Self {
654 Self::Generic(err.to_string())
655 }
656
657 pub fn indeterminate_state() -> Self {
659 Self::Generic("actor is in an indeterminate state".to_string())
660 }
661}
662
663impl ActorError {
664 pub(crate) fn new(actor_id: &ActorAddr, kind: ActorErrorKind) -> Self {
666 Self {
667 actor_id: Box::new(actor_id.clone()),
668 kind: Box::new(kind),
669 }
670 }
671}
672
673impl fmt::Display for ActorError {
674 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
675 write!(f, "serving {}: ", self.actor_id)?;
676 fmt::Display::fmt(&self.kind, f)
677 }
678}
679
680impl std::error::Error for ActorError {
681 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
682 self.kind.source()
683 }
684}
685
686impl From<MailboxError> for ActorError {
687 fn from(inner: MailboxError) -> Self {
688 Self {
689 actor_id: Box::new(inner.actor_addr().clone()),
690 kind: Box::new(ActorErrorKind::mailbox(inner)),
691 }
692 }
693}
694
695impl From<MailboxSenderError> for ActorError {
696 fn from(inner: MailboxSenderError) -> Self {
697 Self {
698 actor_id: Box::new(inner.location().actor_addr()),
699 kind: Box::new(ActorErrorKind::mailbox_sender(inner)),
700 }
701 }
702}
703
704#[derive(Clone, Debug, Serialize, Deserialize, typeuri::Named)]
711pub enum Signal {
712 DrainAndStop(String),
714
715 Stop(String),
717
718 ExitRequested(String),
720
721 ChildStopped(crate::id::Uid),
723
724 Kill(String),
728}
729
730impl fmt::Display for Signal {
731 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
732 match self {
733 Signal::DrainAndStop(reason) => write!(f, "DrainAndStop({})", reason),
734 Signal::Stop(reason) => write!(f, "Stop({})", reason),
735 Signal::ExitRequested(reason) => write!(f, "ExitRequested({})", reason),
736 Signal::ChildStopped(uid) => write!(f, "ChildStopped({})", uid),
737 Signal::Kill(reason) => write!(f, "Kill({})", reason),
738 }
739 }
740}
741
742#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
747pub struct HandlerInfo {
748 pub typename: Cow<'static, str>,
750 pub arm: Option<Cow<'static, str>>,
752}
753
754impl HandlerInfo {
755 pub fn from_static(typename: &'static str, arm: Option<&'static str>) -> Self {
757 Self {
758 typename: Cow::Borrowed(typename),
759 arm: arm.map(Cow::Borrowed),
760 }
761 }
762
763 pub fn from_owned(typename: String, arm: Option<String>) -> Self {
765 Self {
766 typename: Cow::Owned(typename),
767 arm: arm.map(Cow::Owned),
768 }
769 }
770}
771
772impl fmt::Display for HandlerInfo {
773 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
774 match &self.arm {
775 Some(arm) => write!(f, "{}.{}", self.typename, arm),
776 None => write!(f, "{}", self.typename),
777 }
778 }
779}
780
781#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
783pub enum ActorStoppingReason {
784 Requested,
786 Zombie(String),
789}
790
791impl fmt::Display for ActorStoppingReason {
792 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
793 match self {
794 Self::Requested => write!(f, "requested"),
795 Self::Zombie(reason) => write!(f, "zombie: {}", reason),
796 }
797 }
798}
799
800#[derive(
802 Debug,
803 Serialize,
804 Deserialize,
805 PartialEq,
806 Eq,
807 Clone,
808 typeuri::Named,
809 EnumAsInner
810)]
811pub enum ActorStatus {
812 Unknown,
814 Created,
816 Initializing,
818 Client,
821 Idle,
823 Processing(SystemTime, Option<HandlerInfo>),
826 Stopping(ActorStoppingReason),
828 Stopped(String),
831 Failed(ActorErrorKind),
833}
834
835impl ActorStatus {
836 pub fn is_terminal(&self) -> bool {
838 self.is_stopped() || self.is_failed()
839 }
840
841 pub fn stopping() -> Self {
843 Self::Stopping(ActorStoppingReason::Requested)
844 }
845
846 pub fn zombie(reason: impl Into<String>) -> Self {
848 Self::Stopping(ActorStoppingReason::Zombie(reason.into()))
849 }
850
851 pub fn is_zombie(&self) -> bool {
853 matches!(self, Self::Stopping(ActorStoppingReason::Zombie(_)))
854 }
855
856 pub fn generic_failure(message: impl Into<String>) -> Self {
858 Self::Failed(ActorErrorKind::Generic(message.into()))
859 }
860
861 fn span_string(&self) -> &'static str {
862 self.arm().unwrap_or_default()
863 }
864}
865
866impl fmt::Display for ActorStatus {
867 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
868 match self {
869 Self::Unknown => write!(f, "unknown"),
870 Self::Created => write!(f, "created"),
871 Self::Initializing => write!(f, "initializing"),
872 Self::Client => write!(f, "client"),
873 Self::Idle => write!(f, "idle"),
874 Self::Processing(instant, None) => {
875 write!(
876 f,
877 "processing for {}ms",
878 std::time::SystemTime::now()
879 .duration_since(*instant)
880 .unwrap_or_default()
881 .as_millis()
882 )
883 }
884 Self::Processing(instant, Some(handler_info)) => {
885 write!(
886 f,
887 "{}: processing for {}ms",
888 handler_info,
889 std::time::SystemTime::now()
890 .duration_since(*instant)
891 .unwrap_or_default()
892 .as_millis()
893 )
894 }
895 Self::Stopping(ActorStoppingReason::Requested) => write!(f, "stopping"),
896 Self::Stopping(ActorStoppingReason::Zombie(reason)) => write!(f, "zombie: {}", reason),
897 Self::Stopped(reason) => write!(f, "stopped: {}", reason),
898 Self::Failed(err) => write!(f, "failed: {}", err),
899 }
900 }
901}
902
903pub struct ActorHandle<A: Actor> {
912 cell: InstanceCell,
913 ports: Arc<HandlerPorts<A>>,
914}
915
916impl<A: Actor> ActorHandle<A> {
918 pub(crate) fn new(cell: InstanceCell, ports: Arc<HandlerPorts<A>>) -> Self {
919 Self { cell, ports }
920 }
921
922 pub(crate) fn cell(&self) -> &InstanceCell {
925 &self.cell
926 }
927
928 pub fn actor_addr(&self) -> &ActorAddr {
930 self.cell.actor_addr()
931 }
932
933 pub fn drain_and_stop(&self, reason: &str) -> Result<(), ActorError> {
935 tracing::info!("ActorHandle::drain_and_stop called: {}", self.actor_addr());
936 self.cell.signal(Signal::DrainAndStop(reason.to_string()))
937 }
938
939 pub fn stop(&self, reason: &str) -> Result<(), ActorError> {
942 tracing::info!("actor handle stop called: {}", self.actor_addr());
943 self.cell.signal(Signal::Stop(reason.to_string()))
944 }
945
946 pub fn kill(&self, reason: &str) -> Result<(), ActorError> {
948 tracing::info!("actor handle kill called: {}", self.actor_addr());
949 self.cell.signal(Signal::Kill(reason.to_string()))
950 }
951
952 pub fn status(&self) -> watch::Receiver<ActorStatus> {
954 self.cell.status().clone()
955 }
956
957 pub fn port<M: Message>(&self) -> PortHandle<M>
959 where
960 A: Handler<M>,
961 {
962 self.ports.get()
963 }
964
965 pub fn try_post<C, M>(&self, cx: &C, message: M) -> Result<(), MailboxSenderError>
971 where
972 C: context::Actor,
973 M: Message,
974 A: Handler<M>,
975 {
976 self.ports.get::<M>().try_post(cx, message)
977 }
978
979 pub fn bind<R: Binds<A>>(&self) -> ActorRef<R> {
982 self.cell.bind(self.ports.as_ref())
983 }
984
985 pub fn into_any(self) -> AnyActorHandle {
987 AnyActorHandle { cell: self.cell }
988 }
989
990 pub fn into_guard(self) -> ActorGuard<A> {
995 ActorGuard { handle: Some(self) }
996 }
997}
998
999pub struct ActorGuard<A: Actor> {
1001 handle: Option<ActorHandle<A>>,
1002}
1003
1004impl<A: Actor> ActorGuard<A> {
1005 pub fn into_inner(mut self) -> ActorHandle<A> {
1007 self.handle
1008 .take()
1009 .expect("actor guard must contain a handle")
1010 }
1011}
1012
1013impl<A: Actor> Deref for ActorGuard<A> {
1014 type Target = ActorHandle<A>;
1015
1016 fn deref(&self) -> &Self::Target {
1017 self.handle
1018 .as_ref()
1019 .expect("actor guard must contain a handle")
1020 }
1021}
1022
1023impl<A: Actor> Drop for ActorGuard<A> {
1024 fn drop(&mut self) {
1025 if let Some(handle) = self.handle.take()
1026 && let Err(err) = handle.stop("actor guard dropped")
1027 {
1028 tracing::debug!(
1029 actor_id = %handle.actor_addr(),
1030 "actor guard failed to stop actor: {}",
1031 err
1032 );
1033 }
1034 }
1035}
1036
1037impl<A: Actor> Debug for ActorGuard<A> {
1038 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
1039 f.debug_struct("ActorGuard")
1040 .field(
1041 "actor_id",
1042 &self.handle.as_ref().map(|handle| handle.actor_addr()),
1043 )
1044 .finish()
1045 }
1046}
1047
1048pub struct AnyActorHandle {
1054 cell: InstanceCell,
1055}
1056
1057impl AnyActorHandle {
1058 pub fn actor_id(&self) -> &ActorAddr {
1060 self.cell.actor_addr()
1061 }
1062
1063 pub fn drain_and_stop(&self, reason: &str) -> Result<(), ActorError> {
1065 self.cell.signal(Signal::DrainAndStop(reason.to_string()))
1066 }
1067
1068 pub fn stop(&self, reason: &str) -> Result<(), ActorError> {
1070 self.cell.signal(Signal::Stop(reason.to_string()))
1071 }
1072
1073 pub fn kill(&self, reason: &str) -> Result<(), ActorError> {
1075 self.cell.signal(Signal::Kill(reason.to_string()))
1076 }
1077
1078 pub fn status(&self) -> watch::Receiver<ActorStatus> {
1080 self.cell.status().clone()
1081 }
1082
1083 pub fn downcast<A: Actor>(&self) -> Option<ActorHandle<A>> {
1085 self.cell.downcast_handle()
1086 }
1087
1088 pub fn into_guard(self) -> AnyActorGuard {
1093 AnyActorGuard { handle: Some(self) }
1094 }
1095}
1096
1097pub struct AnyActorGuard {
1099 handle: Option<AnyActorHandle>,
1100}
1101
1102impl AnyActorGuard {
1103 pub fn into_inner(mut self) -> AnyActorHandle {
1105 self.handle
1106 .take()
1107 .expect("actor guard must contain a handle")
1108 }
1109}
1110
1111impl Deref for AnyActorGuard {
1112 type Target = AnyActorHandle;
1113
1114 fn deref(&self) -> &Self::Target {
1115 self.handle
1116 .as_ref()
1117 .expect("actor guard must contain a handle")
1118 }
1119}
1120
1121impl Drop for AnyActorGuard {
1122 fn drop(&mut self) {
1123 if let Some(handle) = self.handle.take()
1124 && let Err(err) = handle.stop("actor guard dropped")
1125 {
1126 tracing::debug!(
1127 actor_id = %handle.actor_id(),
1128 "actor guard failed to stop actor: {}",
1129 err
1130 );
1131 }
1132 }
1133}
1134
1135impl Debug for AnyActorGuard {
1136 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
1137 f.debug_struct("AnyActorGuard")
1138 .field(
1139 "actor_id",
1140 &self.handle.as_ref().map(|handle| handle.actor_id()),
1141 )
1142 .finish()
1143 }
1144}
1145
1146impl IntoFuture for AnyActorHandle {
1150 type Output = ActorStatus;
1151 type IntoFuture = BoxFuture<'static, Self::Output>;
1152
1153 fn into_future(self) -> Self::IntoFuture {
1154 let future = async move {
1155 let mut status_receiver = self.cell.status().clone();
1156 let result = status_receiver.wait_for(ActorStatus::is_terminal).await;
1157 match result {
1158 Err(_) => ActorStatus::Unknown,
1159 Ok(status) => status.clone(),
1160 }
1161 };
1162
1163 future.boxed()
1164 }
1165}
1166
1167impl Debug for AnyActorHandle {
1168 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
1169 f.debug_struct("AnyActorHandle")
1170 .field("cell", &"..")
1171 .finish()
1172 }
1173}
1174
1175impl Clone for AnyActorHandle {
1176 fn clone(&self) -> Self {
1177 Self {
1178 cell: self.cell.clone(),
1179 }
1180 }
1181}
1182
1183impl<A: Actor> IntoFuture for ActorHandle<A> {
1187 type Output = ActorStatus;
1188 type IntoFuture = BoxFuture<'static, Self::Output>;
1189
1190 fn into_future(self) -> Self::IntoFuture {
1191 let future = async move {
1192 let mut status_receiver = self.cell.status().clone();
1193 let result = status_receiver.wait_for(ActorStatus::is_terminal).await;
1194 match result {
1195 Err(_) => ActorStatus::Unknown,
1196 Ok(status) => status.clone(),
1197 }
1198 };
1199
1200 future.boxed()
1201 }
1202}
1203
1204impl<A: Actor> Debug for ActorHandle<A> {
1205 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
1206 f.debug_struct("ActorHandle").field("cell", &"..").finish()
1207 }
1208}
1209
1210impl<A, M> Endpoint<M> for &ActorHandle<A>
1211where
1212 A: Actor + Handler<M>,
1213 M: Message,
1214{
1215 fn endpoint_location(&self) -> EndpointLocation {
1216 EndpointLocation::Actor(self.actor_addr().clone())
1217 }
1218
1219 fn post<C>(self, cx: &C, message: M)
1220 where
1221 C: context::Actor,
1222 {
1223 Endpoint::post(&self.ports.get(), cx, message)
1224 }
1225}
1226
1227impl<A: Actor> Clone for ActorHandle<A> {
1228 fn clone(&self) -> Self {
1229 Self {
1230 cell: self.cell.clone(),
1231 ports: self.ports.clone(),
1232 }
1233 }
1234}
1235
1236pub trait Referable: Named {}
1251
1252pub trait Binds<A: Actor>: Referable {
1255 fn bind(ports: &HandlerPorts<A>);
1257}
1258
1259pub trait RemoteHandles<M: RemoteMessage>: Referable {}
1262
1263#[macro_export]
1294macro_rules! assert_behaves {
1295 ($ty:ty as $behavior:ty) => {
1296 const _: fn() = || {
1297 fn check<B: hyperactor::actor::Binds<$ty>>() {}
1298 check::<$behavior>();
1299 };
1300 };
1301}
1302
1303#[cfg(test)]
1304mod tests {
1305 use std::assert_matches;
1306 use std::sync::Mutex;
1307 use std::time::Duration;
1308
1309 use hyperactor_config::Flattrs;
1310 use rand::seq::SliceRandom;
1311 use timed_test::async_timed_test;
1312 use tokio::sync::mpsc;
1313 use tokio::time::timeout;
1314
1315 use super::*;
1316 use crate as hyperactor;
1317 use crate::Actor;
1318 use crate::ActorRef;
1319 use crate::Addr;
1320 use crate::EndpointLocation;
1321 use crate::OncePortHandle;
1322 use crate::PortAddr;
1323 use crate::PortRef;
1324 use crate::config;
1325 use crate::context::Mailbox as _;
1326 use crate::introspect::IntrospectMessage;
1327 use crate::introspect::IntrospectResult;
1328 use crate::introspect::IntrospectView;
1329 use crate::mailbox::BoxableMailboxSender as _;
1330 use crate::mailbox::DeliveryFailure;
1331 use crate::mailbox::DeliveryFailureReport;
1332 use crate::mailbox::ExpiredDelivery;
1333 use crate::mailbox::InvalidReference;
1334 use crate::mailbox::InvalidReferenceReason;
1335 use crate::mailbox::MailboxSender;
1336 use crate::mailbox::PortGone;
1337 use crate::mailbox::PortLocation;
1338 use crate::mailbox::TransportFailure;
1339 use crate::mailbox::TransportFailureReason;
1340 use crate::mailbox::UndeliverableReason;
1341 use crate::mailbox::monitored_return_handle;
1342 use crate::ordering::SEQ_INFO;
1343 use crate::ordering::SeqInfo;
1344 use crate::port::Port;
1345 use crate::testing::ids::test_proc_id;
1346 use crate::testing::pingpong::PingPongActor;
1347 use crate::testing::pingpong::PingPongMessage;
1348 use crate::testing::proc_supervison::ProcSupervisionCoordinator; #[derive(Debug)]
1351 struct EchoActor(PortRef<u64>);
1352
1353 #[async_trait]
1354 impl Actor for EchoActor {}
1355
1356 #[async_trait]
1357 impl Handler<u64> for EchoActor {
1358 async fn handle(&mut self, cx: &Context<Self>, message: u64) -> Result<(), anyhow::Error> {
1359 let Self(port) = self;
1360 port.post(cx, message);
1361 Ok(())
1362 }
1363 }
1364
1365 #[derive(Debug)]
1366 struct DeliveryPolicyActor(PortRef<()>);
1367
1368 #[async_trait]
1369 impl Actor for DeliveryPolicyActor {}
1370
1371 #[async_trait]
1372 impl Handler<()> for DeliveryPolicyActor {
1373 async fn handle(&mut self, cx: &Context<Self>, _message: ()) -> Result<(), anyhow::Error> {
1374 self.0.post(cx, ());
1375 Ok(())
1376 }
1377 }
1378
1379 fn delivery_policy_envelope(
1380 sender: &ActorAddr,
1381 dest: PortAddr,
1382 failure: DeliveryFailure,
1383 ) -> MessageEnvelope {
1384 let mut envelope =
1385 MessageEnvelope::serialize(sender.clone(), dest, &(), Flattrs::new()).unwrap();
1386 envelope.push_delivery_failure(failure);
1387 envelope
1388 }
1389
1390 fn delivery_policy_report(
1391 sender: ActorAddr,
1392 dest: PortAddr,
1393 failure: DeliveryFailure,
1394 ) -> DeliveryFailureReport {
1395 DeliveryFailureReport::new(
1396 sender,
1397 EndpointLocation::Port(dest),
1398 Some("()".to_string()),
1399 failure,
1400 )
1401 }
1402
1403 async fn assert_delivery_policy_actor_remains_live(
1404 make_undeliverable: impl FnOnce(&ActorAddr, PortAddr) -> Undeliverable<MessageEnvelope>,
1405 ) {
1406 let proc = Proc::isolated();
1407 let client = proc.client("client");
1408 let (sync_port, mut sync_rx) = client.open_port::<()>();
1409 let actor = DeliveryPolicyActor(sync_port.bind());
1410 let handle = proc.spawn_with_label("delivery_policy", actor);
1411 let dest = handle.actor_addr().port_addr(Port::from(1234));
1412
1413 handle.post(
1414 &client,
1415 make_undeliverable(handle.actor_addr(), dest.clone()),
1416 );
1417 handle.post(&client, ());
1418
1419 tokio::time::timeout(Duration::from_secs(1), sync_rx.recv())
1420 .await
1421 .expect("actor should remain live")
1422 .expect("sync port should receive response");
1423 handle.drain_and_stop("test").unwrap();
1424 assert_matches!(handle.await, ActorStatus::Stopped(reason) if reason == "test");
1425 }
1426
1427 async fn assert_delivery_policy_actor_fails(
1428 make_undeliverable: impl FnOnce(&ActorAddr, PortAddr) -> Undeliverable<MessageEnvelope>,
1429 ) {
1430 let proc = Proc::isolated();
1431 let (_reported, _coordinator) = ProcSupervisionCoordinator::set(&proc).await.unwrap();
1432 let client = proc.client("client");
1433 let (sync_port, _sync_rx) = client.open_port::<()>();
1434 let actor = DeliveryPolicyActor(sync_port.bind());
1435 let handle = proc.spawn_with_label("delivery_policy", actor);
1436 let dest = handle.actor_addr().port_addr(Port::from(1234));
1437
1438 handle.post(
1439 &client,
1440 make_undeliverable(handle.actor_addr(), dest.clone()),
1441 );
1442
1443 assert_matches!(handle.await, ActorStatus::Failed(_));
1444 }
1445
1446 #[tokio::test]
1447 async fn test_default_transport_undeliverable_policy_does_not_fail_actor() {
1448 let target = Addr::Proc(test_proc_id("target"));
1449 let failure = DeliveryFailure::new(UndeliverableReason::Transport(TransportFailure::new(
1450 target,
1451 TransportFailureReason::NoRoute,
1452 )));
1453 assert_delivery_policy_actor_remains_live(|sender, dest| {
1454 Undeliverable::Returned(delivery_policy_envelope(sender, dest, failure))
1455 })
1456 .await;
1457 }
1458
1459 #[tokio::test]
1460 async fn test_default_transport_report_policy_does_not_fail_actor() {
1461 let target = Addr::Proc(test_proc_id("target"));
1462 let failure = DeliveryFailure::new(UndeliverableReason::Transport(TransportFailure::new(
1463 target,
1464 TransportFailureReason::NoRoute,
1465 )));
1466 assert_delivery_policy_actor_remains_live(|sender, dest| {
1467 Undeliverable::Report(delivery_policy_report(sender.clone(), dest, failure))
1468 })
1469 .await;
1470 }
1471
1472 #[tokio::test]
1473 async fn test_default_oversized_frame_transport_policy_fails_actor() {
1474 let target = Addr::Proc(test_proc_id("target"));
1475 let failure = DeliveryFailure::new(UndeliverableReason::Transport(TransportFailure::new(
1476 target,
1477 TransportFailureReason::OversizedFrame {
1478 len: 55001392,
1479 max: 50000000,
1480 },
1481 )));
1482 assert_delivery_policy_actor_fails(|sender, dest| {
1483 Undeliverable::Returned(delivery_policy_envelope(sender, dest, failure))
1484 })
1485 .await;
1486 }
1487
1488 #[tokio::test]
1489 async fn test_default_oversized_frame_report_policy_fails_actor() {
1490 let target = Addr::Proc(test_proc_id("target"));
1491 let failure = DeliveryFailure::new(UndeliverableReason::Transport(TransportFailure::new(
1492 target,
1493 TransportFailureReason::OversizedFrame {
1494 len: 55001392,
1495 max: 50000000,
1496 },
1497 )));
1498 assert_delivery_policy_actor_fails(|sender, dest| {
1499 Undeliverable::Report(delivery_policy_report(sender.clone(), dest, failure))
1500 })
1501 .await;
1502 }
1503
1504 #[tokio::test]
1505 async fn test_default_port_gone_policy_does_not_fail_actor() {
1506 let port = test_proc_id("target")
1507 .actor_addr("actor")
1508 .port_addr(Port::from(1234));
1509 let failure = DeliveryFailure::new(UndeliverableReason::PortGone(PortGone::new(
1510 port,
1511 Some("()".to_string()),
1512 )));
1513 assert_delivery_policy_actor_remains_live(|sender, dest| {
1514 Undeliverable::Returned(delivery_policy_envelope(sender, dest, failure))
1515 })
1516 .await;
1517 }
1518
1519 #[tokio::test]
1520 async fn test_default_invalid_reference_policy_fails_actor() {
1521 let target = test_proc_id("target").actor_addr("actor");
1522 let failure = DeliveryFailure::new(InvalidReference::new(
1523 target,
1524 InvalidReferenceReason::ActorNotExist,
1525 ));
1526 assert_delivery_policy_actor_fails(|sender, dest| {
1527 Undeliverable::Returned(delivery_policy_envelope(sender, dest, failure))
1528 })
1529 .await;
1530 }
1531
1532 #[tokio::test]
1533 async fn test_default_invalid_reference_policy_allows_return_handle_sender_mismatch() {
1534 let target = test_proc_id("target").actor_addr("actor");
1535 let failure = DeliveryFailure::new(InvalidReference::new(
1536 target,
1537 InvalidReferenceReason::ActorNotExist,
1538 ));
1539 assert_delivery_policy_actor_fails(|_actor, dest| {
1540 Undeliverable::Returned(delivery_policy_envelope(
1541 &test_proc_id("sender").actor_addr("actor"),
1542 dest,
1543 failure,
1544 ))
1545 })
1546 .await;
1547 }
1548
1549 #[tokio::test]
1550 async fn test_default_expired_delivery_policy_fails_actor() {
1551 let port = test_proc_id("target")
1552 .actor_addr("actor")
1553 .port_addr(Port::from(1234));
1554 let failure = DeliveryFailure::new(ExpiredDelivery::new(port));
1555 assert_delivery_policy_actor_fails(|sender, dest| {
1556 Undeliverable::Returned(delivery_policy_envelope(sender, dest, failure))
1557 })
1558 .await;
1559 }
1560
1561 #[test]
1562 fn test_delivery_failure_policy_ignores_attrs() {
1563 hyperactor_config::attrs::declare_attrs! {
1564 attr TEST_DELIVERY_FAILURE_ATTR: String;
1565 }
1566
1567 let sender = test_proc_id("sender").actor_addr("actor");
1568 let dest = sender.port_addr(Port::from(1234));
1569 let mut attrs = Flattrs::new();
1570 attrs.set(TEST_DELIVERY_FAILURE_ATTR, "context".to_string());
1571
1572 let transport = delivery_policy_envelope(
1573 &sender,
1574 dest.clone(),
1575 DeliveryFailure::with_attrs(
1576 UndeliverableReason::Transport(TransportFailure::new(
1577 dest.clone(),
1578 TransportFailureReason::NoRoute,
1579 )),
1580 attrs.clone(),
1581 ),
1582 );
1583 assert_matches!(
1584 delivery_failure_policy(&Undeliverable::Returned(transport)),
1585 DeliveryFailurePolicy::Undeliverable
1586 );
1587
1588 let invalid_reference = delivery_policy_envelope(
1589 &sender,
1590 dest.clone(),
1591 DeliveryFailure::with_attrs(
1592 InvalidReference::new(dest.clone(), InvalidReferenceReason::PortNeverAllocated),
1593 attrs.clone(),
1594 ),
1595 );
1596 assert_matches!(
1597 delivery_failure_policy(&Undeliverable::Returned(invalid_reference)),
1598 DeliveryFailurePolicy::InvalidReference
1599 );
1600
1601 let expired = delivery_policy_envelope(
1602 &sender,
1603 dest.clone(),
1604 DeliveryFailure::with_attrs(ExpiredDelivery::new(dest), attrs),
1605 );
1606 assert_matches!(
1607 delivery_failure_policy(&Undeliverable::Returned(expired)),
1608 DeliveryFailurePolicy::Expired
1609 );
1610 }
1611
1612 #[test]
1613 fn test_delivery_failure_policy_uses_report_root_failure() {
1614 let sender = test_proc_id("sender").actor_addr("actor");
1615 let dest = sender.port_addr(Port::from(1234));
1616 let report = DeliveryFailureReport::new(
1617 sender,
1618 EndpointLocation::Port(dest.clone()),
1619 Some("()".to_string()),
1620 DeliveryFailure::new(ExpiredDelivery::new(dest)),
1621 );
1622
1623 assert_matches!(
1624 delivery_failure_policy(&Undeliverable::Report(report)),
1625 DeliveryFailurePolicy::Expired
1626 );
1627 }
1628
1629 #[tokio::test]
1630 async fn test_server_basic() {
1631 let proc = Proc::isolated();
1632 let client = proc.client("client");
1633 let (tx, mut rx) = client.open_port();
1634 let actor = EchoActor(tx.bind());
1635 let handle = proc.spawn(actor);
1636 handle.post(&client, 123u64);
1637 handle.drain_and_stop("test").unwrap();
1638 handle.await;
1639
1640 assert_eq!(rx.drain(), vec![123u64]);
1641 }
1642
1643 #[tokio::test]
1644 async fn test_actor_handle_guard_stops_actor_on_drop() {
1645 let proc = Proc::isolated();
1646 let handle = proc.spawn(());
1647 let mut status = handle.status();
1648
1649 {
1650 let _guard = handle.into_guard();
1651 }
1652
1653 let stopped = timeout(
1654 Duration::from_secs(5),
1655 status.wait_for(|status| {
1656 matches!(status, ActorStatus::Stopped(reason) if reason == "actor guard dropped")
1657 }),
1658 )
1659 .await
1660 .unwrap()
1661 .unwrap()
1662 .clone();
1663
1664 match stopped {
1665 ActorStatus::Stopped(reason) => assert_eq!(reason, "actor guard dropped"),
1666 status => panic!("actor guard should stop actor, got {status}"),
1667 }
1668 }
1669
1670 #[tokio::test]
1671 async fn test_actor_handle_guard_into_inner_disarms_stop() {
1672 let proc = Proc::isolated();
1673 let handle = proc.spawn(());
1674 let mut status = handle.status();
1675
1676 let guard = handle.into_guard();
1677 let _ = guard.status();
1678 let guarded_handle = guard.into_inner();
1679 let result = timeout(
1680 Duration::from_millis(100),
1681 status.wait_for(ActorStatus::is_terminal),
1682 )
1683 .await;
1684 assert!(result.is_err());
1685
1686 guarded_handle.drain_and_stop("test").unwrap();
1687 guarded_handle.await;
1688 }
1689
1690 #[tokio::test]
1691 async fn test_ping_pong() {
1692 let proc = Proc::isolated();
1693 let client = proc.client("client");
1694 let (undeliverable_msg_tx, _) = client.open_port();
1695
1696 let ping_actor = PingPongActor::new(Some(undeliverable_msg_tx.bind()), None, None);
1697 let pong_actor = PingPongActor::new(Some(undeliverable_msg_tx.bind()), None, None);
1698 let ping_handle = proc.spawn_with_label::<PingPongActor>("ping", ping_actor);
1699 let pong_handle = proc.spawn_with_label::<PingPongActor>("pong", pong_actor);
1700
1701 let (local_port, local_receiver) = client.open_once_port();
1702
1703 ping_handle.post(
1704 &client,
1705 PingPongMessage(10, pong_handle.bind(), local_port.bind()),
1706 );
1707
1708 assert!(local_receiver.recv().await.unwrap());
1709 }
1710
1711 #[tokio::test]
1712 async fn test_ping_pong_on_handler_error() {
1713 let proc = Proc::isolated();
1714 let client = proc.client("client");
1715 let (undeliverable_msg_tx, _) = client.open_port();
1716
1717 let (_reported, _coordinator) = ProcSupervisionCoordinator::set(&proc).await.unwrap();
1720
1721 let error_ttl = 66;
1722
1723 let ping_actor =
1724 PingPongActor::new(Some(undeliverable_msg_tx.bind()), Some(error_ttl), None);
1725 let pong_actor =
1726 PingPongActor::new(Some(undeliverable_msg_tx.bind()), Some(error_ttl), None);
1727 let ping_handle = proc.spawn_with_label::<PingPongActor>("ping", ping_actor);
1728 let pong_handle = proc.spawn_with_label::<PingPongActor>("pong", pong_actor);
1729
1730 let (local_port, local_receiver) = client.open_once_port();
1731
1732 ping_handle.post(
1733 &client,
1734 PingPongMessage(
1735 error_ttl + 1, pong_handle.bind(),
1737 local_port.bind(),
1738 ),
1739 );
1740
1741 let res: Result<Result<bool, MailboxError>, tokio::time::error::Elapsed> =
1743 timeout(Duration::from_secs(5), local_receiver.recv()).await;
1744 assert!(res.is_err());
1745 }
1746
1747 #[derive(Debug)]
1748 struct InitActor(bool);
1749
1750 #[async_trait]
1751 impl Actor for InitActor {
1752 async fn init(&mut self, _this: &Instance<Self>) -> Result<(), anyhow::Error> {
1753 self.0 = true;
1754 Ok(())
1755 }
1756 }
1757
1758 #[async_trait]
1759 impl Handler<OncePortHandle<bool>> for InitActor {
1760 async fn handle(
1761 &mut self,
1762 cx: &Context<Self>,
1763 port: OncePortHandle<bool>,
1764 ) -> Result<(), anyhow::Error> {
1765 port.post(cx, self.0);
1766 Ok(())
1767 }
1768 }
1769
1770 #[tokio::test]
1771 async fn test_init() {
1772 let proc = Proc::isolated();
1773 let actor = InitActor(false);
1774 let handle = proc.spawn(actor);
1775 let client = proc.client("client");
1776
1777 let (port, receiver) = client.open_once_port();
1778 handle.post(&client, port);
1779 assert!(receiver.recv().await.unwrap());
1780
1781 handle.drain_and_stop("test").unwrap();
1782 handle.await;
1783 }
1784
1785 type MultiValues = Arc<Mutex<(u64, String)>>;
1786
1787 struct MultiValuesTest {
1788 proc: Proc,
1789 values: MultiValues,
1790 handle: ActorHandle<MultiActor>,
1791 client: Client,
1792 }
1793
1794 impl MultiValuesTest {
1795 async fn new() -> Self {
1796 let proc = Proc::isolated();
1797 let values: MultiValues = Arc::new(Mutex::new((0, "".to_string())));
1798 let actor = MultiActor(values.clone());
1799 let handle = proc.spawn(actor);
1800 let client = proc.client("client");
1801 Self {
1802 proc,
1803 values,
1804 handle,
1805 client,
1806 }
1807 }
1808
1809 fn send<M>(&self, message: M)
1810 where
1811 M: RemoteMessage,
1812 MultiActor: Handler<M>,
1813 {
1814 self.handle.post(&self.client, message)
1815 }
1816
1817 async fn sync(&self) {
1818 let (port, done) = self.client.open_once_port::<bool>();
1819 self.handle.post(&self.client, port);
1820 assert!(done.recv().await.unwrap());
1821 }
1822
1823 fn get_values(&self) -> (u64, String) {
1824 self.values.lock().unwrap().clone()
1825 }
1826 }
1827
1828 #[derive(Debug)]
1829 #[hyperactor::export(handlers = [u64, String])]
1830 struct MultiActor(MultiValues);
1831
1832 #[async_trait]
1833 impl Actor for MultiActor {}
1834
1835 #[async_trait]
1836 impl Handler<u64> for MultiActor {
1837 async fn handle(&mut self, _cx: &Context<Self>, message: u64) -> Result<(), anyhow::Error> {
1838 let mut vals = self.0.lock().unwrap();
1839 vals.0 = message;
1840 Ok(())
1841 }
1842 }
1843
1844 #[async_trait]
1845 impl Handler<String> for MultiActor {
1846 async fn handle(
1847 &mut self,
1848 _cx: &Context<Self>,
1849 message: String,
1850 ) -> Result<(), anyhow::Error> {
1851 let mut vals = self.0.lock().unwrap();
1852 vals.1 = message;
1853 Ok(())
1854 }
1855 }
1856
1857 #[async_trait]
1858 impl Handler<OncePortHandle<bool>> for MultiActor {
1859 async fn handle(
1860 &mut self,
1861 cx: &Context<Self>,
1862 message: OncePortHandle<bool>,
1863 ) -> Result<(), anyhow::Error> {
1864 message.post(cx, true);
1865 Ok(())
1866 }
1867 }
1868
1869 #[tokio::test]
1870 async fn test_multi_handler_refs() {
1871 let test = MultiValuesTest::new().await;
1872
1873 test.send(123u64);
1874 test.send("foo".to_string());
1875 test.sync().await;
1876 assert_eq!(test.get_values(), (123u64, "foo".to_string()));
1877
1878 let myref: ActorRef<MultiActor> = test.handle.bind();
1879
1880 myref.port().post(&test.client, 321u64);
1881 test.sync().await;
1882 assert_eq!(test.get_values(), (321u64, "foo".to_string()));
1883
1884 myref.port().post(&test.client, "bar".to_string());
1885 test.sync().await;
1886 assert_eq!(test.get_values(), (321u64, "bar".to_string()));
1887 }
1888
1889 #[tokio::test]
1890 async fn test_ref_behavior() {
1891 let test = MultiValuesTest::new().await;
1892
1893 test.send(123u64);
1894 test.send("foo".to_string());
1895
1896 hyperactor::behavior!(MyActorBehavior, u64, String);
1897
1898 let myref: ActorRef<MyActorBehavior> = test.handle.bind();
1899 myref.port().post(&test.client, "biz".to_string());
1900 myref.port().post(&test.client, 999u64);
1901
1902 test.sync().await;
1903 assert_eq!(test.get_values(), (999u64, "biz".to_string()));
1904 }
1905
1906 #[tokio::test]
1907 async fn test_actor_handle_downcast() {
1908 #[derive(Debug, Default)]
1909 struct NothingActor;
1910
1911 impl Actor for NothingActor {}
1912
1913 let proc = Proc::isolated();
1916 let handle = proc.spawn(NothingActor);
1917 let cell = handle.cell();
1918
1919 assert!(cell.downcast_handle::<EchoActor>().is_none());
1921
1922 let handle = cell.downcast_handle::<NothingActor>().unwrap();
1923 handle.drain_and_stop("test").unwrap();
1924 handle.await;
1925 }
1926
1927 #[derive(Debug)]
1929 #[hyperactor::export(handlers = [String, Callback])]
1930 struct GetSeqActor(PortRef<(String, SeqInfo)>);
1931
1932 #[async_trait]
1933 impl Actor for GetSeqActor {}
1934
1935 #[async_trait]
1936 impl Handler<String> for GetSeqActor {
1937 async fn handle(
1938 &mut self,
1939 cx: &Context<Self>,
1940 message: String,
1941 ) -> Result<(), anyhow::Error> {
1942 let Self(port) = self;
1943 let seq_info = cx.headers().get(SEQ_INFO).unwrap();
1944 port.post(cx, (message, seq_info.clone()));
1945 Ok(())
1946 }
1947 }
1948
1949 #[derive(Clone, Debug, Serialize, Deserialize, Named)]
1954 struct Callback(PortRef<PortRef<String>>);
1955
1956 #[async_trait]
1957 impl Handler<Callback> for GetSeqActor {
1958 async fn handle(
1959 &mut self,
1960 cx: &Context<Self>,
1961 message: Callback,
1962 ) -> Result<(), anyhow::Error> {
1963 let (handle, mut receiver) = cx.open_port::<String>();
1964 let callback_ref = handle.bind();
1965 message.0.post(cx, callback_ref);
1966 let msg = receiver.recv().await.unwrap();
1967 self.handle(cx, msg).await
1968 }
1969 }
1970
1971 #[async_timed_test(timeout_secs = 30)]
1972 async fn test_sequencing_actor_handle_basic() {
1973 let proc = Proc::isolated();
1974 let client = proc.client("client");
1975 let (tx, mut rx) = client.open_port();
1976
1977 let actor_handle = proc.spawn(GetSeqActor(tx.bind()));
1978
1979 actor_handle.post(&client, "unbound".to_string());
1981 assert_eq!(
1982 rx.recv().await.unwrap(),
1983 ("unbound".to_string(), SeqInfo::Direct)
1984 );
1985
1986 let actor_ref: ActorRef<GetSeqActor> = actor_handle.bind();
1987
1988 let session_id = client.sequencer().session_id();
1989 let mut expected_seq = 0;
1990 for m in 0..10 {
1992 actor_handle.post(&client, format!("{m}"));
1993 expected_seq += 1;
1994 assert_eq!(
1995 rx.recv().await.unwrap(),
1996 (
1997 format!("{m}"),
1998 SeqInfo::Session {
1999 session_id,
2000 seq: expected_seq,
2001 }
2002 )
2003 );
2004
2005 for n in 0..2 {
2006 actor_ref.port().post(&client, format!("{m}-{n}"));
2007 expected_seq += 1;
2008 assert_eq!(
2009 rx.recv().await.unwrap(),
2010 (
2011 format!("{m}-{n}"),
2012 SeqInfo::Session {
2013 session_id,
2014 seq: expected_seq,
2015 }
2016 )
2017 );
2018 }
2019 }
2020 }
2021
2022 #[async_timed_test(timeout_secs = 30)]
2024 async fn test_sequencing_mixed_handler_and_non_handler_ports() {
2025 let proc = Proc::isolated();
2026 let client = proc.client("client");
2027
2028 let (actor_tx, mut actor_rx) = client.open_port();
2030
2031 let (non_handler_tx, mut non_handler_rx) = mpsc::unbounded_channel::<Option<SeqInfo>>();
2033
2034 let actor_handle = proc.spawn(GetSeqActor(actor_tx.bind()));
2035 let actor_ref: ActorRef<GetSeqActor> = actor_handle.bind();
2036
2037 let non_handler_tx_clone = non_handler_tx.clone();
2039 let non_handler_port_handle =
2040 client
2041 .mailbox()
2042 .open_enqueue_port(move |headers: Flattrs, _m: ()| {
2043 let seq_info = headers.get(SEQ_INFO);
2044 non_handler_tx_clone.send(seq_info).unwrap();
2045 Ok(())
2046 });
2047
2048 non_handler_port_handle.bind();
2050 let non_handler_port_id = match non_handler_port_handle.location() {
2051 PortLocation::Bound(port_id) => port_id,
2052 _ => panic!("port_handle should be bound"),
2053 };
2054 assert!(!non_handler_port_id.is_handler_port());
2055
2056 let session_id = client.sequencer().session_id();
2057
2058 actor_handle.post(&client, "msg1".to_string());
2060 assert_eq!(
2061 actor_rx.recv().await.unwrap().1,
2062 SeqInfo::Session { session_id, seq: 1 }
2063 );
2064
2065 actor_ref.port().post(&client, "msg2".to_string());
2067 assert_eq!(
2068 actor_rx.recv().await.unwrap().1,
2069 SeqInfo::Session { session_id, seq: 2 }
2070 );
2071
2072 non_handler_port_handle.post(&client, ());
2074 assert_eq!(
2075 non_handler_rx.recv().await.unwrap(),
2076 Some(SeqInfo::Session { session_id, seq: 1 })
2077 );
2078
2079 actor_handle.post(&client, "msg3".to_string());
2081 assert_eq!(
2082 actor_rx.recv().await.unwrap().1,
2083 SeqInfo::Session { session_id, seq: 3 }
2084 );
2085
2086 non_handler_port_handle.post(&client, ());
2088 assert_eq!(
2089 non_handler_rx.recv().await.unwrap(),
2090 Some(SeqInfo::Session { session_id, seq: 2 })
2091 );
2092
2093 actor_ref.port().post(&client, "msg4".to_string());
2095 assert_eq!(
2096 actor_rx.recv().await.unwrap().1,
2097 SeqInfo::Session { session_id, seq: 4 }
2098 );
2099
2100 actor_handle.drain_and_stop("test cleanup").unwrap();
2101 actor_handle.await;
2102 }
2103
2104 #[async_timed_test(timeout_secs = 30)]
2106 async fn test_sequencing_multiple_clients() {
2107 let proc = Proc::isolated();
2108 let client1 = proc.client("client1");
2109 let client2 = proc.client("client2");
2110
2111 let (tx, mut rx) = client1.open_port();
2113
2114 let actor_handle = proc.spawn(GetSeqActor(tx.bind()));
2115 let actor_ref: ActorRef<GetSeqActor> = actor_handle.bind();
2116
2117 let session_id_1 = client1.sequencer().session_id();
2119 let session_id_2 = client2.sequencer().session_id();
2120 assert_ne!(session_id_1, session_id_2);
2121
2122 actor_handle.post(&client1, "c1_msg1".to_string());
2124 assert_eq!(
2125 rx.recv().await.unwrap().1,
2126 SeqInfo::Session {
2127 session_id: session_id_1,
2128 seq: 1
2129 }
2130 );
2131
2132 actor_handle.post(&client2, "c2_msg1".to_string());
2134 assert_eq!(
2135 rx.recv().await.unwrap().1,
2136 SeqInfo::Session {
2137 session_id: session_id_2,
2138 seq: 1
2139 }
2140 );
2141
2142 actor_ref.port().post(&client1, "c1_msg2".to_string());
2144 assert_eq!(
2145 rx.recv().await.unwrap().1,
2146 SeqInfo::Session {
2147 session_id: session_id_1,
2148 seq: 2
2149 }
2150 );
2151
2152 actor_ref.port().post(&client2, "c2_msg2".to_string());
2154 assert_eq!(
2155 rx.recv().await.unwrap().1,
2156 SeqInfo::Session {
2157 session_id: session_id_2,
2158 seq: 2
2159 }
2160 );
2161
2162 actor_handle.post(&client1, "c1_msg3".to_string());
2164 assert_eq!(
2165 rx.recv().await.unwrap().1,
2166 SeqInfo::Session {
2167 session_id: session_id_1,
2168 seq: 3
2169 }
2170 );
2171
2172 actor_ref.port().post(&client2, "c2_msg3".to_string());
2173 assert_eq!(
2174 rx.recv().await.unwrap().1,
2175 SeqInfo::Session {
2176 session_id: session_id_2,
2177 seq: 3
2178 }
2179 );
2180
2181 actor_handle.drain_and_stop("test cleanup").unwrap();
2182 actor_handle.await;
2183 }
2184
2185 #[async_timed_test(timeout_secs = 30)]
2206 async fn test_sequencing_actor_handle_callback() {
2207 let config = hyperactor_config::global::lock();
2208 let _guard = config.override_key(config::ENABLE_DEST_ACTOR_REORDERING_BUFFER, true);
2209
2210 let proc = Proc::isolated();
2211 let client = proc.client("client");
2212 let (tx, mut rx) = client.open_port();
2213
2214 let actor_handle = proc.spawn(GetSeqActor(tx.bind()));
2215 let actor_ref: ActorRef<GetSeqActor> = actor_handle.bind();
2216
2217 let (callback_tx, mut callback_rx) = client.open_port();
2218 actor_ref.post(&client, Callback(callback_tx.bind()));
2220 let msg_port_ref = callback_rx.recv().await.unwrap();
2221 msg_port_ref.post(&client, "finally".to_string());
2224
2225 let session_id = client.sequencer().session_id();
2226 assert_eq!(
2228 rx.recv().await.unwrap(),
2229 (
2230 "finally".to_string(),
2231 SeqInfo::Session { session_id, seq: 1 }
2232 )
2233 );
2234 }
2235
2236 #[derive(Clone, Debug)]
2239 struct DelayedMailboxSender {
2240 relay_tx: mpsc::UnboundedSender<MessageEnvelope>,
2241 }
2242
2243 impl DelayedMailboxSender {
2244 fn new(
2246 dest_proc: Proc,
2249 relay_orders: Vec<usize>,
2253 ) -> Self {
2254 let (relay_tx, mut relay_rx) = mpsc::unbounded_channel::<MessageEnvelope>();
2255
2256 tokio::spawn(async move {
2257 let mut buffer = Vec::new();
2258
2259 for _ in 0..relay_orders.len() {
2260 let envelope = relay_rx.recv().await.unwrap();
2261 buffer.push(envelope);
2262 }
2263
2264 for m in buffer.clone() {
2265 let seq = match m.headers().get(SEQ_INFO) {
2266 Some(SeqInfo::Session { seq, .. }) => seq as usize,
2267 Some(SeqInfo::Direct) => panic!("expected Session variant"),
2268 None => panic!("expected seq info"),
2269 };
2270 let order = relay_orders[seq - 1];
2272 buffer[order] = m;
2273 }
2274
2275 let dest_proc_clone = dest_proc.clone();
2276 for msg in buffer {
2277 dest_proc_clone.post(msg, monitored_return_handle());
2278 }
2279 });
2280
2281 Self { relay_tx }
2282 }
2283 }
2284
2285 #[async_trait]
2286 impl MailboxSender for DelayedMailboxSender {
2287 fn post_unchecked(
2288 &self,
2289 envelope: MessageEnvelope,
2290 _return_handle: PortHandle<Undeliverable<MessageEnvelope>>,
2291 ) {
2292 self.relay_tx.send(envelope).unwrap();
2293 }
2294 }
2295
2296 async fn assert_out_of_order_delivery(expected: Vec<(String, u64)>, relay_orders: Vec<usize>) {
2297 let local_proc: Proc = Proc::isolated();
2298 let client = local_proc.client("local");
2299 let (tx, mut rx) = client.open_port();
2300
2301 let handle = local_proc.spawn(GetSeqActor(tx.bind()));
2302 let actor_ref: ActorRef<GetSeqActor> = handle.bind();
2303
2304 let remote_proc = Proc::configured(
2305 test_proc_id("remote_0"),
2306 DelayedMailboxSender::new(local_proc.clone(), relay_orders).boxed(),
2307 );
2308 let remote_client = remote_proc.client("remote");
2309 let mut messages = expected.clone();
2311 messages.sort_by_key(|v| v.1);
2312 for (message, _seq) in messages {
2313 actor_ref.post(&remote_client, message);
2314 }
2315 let session_id = remote_client.sequencer().session_id();
2316 for expect in expected {
2317 let expected = (
2318 expect.0,
2319 SeqInfo::Session {
2320 session_id,
2321 seq: expect.1,
2322 },
2323 );
2324 assert_eq!(rx.recv().await.unwrap(), expected);
2325 }
2326
2327 handle.drain_and_stop("test cleanup").unwrap();
2328 handle.await;
2329 }
2330
2331 #[async_timed_test(timeout_secs = 30)]
2336 async fn test_sequencing_actor_ref_known_delivery_order() {
2337 let config = hyperactor_config::global::lock();
2338
2339 let relay_orders = vec![2, 0, 1];
2341
2342 let _guard = config.override_key(config::ENABLE_DEST_ACTOR_REORDERING_BUFFER, false);
2345 assert_out_of_order_delivery(
2346 vec![
2347 ("second".to_string(), 2),
2348 ("third".to_string(), 3),
2349 ("first".to_string(), 1),
2350 ],
2351 relay_orders.clone(),
2352 )
2353 .await;
2354
2355 let _guard = config.override_key(config::ENABLE_DEST_ACTOR_REORDERING_BUFFER, true);
2358 assert_out_of_order_delivery(
2359 vec![
2360 ("first".to_string(), 1),
2361 ("second".to_string(), 2),
2362 ("third".to_string(), 3),
2363 ],
2364 relay_orders.clone(),
2365 )
2366 .await;
2367 }
2368
2369 #[async_timed_test(timeout_secs = 30)]
2374 async fn test_sequencing_actor_ref_random_delivery_order() {
2375 let config = hyperactor_config::global::lock();
2376
2377 let _guard = config.override_key(config::ENABLE_DEST_ACTOR_REORDERING_BUFFER, true);
2380 let expected = (0..10000)
2381 .map(|i| (format!("msg{i}"), i + 1))
2382 .collect::<Vec<_>>();
2383
2384 let mut relay_orders: Vec<usize> = (0..10000).collect();
2385 relay_orders.shuffle(&mut rand::rng());
2386 assert_out_of_order_delivery(expected, relay_orders).await;
2387 }
2388
2389 #[tokio::test]
2407 async fn test_introspect_query_default_payload() {
2408 let proc = Proc::isolated();
2409 let client = proc.client("client");
2410 let (tx, _rx) = client.open_port::<u64>();
2411 let actor = EchoActor(tx.bind());
2412 let handle = proc.spawn(actor);
2413
2414 let (reply_port, reply_rx) = client.open_once_port::<IntrospectResult>();
2415 handle.actor_addr().introspect_port().post(
2416 &client,
2417 IntrospectMessage::Query {
2418 view: IntrospectView::Actor,
2419 reply: reply_port.bind(),
2420 },
2421 );
2422 let payload = reply_rx.recv().await.unwrap();
2423
2424 assert_eq!(
2425 payload.identity,
2426 crate::introspect::IntrospectRef::Actor(handle.actor_addr().clone())
2427 );
2428 assert_valid_attrs(&payload);
2429 assert_has_attr(&payload, "status");
2430 assert_has_attr(&payload, "actor_type");
2431 assert_has_attr(&payload, "created_at");
2432 assert!(payload.children.is_empty());
2433 assert!(payload.parent.is_none());
2434
2435 handle.drain_and_stop("test").unwrap();
2436 handle.await;
2437 }
2438
2439 fn attrs_get(attrs_json: &str, short_name: &str) -> Option<serde_json::Value> {
2441 use hyperactor_config::INTROSPECT;
2442 use hyperactor_config::attrs::AttrKeyInfo;
2443 let fq_name = inventory::iter::<AttrKeyInfo>()
2444 .find(|info| {
2445 info.meta
2446 .get(INTROSPECT)
2447 .is_some_and(|ia| ia.name == short_name)
2448 })
2449 .map(|info| info.name)?;
2450 let obj: serde_json::Value = serde_json::from_str(attrs_json).ok()?;
2451 obj.get(fq_name).cloned()
2452 }
2453
2454 fn assert_valid_attrs(result: &IntrospectResult) {
2456 let parsed: serde_json::Value =
2457 serde_json::from_str(&result.attrs).expect("attrs must be valid JSON");
2458 assert!(parsed.is_object(), "IA-1: attrs must be a JSON object");
2459 }
2460
2461 fn assert_status(result: &IntrospectResult, expected: &str) {
2463 let status = attrs_get(&result.attrs, "status")
2464 .and_then(|v| v.as_str().map(String::from))
2465 .expect("attrs must contain status");
2466 assert_eq!(status, expected, "unexpected actor status");
2467 }
2468
2469 fn assert_handler(result: &IntrospectResult, expected: Option<&str>) {
2471 let handler =
2472 attrs_get(&result.attrs, "last_handler").and_then(|v| v.as_str().map(String::from));
2473 assert_eq!(handler.as_deref(), expected);
2474 }
2475
2476 fn assert_error_code(result: &IntrospectResult, expected: &str) {
2478 let code = attrs_get(&result.attrs, "error_code")
2479 .and_then(|v| v.as_str().map(String::from))
2480 .expect("attrs must contain error_code");
2481 assert_eq!(code, expected);
2482 }
2483
2484 fn assert_handler_not_contains(result: &IntrospectResult, forbidden: &str) {
2486 if let Some(handler) =
2487 attrs_get(&result.attrs, "last_handler").and_then(|v| v.as_str().map(String::from))
2488 {
2489 assert!(
2490 !handler.contains(forbidden),
2491 "handler should not contain '{}'; got: {}",
2492 forbidden,
2493 handler
2494 );
2495 }
2496 }
2497
2498 fn assert_has_attr(result: &IntrospectResult, short_name: &str) {
2500 assert!(
2501 attrs_get(&result.attrs, short_name).is_some(),
2502 "attrs must contain '{}'",
2503 short_name
2504 );
2505 }
2506
2507 fn assert_status_contains(result: &IntrospectResult, substring: &str) {
2510 let status = attrs_get(&result.attrs, "status")
2511 .and_then(|v| v.as_str().map(String::from))
2512 .expect("attrs must contain status");
2513 assert!(
2514 status.contains(substring),
2515 "status should contain '{}'; got: {}",
2516 substring,
2517 status
2518 );
2519 }
2520
2521 fn assert_no_status_reason(result: &IntrospectResult) {
2523 assert!(
2524 attrs_get(&result.attrs, "status_reason").is_none(),
2525 "IA-3: must not have status_reason"
2526 );
2527 }
2528
2529 fn assert_has_handler(result: &IntrospectResult) {
2531 assert!(
2532 attrs_get(&result.attrs, "last_handler").is_some(),
2533 "must have a handler"
2534 );
2535 }
2536
2537 fn assert_no_failure_attrs(result: &IntrospectResult) {
2539 assert!(
2540 attrs_get(&result.attrs, "failure_error_message").is_none(),
2541 "IA-4: must not have failure attrs"
2542 );
2543 }
2544
2545 #[tokio::test]
2550 async fn test_ia1_ia4_running_actor_attrs() {
2551 let proc = Proc::isolated();
2552 let client = proc.client("client");
2553 let (tx, _rx) = client.open_port::<u64>();
2554 let actor = EchoActor(tx.bind());
2555 let handle = proc.spawn(actor);
2556
2557 let payload = crate::introspect::live_actor_payload(handle.cell());
2558
2559 assert_valid_attrs(&payload);
2561
2562 assert_has_attr(&payload, "status");
2564 assert_no_status_reason(&payload);
2565
2566 assert_no_failure_attrs(&payload);
2568
2569 handle.drain_and_stop("test").unwrap();
2570 handle.await;
2571 }
2572
2573 #[tokio::test]
2579 async fn test_actor_attrs_snapshot_appears_verbatim() {
2580 let proc = Proc::isolated();
2581 let client = proc.client("client");
2582 let (tx, _rx) = client.open_port::<u64>();
2583 let handle = proc.spawn(EchoActor(tx.bind()));
2584
2585 handle.cell().set_attrs_snapshot(|| {
2586 let mut attrs = hyperactor_config::Attrs::new();
2587 attrs.set(crate::introspect::LAST_HANDLER, "seam-probe".to_string());
2588 attrs
2589 });
2590
2591 let payload = crate::introspect::live_actor_payload(handle.cell());
2592 assert_valid_attrs(&payload);
2593 assert_eq!(
2594 attrs_get(&payload.attrs, "last_handler").and_then(|v| v.as_str().map(String::from)),
2595 Some("seam-probe".to_string()),
2596 "AS-1: actor-supplied attr must appear verbatim"
2597 );
2598
2599 handle.drain_and_stop("test").unwrap();
2600 handle.await;
2601 }
2602
2603 #[tokio::test]
2606 async fn test_actor_attrs_snapshot_core_wins_on_collision() {
2607 let proc = Proc::isolated();
2608 let client = proc.client("client");
2609 let (tx, _rx) = client.open_port::<u64>();
2610 let handle = proc.spawn(EchoActor(tx.bind()));
2611
2612 handle.cell().set_attrs_snapshot(|| {
2616 let mut attrs = hyperactor_config::Attrs::new();
2617 attrs.set(crate::introspect::MESSAGES_PROCESSED, 999u64);
2618 attrs
2619 });
2620
2621 let payload = crate::introspect::live_actor_payload(handle.cell());
2622 let messages = attrs_get(&payload.attrs, "messages_processed")
2623 .and_then(|v| v.as_u64())
2624 .expect("attrs must contain messages_processed");
2625 assert_eq!(messages, 0, "AS-2: core value must win over the snapshot");
2628
2629 handle.drain_and_stop("test").unwrap();
2630 handle.await;
2631 }
2632
2633 #[tokio::test]
2636 async fn test_actor_attrs_snapshot_panic_is_non_fatal() {
2637 let proc = Proc::isolated();
2638 let client = proc.client("client");
2639 let (tx, _rx) = client.open_port::<u64>();
2640 let handle = proc.spawn(EchoActor(tx.bind()));
2641
2642 handle
2643 .cell()
2644 .set_attrs_snapshot(|| -> hyperactor_config::Attrs {
2645 panic!("snapshot callback boom")
2646 });
2647
2648 let payload = crate::introspect::live_actor_payload(handle.cell());
2649 assert_valid_attrs(&payload);
2651 assert_has_attr(&payload, "status");
2652 assert_has_attr(&payload, "actor_type");
2653
2654 handle.drain_and_stop("test").unwrap();
2655 handle.await;
2656 }
2657
2658 #[tokio::test]
2664 async fn test_introspect_query_child_not_found() {
2665 let proc = Proc::isolated();
2666 let client = proc.client("client");
2667 let (tx, _rx) = client.open_port::<u64>();
2668 let actor = EchoActor(tx.bind());
2669 let handle = proc.spawn(actor);
2670
2671 let child_ref = crate::Addr::Actor(test_proc_id("nonexistent").actor_addr("child"));
2672 let (reply_port, reply_rx) = client.open_once_port::<IntrospectResult>();
2673 handle.actor_addr().introspect_port().post(
2674 &client,
2675 IntrospectMessage::QueryChild {
2676 child_ref,
2677 reply: reply_port.bind(),
2678 },
2679 );
2680 let payload = reply_rx.recv().await.unwrap();
2681
2682 assert_eq!(
2683 payload.identity,
2684 crate::introspect::IntrospectRef::Actor(
2685 test_proc_id("nonexistent").actor_addr("child")
2686 )
2687 );
2688 assert_error_code(&payload, "not_found");
2689
2690 handle.drain_and_stop("test").unwrap();
2691 handle.await;
2692 }
2693
2694 #[tokio::test]
2700 async fn test_introspect_override() {
2701 #[derive(Debug, Default)]
2702 #[hyperactor::export(handlers = [])]
2703 struct CustomIntrospectActor;
2704
2705 #[async_trait]
2706 impl Actor for CustomIntrospectActor {}
2707
2708 let proc = Proc::isolated();
2709 let client = proc.client("client");
2710 let handle = proc.spawn(CustomIntrospectActor);
2711
2712 handle
2713 .status()
2714 .wait_for(|s| matches!(s, ActorStatus::Idle))
2715 .await
2716 .unwrap();
2717
2718 let (reply_port, reply_rx) = client.open_once_port::<IntrospectResult>();
2719 handle.actor_addr().introspect_port().post(
2720 &client,
2721 IntrospectMessage::Query {
2722 view: IntrospectView::Actor,
2723 reply: reply_port.bind(),
2724 },
2725 );
2726 let payload = reply_rx.recv().await.unwrap();
2727
2728 assert_has_attr(&payload, "status");
2731
2732 handle.drain_and_stop("test").unwrap();
2733 handle.await;
2734 }
2735
2736 #[tokio::test]
2740 async fn test_introspect_query_supervision_child() {
2741 let proc = Proc::isolated();
2742 let client = proc.client("client");
2743
2744 let (tx_parent, _rx_parent) = client.open_port::<u64>();
2746 let parent_handle =
2747 proc.spawn_with_label::<EchoActor>("parent", EchoActor(tx_parent.bind()));
2748
2749 let (tx_child, _rx_child) = client.open_port::<u64>();
2751 let child_handle =
2752 proc.spawn_child::<EchoActor>(parent_handle.cell().clone(), EchoActor(tx_child.bind()));
2753
2754 let (reply_port, reply_rx) = client.open_once_port::<IntrospectResult>();
2756 child_handle.actor_addr().introspect_port().post(
2757 &client,
2758 IntrospectMessage::Query {
2759 view: IntrospectView::Actor,
2760 reply: reply_port.bind(),
2761 },
2762 );
2763 let child_payload = reply_rx.recv().await.unwrap();
2764
2765 assert_eq!(
2766 child_payload.identity,
2767 crate::introspect::IntrospectRef::Actor(child_handle.actor_addr().clone()),
2768 );
2769 assert!(
2771 attrs_get(&child_payload.attrs, "status").is_some(),
2772 "child should have actor attrs"
2773 );
2774 assert_eq!(
2775 child_payload.parent,
2776 Some(crate::introspect::IntrospectRef::Actor(
2777 parent_handle.actor_addr().clone()
2778 )),
2779 );
2780
2781 let (reply_port, reply_rx) = client.open_once_port::<IntrospectResult>();
2783 parent_handle.actor_addr().introspect_port().post(
2784 &client,
2785 IntrospectMessage::Query {
2786 view: IntrospectView::Actor,
2787 reply: reply_port.bind(),
2788 },
2789 );
2790 let parent_payload = reply_rx.recv().await.unwrap();
2791
2792 assert!(parent_payload.parent.is_none());
2793 assert!(
2794 parent_payload
2795 .children
2796 .contains(&crate::introspect::IntrospectRef::Actor(
2797 child_handle.actor_addr().clone()
2798 )),
2799 );
2800
2801 child_handle.drain_and_stop("test").unwrap();
2802 child_handle.await;
2803 parent_handle.drain_and_stop("test").unwrap();
2804 parent_handle.await;
2805 }
2806
2807 #[tokio::test]
2812 async fn test_introspect_fresh_actor_status() {
2813 let proc = Proc::isolated();
2814 let client = proc.client("client");
2815 let (tx, _rx) = client.open_port::<u64>();
2816 let actor = EchoActor(tx.bind());
2817 let handle = proc.spawn(actor);
2818
2819 handle
2821 .status()
2822 .wait_for(|s| matches!(s, ActorStatus::Idle))
2823 .await
2824 .unwrap();
2825
2826 let (reply_port, reply_rx) = client.open_once_port::<IntrospectResult>();
2827 handle.actor_addr().introspect_port().post(
2828 &client,
2829 IntrospectMessage::Query {
2830 view: IntrospectView::Actor,
2831 reply: reply_port.bind(),
2832 },
2833 );
2834 let payload = reply_rx.recv().await.unwrap();
2835
2836 assert_status(&payload, "idle");
2837 assert_handler(&payload, None);
2838
2839 handle.drain_and_stop("test").unwrap();
2840 handle.await;
2841 }
2842
2843 #[tokio::test]
2848 async fn test_introspect_after_user_message() {
2849 let proc = Proc::isolated();
2850 let client = proc.client("client");
2851 let (tx, mut rx) = client.open_port::<u64>();
2852 let actor = EchoActor(tx.bind());
2853 let handle = proc.spawn(actor);
2854
2855 handle.post(&client, 42u64);
2857 let _ = rx.recv().await.unwrap();
2858
2859 let (reply_port, reply_rx) = client.open_once_port::<IntrospectResult>();
2860 handle.actor_addr().introspect_port().post(
2861 &client,
2862 IntrospectMessage::Query {
2863 view: IntrospectView::Actor,
2864 reply: reply_port.bind(),
2865 },
2866 );
2867 let payload = reply_rx.recv().await.unwrap();
2868
2869 assert_status(&payload, "idle");
2870 assert_has_handler(&payload);
2871 assert_handler_not_contains(&payload, "IntrospectMessage");
2872
2873 handle.drain_and_stop("test").unwrap();
2874 handle.await;
2875 }
2876
2877 #[tokio::test]
2882 async fn test_introspect_consecutive_queries() {
2883 let proc = Proc::isolated();
2884 let client = proc.client("client");
2885 let (tx, _rx) = client.open_port::<u64>();
2886 let actor = EchoActor(tx.bind());
2887 let handle = proc.spawn(actor);
2888
2889 handle
2890 .status()
2891 .wait_for(|s| matches!(s, ActorStatus::Idle))
2892 .await
2893 .unwrap();
2894
2895 let (reply_port, reply_rx) = client.open_once_port::<IntrospectResult>();
2897 handle.actor_addr().introspect_port().post(
2898 &client,
2899 IntrospectMessage::Query {
2900 view: IntrospectView::Actor,
2901 reply: reply_port.bind(),
2902 },
2903 );
2904 let payload1 = reply_rx.recv().await.unwrap();
2905
2906 let (reply_port2, reply_rx2) = client.open_once_port::<IntrospectResult>();
2908 handle.actor_addr().introspect_port().post(
2909 &client,
2910 IntrospectMessage::Query {
2911 view: IntrospectView::Actor,
2912 reply: reply_port2.bind(),
2913 },
2914 );
2915 let payload2 = reply_rx2.recv().await.unwrap();
2916
2917 assert_handler(&payload1, None);
2919 assert_handler(&payload2, None);
2920
2921 handle.drain_and_stop("test").unwrap();
2922 handle.await;
2923 }
2924
2925 #[tokio::test]
2933 async fn test_publish_attrs_round_trip() {
2934 use hyperactor_config::Attrs;
2935 use hyperactor_config::declare_attrs;
2936
2937 declare_attrs! {
2938 attr TEST_KEY_A: String;
2939 attr TEST_KEY_B: u64;
2940 }
2941
2942 let proc = Proc::isolated();
2943 let client = proc.client("client");
2944 let (tx, _rx) = client.open_port::<u64>();
2945 let actor = EchoActor(tx.bind());
2946 let handle = proc.spawn(actor);
2947
2948 assert!(handle.cell().published_attrs().is_none());
2950
2951 let mut attrs = Attrs::new();
2953 attrs.set(TEST_KEY_A, "hello".to_string());
2954 handle.cell().set_published_attrs(attrs);
2955 let published = handle.cell().published_attrs().unwrap();
2956 assert_eq!(published.get(TEST_KEY_A), Some(&"hello".to_string()));
2957
2958 handle.cell().merge_published_attr(TEST_KEY_B, 42u64);
2960 let published = handle.cell().published_attrs().unwrap();
2961 assert_eq!(published.get(TEST_KEY_A), Some(&"hello".to_string()));
2962 assert_eq!(published.get(TEST_KEY_B), Some(&42u64));
2963
2964 handle
2966 .cell()
2967 .merge_published_attr(TEST_KEY_A, "world".to_string());
2968 let published = handle.cell().published_attrs().unwrap();
2969 assert_eq!(published.get(TEST_KEY_A), Some(&"world".to_string()));
2970
2971 handle.drain_and_stop("test").unwrap();
2972 handle.await;
2973 }
2974
2975 #[tokio::test]
2978 async fn test_query_child_handler_round_trip() {
2979 let proc = Proc::isolated();
2980 let client = proc.client("client");
2981 let (tx, _rx) = client.open_port::<u64>();
2982 let actor = EchoActor(tx.bind());
2983 let handle = proc.spawn(actor);
2984
2985 let test_ref = Addr::Actor(test_proc_id("test").actor_addr("child"));
2987 assert!(handle.cell().query_child(&test_ref).is_none());
2988
2989 handle.cell().set_query_child_handler(|child_ref| {
2991 use crate::introspect::IntrospectRef;
2992 let identity = match child_ref {
2993 Addr::Proc(p) => IntrospectRef::Proc(p.clone()),
2994 Addr::Actor(a) => IntrospectRef::Actor(a.clone()),
2995 Addr::Port(p) => IntrospectRef::Actor(p.actor_addr()),
2996 };
2997 IntrospectResult {
2998 identity,
2999 attrs: serde_json::json!({
3000 "proc_name": "test_proc",
3001 "num_actors": 42,
3002 })
3003 .to_string(),
3004 children: Vec::new(),
3005 parent: None,
3006 as_of: std::time::SystemTime::now(),
3007 }
3008 });
3009
3010 let payload = handle
3012 .cell()
3013 .query_child(&test_ref)
3014 .expect("query_child must return payload");
3015 assert_eq!(
3016 payload.identity,
3017 crate::introspect::IntrospectRef::Actor(test_proc_id("test").actor_addr("child"))
3018 );
3019 let attrs: serde_json::Value =
3020 serde_json::from_str(&payload.attrs).expect("attrs must be valid JSON");
3021 assert_eq!(
3022 attrs.get("proc_name").and_then(|v| v.as_str()),
3023 Some("test_proc")
3024 );
3025 assert_eq!(attrs.get("num_actors").and_then(|v| v.as_u64()), Some(42));
3026
3027 handle.drain_and_stop("test").unwrap();
3028 handle.await;
3029 }
3030
3031 #[tokio::test]
3037 async fn test_introspect_wedged() {
3038 #[derive(Debug, Default)]
3039 #[hyperactor::export(handlers = [u64])]
3040 struct WedgedActor;
3041
3042 #[async_trait]
3043 impl Actor for WedgedActor {}
3044
3045 #[async_trait]
3046 impl Handler<u64> for WedgedActor {
3047 async fn handle(
3048 &mut self,
3049 _cx: &Context<Self>,
3050 _message: u64,
3051 ) -> Result<(), anyhow::Error> {
3052 std::future::pending::<()>().await;
3054 Ok(())
3055 }
3056 }
3057
3058 let proc = Proc::isolated();
3059 let client = proc.client("client");
3060 let handle = proc.spawn(WedgedActor);
3061
3062 handle
3064 .status()
3065 .wait_for(|s| matches!(s, ActorStatus::Idle))
3066 .await
3067 .unwrap();
3068
3069 handle.post(&client, 1u64);
3071
3072 tokio::time::sleep(Duration::from_millis(50)).await;
3074
3075 let (reply_port, reply_rx) = client.open_once_port::<IntrospectResult>();
3077 handle.actor_addr().introspect_port().post(
3078 &client,
3079 IntrospectMessage::Query {
3080 view: IntrospectView::Actor,
3081 reply: reply_port.bind(),
3082 },
3083 );
3084
3085 let payload = tokio::time::timeout(Duration::from_secs(5), reply_rx.recv())
3087 .await
3088 .expect("introspect should not hang on a wedged actor")
3089 .unwrap();
3090
3091 assert_status_contains(&payload, "processing");
3092 assert_handler_not_contains(&payload, "IntrospectMessage");
3093 }
3094
3095 #[tokio::test]
3100 async fn test_introspect_no_perturbation() {
3101 let proc = Proc::isolated();
3102 let client = proc.client("client");
3103 let (tx, mut rx) = client.open_port::<u64>();
3104 let actor = EchoActor(tx.bind());
3105 let handle = proc.spawn(actor);
3106
3107 handle
3109 .status()
3110 .wait_for(|s| matches!(s, ActorStatus::Idle))
3111 .await
3112 .unwrap();
3113
3114 handle.post(&client, 42u64);
3116 let _ = rx.recv().await.unwrap();
3117
3118 let (reply_port1, reply_rx1) = client.open_once_port::<IntrospectResult>();
3120 handle.actor_addr().introspect_port().post(
3121 &client,
3122 IntrospectMessage::Query {
3123 view: IntrospectView::Actor,
3124 reply: reply_port1.bind(),
3125 },
3126 );
3127 let payload1 = reply_rx1.recv().await.unwrap();
3128
3129 let (reply_port2, reply_rx2) = client.open_once_port::<IntrospectResult>();
3131 handle.actor_addr().introspect_port().post(
3132 &client,
3133 IntrospectMessage::Query {
3134 view: IntrospectView::Actor,
3135 reply: reply_port2.bind(),
3136 },
3137 );
3138 let payload2 = reply_rx2.recv().await.unwrap();
3139
3140 assert_handler_not_contains(&payload1, "IntrospectMessage");
3142 assert_handler_not_contains(&payload2, "IntrospectMessage");
3143 let attrs1: serde_json::Value = serde_json::from_str(&payload1.attrs).unwrap();
3146 let attrs2: serde_json::Value = serde_json::from_str(&payload2.attrs).unwrap();
3147 assert_eq!(attrs1, attrs2, "consecutive queries should be identical");
3148
3149 handle.drain_and_stop("test").unwrap();
3150 handle.await;
3151 }
3152
3153 #[tokio::test]
3160 async fn test_introspectable_instance_responds_to_query() {
3161 let proc = Proc::isolated();
3162 let (bridge, handle) = proc.introspectable_instance("bridge").unwrap();
3163 let actor_id: crate::ActorAddr = handle.actor_addr().clone();
3164
3165 let (reply_port, reply_rx) = bridge.open_once_port::<IntrospectResult>();
3166 actor_id.introspect_port().post(
3167 &bridge,
3168 IntrospectMessage::Query {
3169 view: IntrospectView::Actor,
3170 reply: reply_port.bind(),
3171 },
3172 );
3173 let payload = reply_rx.recv().await.unwrap();
3174
3175 assert_eq!(
3178 payload.identity,
3179 crate::introspect::IntrospectRef::Actor(actor_id.clone())
3180 );
3181 assert_status(&payload, "client");
3182 let actor_type =
3183 attrs_get(&payload.attrs, "actor_type").and_then(|v| v.as_str().map(String::from));
3184 assert_eq!(
3185 actor_type.as_deref(),
3186 Some("()"),
3187 "CI-1: actor_type must be \"()\""
3188 );
3189 }
3190
3191 #[tokio::test]
3199 async fn test_instance_does_not_respond_to_query() {
3200 let proc = Proc::isolated();
3201 let client = proc.client("client");
3202 let mailbox = proc.client("mailbox");
3203 let mailbox_id: crate::ActorAddr = mailbox.self_addr().clone();
3204
3205 let (reply_port, reply_rx) = client.open_once_port::<IntrospectResult>();
3206 let mut introspect_port = mailbox_id.introspect_port();
3207 introspect_port.return_undeliverable(false);
3210 introspect_port.post(
3211 &client,
3212 IntrospectMessage::Query {
3213 view: IntrospectView::Actor,
3214 reply: reply_port.bind(),
3215 },
3216 );
3217
3218 let result = tokio::time::timeout(Duration::from_millis(100), reply_rx.recv()).await;
3221 assert!(
3222 result.is_err(),
3223 "client() must not respond to IntrospectMessage (introspect receiver dropped)"
3224 );
3225 }
3226
3227 #[tokio::test]
3232 async fn test_introspectable_instance_snapshot_on_drop() {
3233 let proc = Proc::isolated();
3234 let (instance, handle) = proc.introspectable_instance("bridge").unwrap();
3235 let actor_id = handle.actor_addr().clone();
3236
3237 assert!(
3238 proc.all_actor_ids().contains(&actor_id),
3239 "should appear in all_actor_ids while live"
3240 );
3241
3242 drop(instance);
3243 handle.await;
3244
3245 let snapshot = proc.terminated_snapshot(&actor_id).unwrap();
3246 let actor_status = attrs_get(&snapshot.attrs, "status")
3247 .and_then(|v| v.as_str().map(String::from))
3248 .expect("snapshot attrs must contain status");
3249 assert!(
3250 actor_status.starts_with("stopped"),
3251 "CI-2: snapshot actor_status should be stopped, got: {}",
3252 actor_status
3253 );
3254 }
3255
3256 #[test]
3257 fn zombie_status_is_not_terminal() {
3258 let status = ActorStatus::zombie("hard kill did not finish");
3259
3260 assert!(!status.is_terminal(), "zombie status is not terminal");
3261 assert!(status.is_stopping(), "zombie status is stopping");
3262 assert!(status.is_zombie(), "zombie predicate should match");
3263 }
3264}