1use std::any::Any;
104use std::collections::BTreeMap;
105use std::collections::BTreeSet;
106use std::fmt;
107use std::fmt::Debug;
108use std::future;
109use std::future::Future;
110use std::ops::Bound::Excluded;
111use std::pin::Pin;
112use std::sync::Arc;
113use std::sync::Condvar;
114use std::sync::Mutex;
115use std::sync::Once;
116use std::sync::OnceLock;
117use std::sync::RwLock;
118use std::sync::Weak;
119use std::sync::atomic::AtomicU64;
120use std::sync::atomic::AtomicUsize;
121use std::sync::atomic::Ordering;
122use std::task::Context;
123use std::task::Poll;
124
125use async_trait::async_trait;
126use dashmap::DashMap;
127use dashmap::mapref::entry::Entry;
128use enum_as_inner::EnumAsInner;
129use futures::Sink;
130use futures::Stream;
131use hyperactor_config::Flattrs;
132use hyperactor_telemetry::hash_to_u64;
133use serde::Deserialize;
134use serde::Serialize;
135use serde::de::DeserializeOwned;
136use tokio::sync::mpsc;
137use tokio::sync::oneshot;
138use tokio::sync::watch;
139use tokio::task::JoinHandle;
140use tokio_util::sync::CancellationToken;
141use tracing::Instrument;
142use typeuri::Named;
143
144use crate::ActorAddr;
145use crate::Addr;
146use crate::Endpoint;
147use crate::EndpointLocation;
148use crate::IdleFlushPortRef;
149use crate::OncePortRef;
151use crate::PortAddr;
152use crate::PortRef;
153use crate::ProcAddr;
154use crate::accum::Accumulator;
155use crate::accum::IdleFlushReducerOpts;
156use crate::accum::ReducerSpec;
157use crate::accum::StreamingReducerOpts;
158use crate::actor::ActorStatus;
159use crate::channel;
160use crate::channel::ChannelAddr;
161use crate::channel::ChannelError;
162use crate::channel::ChannelTransport;
163use crate::channel::CloseReason;
164use crate::channel::CompletionSink;
165use crate::channel::SendError;
166use crate::channel::SendErrorReason;
167use crate::channel::TxStatus;
168use crate::context;
169use crate::id::ActorId;
170use crate::metrics;
171use crate::ordering::OrderingSnapshot;
172use crate::ordering::SEQ_INFO;
173use crate::ordering::SeqInfo;
174use crate::port::ControlPort;
175use crate::port::Port;
176use crate::sequenced::SequencedEnvelope;
177use crate::sequenced::SequencedReceiver;
178use crate::sequenced::sequenced_unbounded;
179
180mod undeliverable;
181pub use undeliverable::DeliveryFailureReport;
183pub use undeliverable::Undeliverable;
184pub use undeliverable::UndeliverableMessageError;
185pub use undeliverable::custom_monitored_return_handle;
186pub use undeliverable::monitored_return_handle; pub mod mailbox_admin_message;
189pub use mailbox_admin_message::MailboxAdminMessage;
190pub use mailbox_admin_message::MailboxAdminMessageHandler;
191pub mod headers;
193
194pub trait Message: Send + Sync + 'static {}
197impl<M: Send + Sync + 'static> Message for M {}
198
199pub trait RemoteMessage: Message + Named + Serialize + DeserializeOwned {}
203
204impl<M: Message + Named + Serialize + DeserializeOwned> RemoteMessage for M {}
205
206pub type Data = Vec<u8>;
208
209const MAX_RENDERED_DELIVERY_FAILURE_ATTRS_LEN: usize = 1024;
210
211fn truncate_for_delivery_failure_rendering(value: String) -> String {
212 if value.len() <= MAX_RENDERED_DELIVERY_FAILURE_ATTRS_LEN {
213 return value;
214 }
215
216 let mut truncated = value;
217 let truncate_at = truncated
218 .char_indices()
219 .map(|(index, _)| index)
220 .take_while(|index| *index <= MAX_RENDERED_DELIVERY_FAILURE_ATTRS_LEN)
221 .last()
222 .unwrap_or(0);
223 truncated.truncate(truncate_at);
224 truncated.push_str("...");
225 truncated
226}
227
228#[derive(thiserror::Error, Debug, Serialize, Deserialize, typeuri::Named, Clone)]
230#[error("{kind}")]
231pub struct DeliveryFailure {
232 pub kind: DeliveryFailureKind,
234
235 pub attrs: Flattrs,
237}
238
239impl DeliveryFailure {
240 pub fn new(kind: impl Into<DeliveryFailureKind>) -> Self {
242 Self {
243 kind: kind.into(),
244 attrs: Flattrs::new(),
245 }
246 }
247
248 pub fn with_attrs(kind: impl Into<DeliveryFailureKind>, attrs: Flattrs) -> Self {
250 Self {
251 kind: kind.into(),
252 attrs,
253 }
254 }
255
256 pub fn render_bounded(&self) -> String {
258 let mut rendered = format!("delivery failure: {}", self.kind);
259 if !self.attrs.is_empty() {
260 rendered.push_str("; attrs: ");
261 rendered.push_str(&truncate_for_delivery_failure_rendering(
262 self.attrs.to_string(),
263 ));
264 }
265 rendered
266 }
267}
268
269#[derive(
271 thiserror::Error,
272 Debug,
273 Serialize,
274 Deserialize,
275 EnumAsInner,
276 typeuri::Named,
277 Clone,
278 PartialEq,
279 Eq
280)]
281pub enum DeliveryFailureKind {
282 #[error("{0}")]
284 InvalidReference(#[from] InvalidReference),
285
286 #[error("{0}")]
289 Undeliverable(#[from] UndeliverableReason),
290
291 #[error("{0}")]
293 Expired(#[from] ExpiredDelivery),
294}
295
296#[derive(thiserror::Error, Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
298#[error("invalid reference {target}: {reason}")]
299pub struct InvalidReference {
300 pub target: Addr,
302
303 pub reason: InvalidReferenceReason,
305}
306
307impl InvalidReference {
308 pub fn new(target: impl Into<Addr>, reason: InvalidReferenceReason) -> Self {
310 Self {
311 target: target.into(),
312 reason,
313 }
314 }
315}
316
317#[derive(thiserror::Error, Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
319pub enum InvalidReferenceReason {
320 #[error("actor does not exist")]
322 ActorNotExist,
323
324 #[error("handler not bound")]
326 HandlerNotBound,
327
328 #[error("actor stopped")]
330 ActorStopped,
331
332 #[error("actor failed")]
334 ActorFailed,
335
336 #[error("port never allocated")]
338 PortNeverAllocated,
339
340 #[error("protocol mismatch")]
342 ProtocolMismatch,
343
344 #[error("wrong mailbox owner")]
346 WrongMailboxOwner,
347}
348
349#[derive(thiserror::Error, Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
351#[error("ttl expired for {target}")]
352pub struct ExpiredDelivery {
353 pub target: PortAddr,
355}
356
357impl ExpiredDelivery {
358 pub fn new(target: impl Into<PortAddr>) -> Self {
360 Self {
361 target: target.into(),
362 }
363 }
364}
365
366#[derive(
368 thiserror::Error,
369 Debug,
370 Serialize,
371 Deserialize,
372 EnumAsInner,
373 Clone,
374 PartialEq,
375 Eq
376)]
377pub enum UndeliverableReason {
378 #[error("{0}")]
380 Transport(#[from] TransportFailure),
381
382 #[error("{0}")]
384 PortGone(#[from] PortGone),
385}
386
387#[derive(thiserror::Error, Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
389#[error("transport failure to {target}: {reason}")]
390pub struct TransportFailure {
391 pub target: Addr,
393
394 pub reason: TransportFailureReason,
396}
397
398impl TransportFailure {
399 pub fn new(target: impl Into<Addr>, reason: TransportFailureReason) -> Self {
401 Self {
402 target: target.into(),
403 reason,
404 }
405 }
406}
407
408#[derive(thiserror::Error, Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
410pub enum TransportFailureReason {
411 #[error("channel closed: {addr}")]
413 ChannelClosed {
414 addr: ChannelAddr,
416 },
417
418 #[error("ack timed out: {addr}")]
420 AckTimedOut {
421 addr: ChannelAddr,
423 },
424
425 #[error("dial failed: {addr}: {error}")]
427 DialFailed {
428 addr: ChannelAddr,
430
431 error: String,
433 },
434
435 #[error("no route")]
438 NoRoute,
439
440 #[error(
442 "rejecting oversize frame: len={len} > max={max}. \
443 ack will not arrive before timeout; increase CODEC_MAX_FRAME_LENGTH to allow."
444 )]
445 OversizedFrame {
446 len: usize,
448
449 max: usize,
451 },
452
453 #[error("link unavailable: {0}")]
455 LinkUnavailable(String),
456
457 #[error("forwarder unavailable")]
459 ForwarderUnavailable,
460}
461
462#[derive(thiserror::Error, Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
464#[error("port gone: {port}")]
465pub struct PortGone {
466 pub port: PortAddr,
468
469 pub message_type: Option<String>,
471}
472
473impl PortGone {
474 pub fn new(port: impl Into<PortAddr>, message_type: Option<String>) -> Self {
476 Self {
477 port: port.into(),
478 message_type,
479 }
480 }
481}
482
483#[derive(Debug, Serialize, Deserialize, Clone, typeuri::Named)]
487pub struct MessageEnvelope {
488 sender: ActorAddr,
490
491 dest: PortAddr,
493
494 #[serde(default)]
496 next_hop: Option<PortAddr>,
497
498 data: wirevalue::Any,
500
501 delivery_failures: Vec<DeliveryFailure>,
505
506 headers: Flattrs,
508
509 ttl: u8,
511
512 return_undeliverable: bool,
515 }
517wirevalue::register_type!(MessageEnvelope);
518
519impl MessageEnvelope {
520 pub fn new(
522 sender: impl Into<ActorAddr>,
523 dest: impl Into<PortAddr>,
524 data: wirevalue::Any,
525 headers: Flattrs,
526 ) -> Self {
527 let sender = sender.into();
528 let dest = dest.into();
529 Self {
530 sender,
531 dest,
532 next_hop: None,
533 data,
534 delivery_failures: Vec::new(),
535 headers,
536 ttl: hyperactor_config::global::get(crate::config::MESSAGE_TTL_DEFAULT),
537 return_undeliverable: true,
539 }
540 }
541
542 pub(crate) fn new_unknown(dest: impl Into<PortAddr>, data: wirevalue::Any) -> Self {
544 let unknown_addr = ChannelAddr::any(ChannelTransport::Local);
546 let unknown_proc_ref = ProcAddr::instance(unknown_addr, "unknown");
547 let unknown_actor_ref =
548 ActorAddr::root(unknown_proc_ref, crate::id::Label::strip("unknown"));
549 Self::new(unknown_actor_ref, dest, data, Flattrs::new())
550 }
551
552 pub fn serialize<T: Serialize + Named>(
554 source: impl Into<ActorAddr>,
555 dest: impl Into<PortAddr>,
556 value: &T,
557 headers: Flattrs,
558 ) -> Result<Self, wirevalue::Error> {
559 Ok(Self::new(
560 source,
561 dest,
562 wirevalue::Any::serialize(value)?,
563 headers,
564 ))
565 }
566
567 pub fn ttl(&self) -> u8 {
573 self.ttl
574 }
575
576 pub fn set_ttl(mut self, ttl: u8) -> Self {
586 self.ttl = ttl;
587 self
588 }
589
590 fn decrement_ttl(&mut self) -> bool {
594 if self.ttl == 0 {
595 false
596 } else {
597 self.ttl -= 1;
598 true
599 }
600 }
601
602 pub fn deserialized<T: DeserializeOwned + Named>(&self) -> Result<T, anyhow::Error> {
604 Ok(self.data.deserialized()?)
605 }
606
607 pub fn data(&self) -> &wirevalue::Any {
609 &self.data
610 }
611
612 pub fn sender(&self) -> &ActorAddr {
614 &self.sender
615 }
616
617 pub fn dest(&self) -> &PortAddr {
619 &self.dest
620 }
621
622 pub(crate) fn next_hop(&self) -> &PortAddr {
624 self.next_hop.as_ref().unwrap_or(&self.dest)
625 }
626
627 pub(crate) fn has_next_hop(&self) -> bool {
630 self.next_hop.is_some()
631 }
632
633 pub fn with_dest(mut self, dest: PortAddr) -> Self {
635 self.dest = dest;
636 self.next_hop = None;
637 self
638 }
639
640 pub(crate) fn with_next_hop(mut self, dest: PortAddr) -> Self {
642 self.next_hop = if dest == self.dest { None } else { Some(dest) };
643 self
644 }
645
646 pub fn headers(&self) -> &Flattrs {
648 &self.headers
649 }
650
651 pub fn is_signal(&self) -> bool {
653 self.dest
654 .is_control_port_kind(crate::port::ControlPort::Signal)
655 }
656
657 pub fn push_delivery_failure(&mut self, failure: DeliveryFailure) {
659 self.delivery_failures.push(failure)
660 }
661
662 pub fn ensure_root_delivery_failure(&mut self, failure: impl FnOnce() -> DeliveryFailure) {
665 if self.root_delivery_failure().is_none() {
666 self.push_delivery_failure(failure());
667 }
668 }
669
670 pub fn update_sender(&mut self, sender: impl Into<ActorAddr>) {
672 self.sender = sender.into();
673 }
674
675 pub fn set_return_undeliverable(&mut self, return_undeliverable: bool) {
679 self.return_undeliverable = return_undeliverable;
680 }
681
682 pub fn undeliverable(
685 mut self,
686 failure: DeliveryFailure,
687 return_handle: PortHandle<Undeliverable<MessageEnvelope>>,
688 ) {
689 let error = failure.render_bounded();
690 tracing::debug!(
691 name = "undelivered_message_attempt",
692 sender = self.sender.to_string(),
693 dest = self.dest.to_string(),
694 error = %error,
695 return_handle = %return_handle,
696 );
697 metrics::MAILBOX_UNDELIVERABLE_MESSAGES.add(
698 1,
699 hyperactor_telemetry::kv_pairs!(
700 "sender_actor_id" => self.sender.to_string(),
701 "dest_actor_id" => self.dest.to_string(),
702 "message_type" => self.data.typename().unwrap_or("unknown"),
703 "error_type" => error,
704 ),
705 );
706
707 self.push_delivery_failure(failure);
708 undeliverable::return_undeliverable(return_handle, self);
709 }
710
711 pub fn delivery_failures(&self) -> &[DeliveryFailure] {
714 &self.delivery_failures
715 }
716
717 pub fn root_delivery_failure(&self) -> Option<&DeliveryFailure> {
719 self.delivery_failures.first()
720 }
721
722 pub fn root_delivery_failure_mut(&mut self) -> Option<&mut DeliveryFailure> {
724 self.delivery_failures.first_mut()
725 }
726
727 pub fn error_msg(&self) -> Option<String> {
731 if !self.delivery_failures.is_empty() {
732 return Some(
733 self.delivery_failures
734 .iter()
735 .map(DeliveryFailure::render_bounded)
736 .collect::<Vec<_>>()
737 .join("; "),
738 );
739 }
740
741 None
742 }
743
744 fn open(self) -> (MessageMetadata, wirevalue::Any) {
745 let Self {
746 sender,
747 dest,
748 next_hop,
749 data,
750 delivery_failures,
751 headers,
752 ttl,
753 return_undeliverable,
754 } = self;
755
756 (
757 MessageMetadata {
758 sender,
759 dest,
760 next_hop,
761 delivery_failures,
762 headers,
763 ttl,
764 return_undeliverable,
765 },
766 data,
767 )
768 }
769
770 fn seal(metadata: MessageMetadata, data: wirevalue::Any) -> Self {
771 let MessageMetadata {
772 sender,
773 dest,
774 next_hop,
775 delivery_failures,
776 headers,
777 ttl,
778 return_undeliverable,
779 } = metadata;
780
781 Self {
782 sender,
783 dest,
784 next_hop,
785 data,
786 delivery_failures,
787 headers,
788 ttl,
789 return_undeliverable,
790 }
791 }
792
793 fn return_undeliverable(&self) -> bool {
794 self.return_undeliverable
795 }
796
797 pub fn set_header<T: Serialize>(&mut self, key: hyperactor_config::attrs::Key<T>, value: T) {
799 self.headers.set(key, value);
800 }
801}
802
803impl fmt::Display for MessageEnvelope {
804 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
805 match &self.error_msg() {
806 None => write!(
807 f,
808 "{} > {}: {} {{{}}}",
809 self.sender, self.dest, self.data, self.headers
810 ),
811 Some(err) => write!(
812 f,
813 "{} > {}: {} {{{}}}: delivery error: {}",
814 self.sender, self.dest, self.data, self.headers, err
815 ),
816 }
817 }
818}
819
820#[derive(Clone)]
822pub struct MessageMetadata {
823 sender: ActorAddr,
824 dest: PortAddr,
825 next_hop: Option<PortAddr>,
826 delivery_failures: Vec<DeliveryFailure>,
830 headers: Flattrs,
831 ttl: u8,
832 return_undeliverable: bool,
833}
834
835#[derive(Debug)]
838pub struct MailboxError {
839 actor_id: ActorAddr,
840 kind: MailboxErrorKind,
841}
842
843#[derive(thiserror::Error, Debug)]
846#[non_exhaustive]
847pub enum MailboxErrorKind {
848 #[error("mailbox closed")]
850 Closed,
851
852 #[error("invalid port: {0}")]
854 InvalidPort(PortAddr),
855
856 #[error("no sender for port: {0}")]
858 NoSenderForPort(PortAddr),
859
860 #[error("no local sender for port: {0}")]
863 NoLocalSenderForPort(PortAddr),
864
865 #[error("{0}: port closed")]
867 PortClosed(PortAddr),
868
869 #[error("send {0}: {1}")]
871 Send(PortAddr, #[source] anyhow::Error),
872
873 #[error("recv {0}: {1}")]
875 Recv(PortAddr, #[source] anyhow::Error),
876
877 #[error("serialize: {0}")]
879 Serialize(#[source] anyhow::Error),
880
881 #[error("deserialize {0}: {1}")]
883 Deserialize(&'static str, anyhow::Error),
884
885 #[error(transparent)]
887 Channel(#[from] ChannelError),
888
889 #[error("owner terminated: {0}")]
891 OwnerTerminated(ActorStatus),
892}
893
894impl MailboxError {
895 pub fn new(actor_id: impl Into<ActorAddr>, kind: MailboxErrorKind) -> Self {
898 Self {
899 actor_id: actor_id.into(),
900 kind,
901 }
902 }
903
904 pub fn actor_addr(&self) -> &ActorAddr {
906 &self.actor_id
907 }
908
909 pub fn kind(&self) -> &MailboxErrorKind {
911 &self.kind
912 }
913}
914
915impl fmt::Display for MailboxError {
916 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
917 write!(f, "{}: ", self.actor_id)?;
918 fmt::Display::fmt(&self.kind, f)
919 }
920}
921
922impl std::error::Error for MailboxError {
923 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
924 self.kind.source()
925 }
926}
927
928#[derive(Debug, Clone)]
932pub enum PortLocation {
933 Bound(PortAddr),
935 Unbound(ActorAddr, &'static str),
937}
938
939impl PortLocation {
940 fn new_unbound<M: Message>(actor_id: ActorAddr) -> Self {
941 PortLocation::Unbound(actor_id, std::any::type_name::<M>())
942 }
943
944 #[allow(dead_code)]
945 fn new_unbound_type(actor_id: ActorAddr, ty: &'static str) -> Self {
946 PortLocation::Unbound(actor_id, ty)
947 }
948
949 pub fn actor_addr(&self) -> ActorAddr {
951 match self {
952 PortLocation::Bound(port_addr) => port_addr.actor_addr(),
953 PortLocation::Unbound(actor_addr, _) => actor_addr.clone(),
954 }
955 }
956}
957
958impl fmt::Display for PortLocation {
959 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
960 match self {
961 PortLocation::Bound(port_ref) => write!(f, "{}", port_ref),
962 PortLocation::Unbound(actor_ref, name) => write!(f, "{}<{}>", actor_ref, name),
963 }
964 }
965}
966
967#[derive(Debug)]
970pub struct MailboxSenderError {
971 location: Box<PortLocation>,
972 kind: Box<MailboxSenderErrorKind>,
973}
974
975#[derive(thiserror::Error, Debug)]
977pub enum MailboxSenderErrorKind {
978 #[error("serialization error: {0}")]
980 Serialize(anyhow::Error),
981
982 #[error("deserialization error for type {0}: {1}")]
984 Deserialize(&'static str, anyhow::Error),
985
986 #[error("invalid port")]
988 Invalid,
989
990 #[error("port closed")]
992 Closed,
993
994 #[error(transparent)]
997 Mailbox(#[from] MailboxError),
998
999 #[error(transparent)]
1001 Channel(#[from] ChannelError),
1002
1003 #[error("send error: {0}")]
1005 Other(#[from] anyhow::Error),
1006
1007 #[error("unreachable: {0}")]
1009 Unreachable(anyhow::Error),
1010}
1011
1012impl MailboxSenderError {
1013 pub fn new_unbound<M>(actor_id: impl Into<ActorAddr>, kind: MailboxSenderErrorKind) -> Self {
1015 Self {
1016 location: Box::new(PortLocation::Unbound(
1017 actor_id.into(),
1018 std::any::type_name::<M>(),
1019 )),
1020 kind: Box::new(kind),
1021 }
1022 }
1023
1024 pub fn new_unbound_type(
1026 actor_id: impl Into<ActorAddr>,
1027 kind: MailboxSenderErrorKind,
1028 ty: &'static str,
1029 ) -> Self {
1030 Self {
1031 location: Box::new(PortLocation::Unbound(actor_id.into(), ty)),
1032 kind: Box::new(kind),
1033 }
1034 }
1035
1036 pub fn new_bound(port_id: impl Into<PortAddr>, kind: MailboxSenderErrorKind) -> Self {
1038 Self {
1039 location: Box::new(PortLocation::Bound(port_id.into())),
1040 kind: Box::new(kind),
1041 }
1042 }
1043
1044 pub fn location(&self) -> &PortLocation {
1046 &self.location
1047 }
1048
1049 pub fn kind(&self) -> &MailboxSenderErrorKind {
1051 &self.kind
1052 }
1053}
1054
1055impl fmt::Display for MailboxSenderError {
1056 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1057 write!(f, "{}: ", self.location)?;
1058 fmt::Display::fmt(&self.kind, f)
1059 }
1060}
1061
1062impl std::error::Error for MailboxSenderError {
1063 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
1064 self.kind.source()
1065 }
1066}
1067
1068#[async_trait]
1071pub trait MailboxSender: Send + Sync + Any {
1072 fn post(
1075 &self,
1076 mut envelope: MessageEnvelope,
1077 return_handle: PortHandle<Undeliverable<MessageEnvelope>>,
1078 ) {
1079 if !envelope.decrement_ttl() {
1080 let failure = DeliveryFailure::new(ExpiredDelivery::new(envelope.dest().clone()));
1081 envelope.undeliverable(failure, return_handle);
1082 return;
1083 }
1084 self.post_unchecked(envelope, return_handle);
1085 }
1086
1087 fn post_unchecked(
1089 &self,
1090 envelope: MessageEnvelope,
1091 return_handle: PortHandle<Undeliverable<MessageEnvelope>>,
1092 );
1093
1094 async fn flush(&self) -> Result<(), anyhow::Error> {
1099 Ok(())
1100 }
1101}
1102
1103pub trait PortSender: MailboxSender {
1106 fn serialize_and_send<M: RemoteMessage>(
1108 &self,
1109 port: &PortRef<M>,
1110 message: M,
1111 return_handle: PortHandle<Undeliverable<MessageEnvelope>>,
1112 ) -> Result<(), MailboxSenderError> {
1113 let serialized = wirevalue::Any::serialize(&message).map_err(|err| {
1115 MailboxSenderError::new_bound(
1116 port.port_addr().clone(),
1117 MailboxSenderErrorKind::Serialize(err.into()),
1118 )
1119 })?;
1120 let mut envelope = MessageEnvelope::new_unknown(port.port_addr().clone(), serialized);
1121 envelope.set_header(SEQ_INFO, SeqInfo::Direct);
1122 envelope.set_return_undeliverable(port.get_return_undeliverable());
1123 self.post(envelope, return_handle);
1124 Ok(())
1125 }
1126
1127 fn serialize_and_send_once<M: RemoteMessage>(
1130 &self,
1131 once_port: OncePortRef<M>,
1132 message: M,
1133 return_handle: PortHandle<Undeliverable<MessageEnvelope>>,
1134 ) -> Result<(), MailboxSenderError> {
1135 let serialized = wirevalue::Any::serialize(&message).map_err(|err| {
1136 MailboxSenderError::new_bound(
1137 once_port.port_addr().clone(),
1138 MailboxSenderErrorKind::Serialize(err.into()),
1139 )
1140 })?;
1141 let mut envelope = MessageEnvelope::new_unknown(once_port.port_addr().clone(), serialized);
1142 envelope.set_header(SEQ_INFO, SeqInfo::Direct);
1143 envelope.set_return_undeliverable(once_port.get_return_undeliverable());
1144 self.post(envelope, return_handle);
1145 Ok(())
1146 }
1147}
1148
1149impl<T: ?Sized + MailboxSender> PortSender for T {}
1150
1151#[derive(Debug, Clone)]
1155pub struct PanickingMailboxSender;
1156
1157#[async_trait]
1158impl MailboxSender for PanickingMailboxSender {
1159 fn post_unchecked(
1160 &self,
1161 envelope: MessageEnvelope,
1162 _return_handle: PortHandle<Undeliverable<MessageEnvelope>>,
1163 ) {
1164 panic!("panic! in the mailbox! attempted post: {}", envelope)
1165 }
1166}
1167
1168#[derive(Debug)]
1171pub struct UndeliverableMailboxSender;
1172
1173#[async_trait]
1174impl MailboxSender for UndeliverableMailboxSender {
1175 fn post_unchecked(
1176 &self,
1177 envelope: MessageEnvelope,
1178 _return_handle: PortHandle<Undeliverable<MessageEnvelope>>,
1179 ) {
1180 let sender_name = envelope
1181 .sender
1182 .label()
1183 .map_or("?".to_string(), |l| l.to_string());
1184 let error_str = envelope.error_msg().unwrap_or("".to_string());
1185 let operation_endpoint = envelope.headers().get(headers::OPERATION_ENDPOINT);
1186 let operation_adverb = envelope.headers().get(headers::OPERATION_ADVERB);
1187 match &operation_endpoint {
1189 Some(endpoint) => tracing::error!(
1190 name = "undelivered_message_abandoned",
1191 actor_name = sender_name,
1192 actor_id = envelope.sender.to_string(),
1193 dest = envelope.dest.to_string(),
1194 message_type = envelope.data().typename().unwrap_or("unknown"),
1195 data_len = envelope.data().len(),
1196 endpoint = %endpoint,
1197 adverb = operation_adverb.as_deref().unwrap_or(""),
1198 error = %error_str,
1199 "abandoned message for {}",
1200 endpoint,
1201 ),
1202 None => tracing::error!(
1203 name = "undelivered_message_abandoned",
1204 actor_name = sender_name,
1205 actor_id = envelope.sender.to_string(),
1206 dest = envelope.dest.to_string(),
1207 message_type = envelope.data().typename().unwrap_or("unknown"),
1208 data_len = envelope.data().len(),
1209 error = %error_str,
1210 "message not delivered to {}",
1211 envelope.dest,
1212 ),
1213 }
1214 }
1215}
1216
1217#[derive(Clone)]
1223pub struct BoxedMailboxSender(Arc<dyn MailboxSender + Send + Sync + 'static>);
1224
1225impl fmt::Debug for BoxedMailboxSender {
1226 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1227 f.debug_struct("BoxedMailboxSender")
1228 .field("sender", &"<dyn MailboxSender>")
1229 .finish()
1230 }
1231}
1232
1233impl BoxedMailboxSender {
1234 pub fn new(sender: impl MailboxSender + 'static) -> Self {
1236 Self(Arc::new(sender))
1237 }
1238
1239 pub fn downcast_ref<T: 'static>(&self) -> Option<&T> {
1242 (&*self.0 as &dyn Any).downcast_ref::<T>()
1243 }
1244}
1245
1246pub trait BoxableMailboxSender: MailboxSender + Clone + 'static {
1248 fn boxed(&self) -> BoxedMailboxSender;
1250}
1251impl<T: MailboxSender + Clone + 'static> BoxableMailboxSender for T {
1252 fn boxed(&self) -> BoxedMailboxSender {
1253 BoxedMailboxSender::new(self.clone())
1254 }
1255}
1256
1257pub trait IntoBoxedMailboxSender: MailboxSender {
1259 fn into_boxed(self) -> BoxedMailboxSender;
1261}
1262impl<T: MailboxSender + 'static> IntoBoxedMailboxSender for T {
1263 fn into_boxed(self) -> BoxedMailboxSender {
1264 BoxedMailboxSender::new(self)
1265 }
1266}
1267
1268#[async_trait]
1269impl MailboxSender for BoxedMailboxSender {
1270 fn post_unchecked(
1271 &self,
1272 envelope: MessageEnvelope,
1273 return_handle: PortHandle<Undeliverable<MessageEnvelope>>,
1274 ) {
1275 self.0.post_unchecked(envelope, return_handle);
1276 }
1277
1278 async fn flush(&self) -> Result<(), anyhow::Error> {
1279 self.0.flush().await
1280 }
1281}
1282
1283#[derive(thiserror::Error, Debug)]
1285pub enum MailboxServerError {
1286 #[error(transparent)]
1288 Channel(#[from] ChannelError),
1289
1290 #[error(transparent)]
1292 MailboxSender(#[from] MailboxSenderError),
1293}
1294
1295#[derive(Debug)]
1298pub struct MailboxServerHandle {
1299 join_handle: JoinHandle<Result<(), MailboxServerError>>,
1300 stopped_tx: watch::Sender<bool>,
1301}
1302
1303impl MailboxServerHandle {
1304 pub fn stop(&self, reason: &str) {
1307 tracing::info!("stopping mailbox server; reason: {}", reason);
1308 let _ = self.stopped_tx.send(true);
1313 }
1314
1315 pub fn from_parts(
1320 join_handle: JoinHandle<Result<(), MailboxServerError>>,
1321 stopped_tx: watch::Sender<bool>,
1322 ) -> Self {
1323 Self {
1324 join_handle,
1325 stopped_tx,
1326 }
1327 }
1328}
1329
1330impl Future for MailboxServerHandle {
1332 type Output = <JoinHandle<Result<(), MailboxServerError>> as Future>::Output;
1333
1334 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
1335 let join_handle_pinned =
1337 unsafe { self.map_unchecked_mut(|container| &mut container.join_handle) };
1338 join_handle_pinned.poll(cx)
1339 }
1340}
1341
1342pub trait MailboxServer: MailboxSender + Clone + Sized + 'static {
1345 fn serve(
1349 self,
1350 mut rx: impl channel::Rx<MessageEnvelope> + Send + 'static,
1351 ) -> MailboxServerHandle {
1352 let (return_handle, mut undeliverable_rx) = undeliverable::new_undeliverable_port();
1357 tokio::task::spawn(async move {
1358 let client = crate::client("undeliverable_supervisor");
1359 while let Ok(undeliverable) = undeliverable_rx.recv().await {
1360 match undeliverable {
1361 Undeliverable::Returned(mut envelope) => {
1362 match envelope.deserialized::<Undeliverable<MessageEnvelope>>() {
1363 Ok(Undeliverable::Returned(e)) => {
1364 UndeliverableMailboxSender.post(e, monitored_return_handle());
1366 continue;
1367 }
1368 Ok(Undeliverable::Report(report)) => {
1369 tracing::error!(
1370 sender = %report.sender,
1371 dest = %report.dest,
1372 message_type = report.message_type.as_deref().unwrap_or("unknown"),
1373 error = %report.error_msg().unwrap_or_default(),
1374 "undeliverable message report was undeliverable"
1375 );
1376 continue;
1377 }
1378 Err(_) => {}
1379 }
1380 let target = envelope.dest().clone();
1381 envelope.ensure_root_delivery_failure(|| {
1382 DeliveryFailure::new(UndeliverableReason::Transport(
1383 TransportFailure::new(
1384 target,
1385 TransportFailureReason::LinkUnavailable(
1386 "message was undeliverable".to_owned(),
1387 ),
1388 ),
1389 ))
1390 });
1391 let sender_id: ActorAddr = envelope.sender().clone();
1392 let return_port =
1393 PortRef::<Undeliverable<MessageEnvelope>>::attest_handler_port(
1394 &sender_id,
1395 );
1396 return_port.post_serialized(
1397 &client,
1398 Flattrs::new(),
1399 wirevalue::Any::serialize(&Undeliverable::Returned(envelope)).unwrap(),
1400 );
1401 }
1402 Undeliverable::Report(report) => {
1403 tracing::error!(
1404 sender = %report.sender,
1405 dest = %report.dest,
1406 message_type = report.message_type.as_deref().unwrap_or("unknown"),
1407 error = %report.error_msg().unwrap_or_default(),
1408 "undeliverable message report was undeliverable"
1409 );
1410 }
1411 }
1412 }
1413 });
1414
1415 let (stopped_tx, mut stopped_rx) = watch::channel(false);
1416 let join_handle = tokio::spawn(async move {
1417 let mut detached = false;
1418
1419 let result = loop {
1420 if *stopped_rx.borrow_and_update() {
1421 break Ok(());
1422 }
1423
1424 tokio::select! {
1425 message = rx.recv() => {
1426 match message {
1427 Ok(envelope) => self.post(envelope, return_handle.clone()),
1429
1430 Err(ChannelError::Closed) => break Ok(()),
1433 Err(channel_err) => break Err(MailboxServerError::from(channel_err)),
1434 }
1435 }
1436 result = stopped_rx.changed(), if !detached => {
1437 detached = result.is_err();
1438 if detached {
1439 tracing::debug!(
1440 "the mailbox server is detached for Rx {}", rx.addr()
1441 );
1442 } else {
1443 tracing::debug!(
1444 "the mailbox server is stopped for Rx {}", rx.addr()
1445 );
1446 }
1447 }
1448 }
1449 };
1450
1451 rx.join().await;
1454
1455 result
1456 }.instrument(tracing::debug_span!("MailboxServer")));
1457
1458 MailboxServerHandle {
1459 join_handle,
1460 stopped_tx,
1461 }
1462 }
1463}
1464
1465impl<T: MailboxSender + Clone + Sized + Sync + Send + 'static> MailboxServer for T {}
1466
1467struct Buffer<T: Message> {
1468 queue: mpsc::UnboundedSender<(T, PortHandle<Undeliverable<T>>)>,
1469 #[allow(dead_code)]
1470 processed: watch::Receiver<usize>,
1471 seq: AtomicUsize,
1472}
1473
1474impl<T: Message> Buffer<T> {
1475 fn new<Fut>(
1476 process: impl Fn(T, PortHandle<Undeliverable<T>>) -> Fut + Send + Sync + 'static,
1477 ) -> Self
1478 where
1479 Fut: Future<Output = ()> + Send + 'static,
1480 {
1481 let (queue, mut next) = mpsc::unbounded_channel();
1482 let (last_processed, processed) = watch::channel(0);
1483 crate::init::get_runtime().spawn(
1484 async move {
1485 let mut seq = 0;
1486 while let Some((msg, return_handle)) = next.recv().await {
1487 process(msg, return_handle).await;
1488 seq += 1;
1489 let _ = last_processed.send(seq);
1490 }
1491 }
1492 .instrument(tracing::debug_span!("mailbox::Buffer")),
1493 );
1494 Self {
1495 queue,
1496 processed,
1497 seq: AtomicUsize::new(0),
1498 }
1499 }
1500
1501 fn send(
1502 &self,
1503 item: (T, PortHandle<Undeliverable<T>>),
1504 ) -> Result<(), Box<mpsc::error::SendError<(T, PortHandle<Undeliverable<T>>)>>> {
1505 self.seq.fetch_add(1, Ordering::SeqCst);
1506 self.queue.send(item).map_err(Box::new)?;
1507 Ok(())
1508 }
1509}
1510
1511pub struct MailboxClient {
1513 addr: ChannelAddr,
1515
1516 buffer: Buffer<MessageEnvelope>,
1518
1519 _tx_monitoring: CancellationToken,
1521
1522 submitted: Arc<AtomicUsize>,
1524 completed: Arc<AtomicUsize>,
1527 completed_notify: Arc<tokio::sync::Notify>,
1529
1530 tx_status: watch::Receiver<TxStatus>,
1533}
1534
1535impl fmt::Debug for MailboxClient {
1536 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1537 f.debug_struct("MailboxClient")
1538 .field("buffer", &"<Buffer>")
1539 .finish()
1540 }
1541}
1542
1543impl MailboxClient {
1544 pub fn new(tx: impl channel::Tx<MessageEnvelope> + Send + Sync + 'static) -> Self {
1547 let addr = tx.addr();
1548 let tx = Arc::new(tx);
1549 let tx_status = tx.status().clone();
1550 let tx_monitoring = CancellationToken::new();
1551 let completed = Arc::new(AtomicUsize::new(0));
1552 let completed_notify = Arc::new(tokio::sync::Notify::new());
1553 let buffer = {
1554 let completed = completed.clone();
1555 let completed_notify = completed_notify.clone();
1556 let addr = addr.clone();
1557 Buffer::new(move |envelope, return_handle| {
1558 let tx = Arc::clone(&tx);
1559 let addr = addr.clone();
1560 let return_handle_0 = return_handle.clone();
1562 let tracker =
1563 channel::CompletionTracker::new(completed.clone(), completed_notify.clone());
1564 let completion = CompletionSink::tracked(
1565 tracker,
1566 move |send_error: SendError<MessageEnvelope>| {
1567 let SendError {
1568 error,
1569 message,
1570 reason,
1571 } = send_error;
1572 let target = message.dest().clone();
1573 let reason_text = reason
1574 .as_ref()
1575 .map(ToString::to_string)
1576 .unwrap_or_else(|| "channel closed".to_owned());
1577 let reason = match reason {
1578 Some(SendErrorReason::OversizedFrame { len, max }) => {
1579 TransportFailureReason::OversizedFrame { len, max }
1580 }
1581 Some(SendErrorReason::Other(_)) | None => {
1582 TransportFailureReason::ChannelClosed { addr }
1583 }
1584 };
1585 let failure = DeliveryFailure::new(UndeliverableReason::Transport(
1586 TransportFailure::new(target, reason.clone()),
1587 ));
1588 tracing::debug!(
1589 %error,
1590 send_error_reason = %reason_text,
1591 ?reason,
1592 "failed to enqueue in mailbox client while processing buffer",
1593 );
1594 message.undeliverable(failure, return_handle_0);
1595 },
1596 );
1597 tx.do_post(envelope, completion);
1599 future::ready(())
1600 })
1601 };
1602 let this = Self {
1603 addr: addr.clone(),
1604 buffer,
1605 _tx_monitoring: tx_monitoring.clone(),
1606 submitted: Arc::new(AtomicUsize::new(0)),
1607 completed,
1608 completed_notify,
1609 tx_status: tx_status.clone(),
1610 };
1611 Self::monitor_tx_health(tx_status, tx_monitoring, addr);
1612 this
1613 }
1614
1615 pub fn tx_status(&self) -> &watch::Receiver<TxStatus> {
1619 &self.tx_status
1620 }
1621
1622 pub fn dial(addr: ChannelAddr) -> Result<MailboxClient, ChannelError> {
1625 Ok(MailboxClient::new(channel::dial(addr)?))
1626 }
1627
1628 fn monitor_tx_health(
1630 mut rx: watch::Receiver<TxStatus>,
1631 cancel_token: CancellationToken,
1632 addr: ChannelAddr,
1633 ) {
1634 crate::init::get_runtime().spawn(async move {
1635 loop {
1636 tokio::select! {
1637 changed = rx.changed() => {
1638 if changed.is_err() || rx.borrow().is_closed() {
1639 let reason = rx.borrow().as_closed().map(|r| r.to_string()).unwrap_or_else(|| "unknown".to_string());
1640 tracing::warn!("connection to {} lost: {}", addr, reason);
1641 break;
1644 }
1645 }
1646 _ = cancel_token.cancelled() => {
1647 break;
1648 }
1649 }
1650 }
1651 });
1652 }
1653}
1654
1655#[async_trait]
1656impl MailboxSender for MailboxClient {
1657 fn post_unchecked(
1661 &self,
1662 envelope: MessageEnvelope,
1663 return_handle: PortHandle<Undeliverable<MessageEnvelope>>,
1664 ) {
1665 tracing::event!(target:"messages", tracing::Level::TRACE, "size"=envelope.data.len(), "sender"= %envelope.sender, "dest" = %envelope.dest.actor_addr(), "port"= envelope.dest.index(), "message_type" = envelope.data.typename().unwrap_or("unknown"), "send_message");
1666 if let Err(err) = self.buffer.send((envelope, return_handle)) {
1667 let mpsc::error::SendError((envelope, return_handle)) = *err;
1668 let target = envelope.dest().clone();
1669 let failure =
1670 DeliveryFailure::new(UndeliverableReason::Transport(TransportFailure::new(
1671 target,
1672 TransportFailureReason::LinkUnavailable(format!(
1673 "mailbox client buffer is closed for {}",
1674 self.addr
1675 )),
1676 )));
1677
1678 envelope.undeliverable(failure, return_handle);
1680 } else {
1681 self.submitted.fetch_add(1, Ordering::Relaxed);
1682 }
1683 }
1684
1685 async fn flush(&self) -> Result<(), anyhow::Error> {
1686 let target = self.submitted.load(Ordering::Relaxed);
1687 loop {
1688 let notified = self.completed_notify.notified();
1691 if self.completed.load(Ordering::Relaxed) >= target {
1692 return Ok(());
1693 }
1694 notified.await;
1695 }
1696 }
1697}
1698
1699pub struct PortSink<C: context::Actor, M: RemoteMessage> {
1701 cx: C,
1702 port: PortRef<M>,
1703}
1704
1705impl<C: context::Actor, M: RemoteMessage> PortSink<C, M> {
1706 pub fn new(cx: C, port: PortRef<M>) -> Self {
1708 Self { cx, port }
1709 }
1710}
1711
1712impl<C: context::Actor, M: RemoteMessage> Sink<M> for PortSink<C, M> {
1713 type Error = MailboxSenderError;
1714
1715 fn poll_ready(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
1716 Poll::Ready(Ok(()))
1717 }
1718
1719 fn start_send(self: Pin<&mut Self>, item: M) -> Result<(), Self::Error> {
1720 crate::Endpoint::post(&self.port, &self.cx, item);
1721 Ok(())
1722 }
1723
1724 fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
1725 Poll::Ready(Ok(()))
1726 }
1727
1728 fn poll_close(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
1729 Poll::Ready(Ok(()))
1730 }
1731}
1732
1733#[derive(Clone, Debug)]
1736pub struct Mailbox {
1737 inner: Arc<State>,
1738}
1739
1740#[cfg(not(hyperactor_verify_auto_traits))]
1743mod _send_sync_shortcut {
1744 use super::*;
1745
1746 unsafe impl Send for Mailbox {}
1748 unsafe impl Sync for Mailbox {}
1750}
1751
1752const _: fn() = || {
1753 fn assert<T: Send + Sync + ?Sized>() {}
1754 assert::<Mailbox>();
1755};
1756
1757impl Mailbox {
1758 pub fn new(actor_id: impl Into<ActorAddr>) -> Self {
1760 Self {
1761 inner: Arc::new(State::new(actor_id.into())),
1762 }
1763 }
1764
1765 pub fn actor_addr(&self) -> &ActorAddr {
1767 &self.inner.actor_id
1768 }
1769
1770 pub fn open_port<M: Message>(&self) -> (PortHandle<M>, PortReceiver<M>) {
1775 let port_index = self.inner.allocate_port();
1776 let (sender, receiver) = sequenced_unbounded::<SequencedEnvelope<M>>();
1777 let port_id = self.inner.actor_id.port_addr(Port::from(port_index));
1778 self.register_ordering_snapshot(&port_id, &receiver);
1779 tracing::trace!(
1780 name = "open_port",
1781 "opening port for {} at {}",
1782 self.inner.actor_id,
1783 port_id
1784 );
1785 (
1786 PortHandle::new(
1787 self.clone(),
1788 port_index,
1789 UnboundedPortSender::Sequenced(sender),
1790 ),
1791 PortReceiver::new(receiver, port_id, false, self.clone()),
1792 )
1793 }
1794
1795 pub(crate) fn bind_handler_port<M: RemoteMessage>(&self) -> (PortHandle<M>, PortReceiver<M>) {
1806 let (sender, receiver) = sequenced_unbounded::<SequencedEnvelope<M>>();
1807 let port_id = self.inner.actor_id.port_addr(Port::handler::<M>());
1808 self.register_ordering_snapshot(&port_id, &receiver);
1809 let handle = PortHandle::new_full_with_target(
1810 self.clone(),
1811 UnboundedPortSender::Sequenced(sender),
1812 PortBindTarget::Handler,
1813 None,
1814 StreamingReducerOpts::default(),
1815 );
1816 handle.bind_handler_port();
1817 (
1818 handle,
1819 PortReceiver::new(receiver, port_id, false, self.clone()),
1820 )
1821 }
1822
1823 fn register_ordering_snapshot<M: Message>(
1824 &self,
1825 port: &PortAddr,
1826 receiver: &SequencedReceiver<SequencedEnvelope<M>>,
1827 ) {
1828 let snapshot = receiver.snapshot_handle();
1829 self.inner
1830 .ordering_snapshots
1831 .insert(port.port(), Arc::new(move || snapshot.snapshot()));
1832 }
1833
1834 pub(crate) fn ordering_snapshot(&self, port: &Port) -> Option<OrderingSnapshot> {
1835 self.inner
1836 .ordering_snapshots
1837 .get(port)
1838 .map(|snapshot| snapshot.value()())
1839 }
1840
1841 pub fn open_accum_port<A>(&self, accum: A) -> (PortHandle<A::Update>, PortReceiver<A::State>)
1844 where
1845 A: Accumulator + Send + Sync + 'static,
1846 A::Update: Message,
1847 A::State: Message + Default + Clone,
1848 {
1849 self.open_accum_port_opts(accum, StreamingReducerOpts::default())
1850 }
1851
1852 pub fn open_idle_flush_accum_port<A>(
1855 &self,
1856 accum: A,
1857 reducer_opts: IdleFlushReducerOpts,
1858 ) -> (IdleFlushPortHandle<A::Update>, PortReceiver<A::State>)
1859 where
1860 A: Accumulator + Send + Sync + 'static,
1861 A::Update: Message,
1862 A::State: Message + Default + Clone,
1863 {
1864 let (port, receiver) = self.open_accum_port(accum);
1865 (IdleFlushPortHandle { port, reducer_opts }, receiver)
1866 }
1867
1868 pub fn open_accum_port_opts<A>(
1876 &self,
1877 accum: A,
1878 streaming_opts: StreamingReducerOpts,
1879 ) -> (PortHandle<A::Update>, PortReceiver<A::State>)
1880 where
1881 A: Accumulator + Send + Sync + 'static,
1882 A::Update: Message,
1883 A::State: Message + Default + Clone,
1884 {
1885 let port_index = self.inner.allocate_port();
1886 let (sender, receiver) = sequenced_unbounded::<SequencedEnvelope<A::State>>();
1887 let port_id = self.inner.actor_id.port_addr(Port::from(port_index));
1888 let state = Mutex::new(A::State::default());
1889 let reducer_spec = accum.reducer_spec();
1890 let enqueue = move |_, update: A::Update| {
1891 let mut state = state.lock().unwrap();
1892 accum.accumulate(&mut state, update)?;
1893 let _ = sender.send(SequencedEnvelope::new(SeqInfo::Direct, None, state.clone()));
1894 Ok(())
1895 };
1896 (
1897 PortHandle::new_full(
1898 self.clone(),
1899 port_index,
1900 UnboundedPortSender::Func(Arc::new(enqueue)),
1901 reducer_spec,
1902 streaming_opts,
1903 ),
1904 PortReceiver::new(receiver, port_id, true, self.clone()),
1905 )
1906 }
1907
1908 #[cfg(test)]
1912 pub(crate) fn open_enqueue_port<M: Message>(
1913 &self,
1914 enqueue: impl Fn(Flattrs, M) -> Result<(), anyhow::Error> + Send + Sync + 'static,
1915 ) -> PortHandle<M> {
1916 PortHandle::new_full(
1917 self.clone(),
1918 self.inner.allocate_port(),
1919 UnboundedPortSender::Func(Arc::new(enqueue)),
1920 None,
1921 StreamingReducerOpts::default(),
1922 )
1923 }
1924
1925 pub(crate) fn open_handler_enqueue_port<M: Message>(
1928 &self,
1929 enqueue: impl Fn(Flattrs, M) -> Result<(), anyhow::Error> + Send + Sync + 'static,
1930 ) -> PortHandle<M> {
1931 let enqueue = Arc::new(enqueue);
1932 let sender = Arc::new(HandlerPortSender::new(
1933 UnboundedPortSender::Func(enqueue),
1934 self.inner.handler_ingress.clone(),
1935 ));
1936 PortHandle::new_full_with_target(
1937 self.clone(),
1938 UnboundedPortSender::Handler(sender),
1939 PortBindTarget::Handler,
1940 None,
1941 StreamingReducerOpts::default(),
1942 )
1943 }
1944
1945 pub fn open_once_port<M: Message>(&self) -> (OncePortHandle<M>, OncePortReceiver<M>) {
1949 let port_index = self.inner.allocate_port();
1950 let port_id = self.inner.actor_id.port_addr(Port::from(port_index));
1951 let (sender, receiver) = oneshot::channel::<M>();
1952 (
1953 OncePortHandle {
1954 mailbox: self.clone(),
1955 port_id: port_id.clone(),
1956 sender,
1957 reducer_spec: None,
1958 },
1959 OncePortReceiver {
1960 receiver: Some(receiver),
1961 port_id,
1962 mailbox: self.clone(),
1963 },
1964 )
1965 }
1966
1967 pub fn open_reduce_port<A, T>(
1981 &self,
1982 accum: A,
1983 ) -> (OncePortHandle<A::State>, OncePortReceiver<A::State>)
1984 where
1985 A: Accumulator<State = T, Update = T> + Send + Sync + 'static,
1986 T: Message + Default + Clone,
1987 {
1988 let port_index = self.inner.allocate_port();
1989 let (sender, receiver) = oneshot::channel::<T>();
1990 let port_id = self.inner.actor_id.port_addr(Port::from(port_index));
1991 let reducer_spec = accum.reducer_spec();
1992 assert!(
1993 reducer_spec.is_some(),
1994 "cannot use a reduce port without a ReducerSpec"
1995 );
1996
1997 (
1998 OncePortHandle {
1999 mailbox: self.clone(),
2000 port_id: port_id.clone(),
2001 sender,
2002 reducer_spec,
2003 },
2004 OncePortReceiver {
2005 receiver: Some(receiver),
2006 port_id,
2007 mailbox: self.clone(),
2008 },
2009 )
2010 }
2011
2012 #[allow(dead_code)]
2013 fn error(&self, err: MailboxErrorKind) -> MailboxError {
2014 MailboxError::new(self.inner.actor_id.clone(), err)
2015 }
2016
2017 fn lookup_sender<M: RemoteMessage>(&self) -> Option<UnboundedPortSender<M>> {
2018 let port = Port::handler::<M>();
2019 self.inner.ports.get(&port).and_then(|boxed| {
2020 boxed
2021 .as_any()
2022 .downcast_ref::<UnboundedSender<M>>()
2023 .map(|s| {
2024 assert_eq!(
2025 s.port_id,
2026 self.actor_addr().port_addr(port.clone()),
2027 "port_id mismatch in downcasted UnboundedSender"
2028 );
2029 s.sender.clone()
2030 })
2031 })
2032 }
2033
2034 pub fn bound_return_handle(&self) -> Option<PortHandle<Undeliverable<MessageEnvelope>>> {
2036 self.lookup_sender::<Undeliverable<MessageEnvelope>>()
2037 .map(|sender| PortHandle::new(self.clone(), self.inner.allocate_port(), sender))
2038 }
2039
2040 pub(crate) fn set_return_handler_hook(&self, hook: impl Fn() + Send + Sync + 'static) {
2042 assert!(
2043 self.inner.return_handler_hook.set(Box::new(hook)).is_ok(),
2044 "return handler hook should be installed once"
2045 );
2046 }
2047
2048 pub(crate) fn ensure_return_handler(&self) {
2050 if let Some(hook) = self.inner.return_handler_hook.get() {
2051 self.inner.return_handler_init.call_once(hook);
2052 }
2053 }
2054
2055 pub(crate) fn allocate_port(&self) -> u64 {
2056 self.inner.allocate_port()
2057 }
2058
2059 fn bind<M: RemoteMessage>(&self, handle: &PortHandle<M>) -> PortRef<M> {
2060 assert_eq!(
2061 handle.inner.mailbox.actor_addr(),
2062 self.actor_addr(),
2063 "port does not belong to mailbox"
2064 );
2065
2066 let port_ref = self
2069 .actor_addr()
2070 .port_addr(Port::from(handle.inner.bind_target.ephemeral_index()));
2071 match self.inner.ports.entry(port_ref.port()) {
2072 Entry::Vacant(entry) => {
2073 entry.insert(Arc::new(UnboundedSender::new(
2074 handle.inner.sender.clone(),
2075 port_ref.clone(),
2076 )));
2077 }
2078 Entry::Occupied(_entry) => {}
2079 }
2080
2081 PortRef::attest(port_ref)
2082 }
2083
2084 fn bind_to_handler_port<M: RemoteMessage>(&self, handle: &PortHandle<M>) {
2085 self.bind_to_port(handle, Port::handler::<M>());
2086 }
2087
2088 fn bind_to_control_port<M: RemoteMessage>(&self, handle: &PortHandle<M>, port: ControlPort) {
2089 self.bind_to_port(handle, Port::control(port));
2090 }
2091
2092 fn bind_to_port<M: RemoteMessage>(&self, handle: &PortHandle<M>, port: Port) {
2093 assert_eq!(
2094 handle.inner.mailbox.actor_addr(),
2095 self.actor_addr(),
2096 "port does not belong to mailbox"
2097 );
2098
2099 let port_ref = self.actor_addr().port_addr(port.clone());
2100 match self.inner.ports.entry(port) {
2101 Entry::Vacant(entry) => {
2102 entry.insert(Arc::new(UnboundedSender::new(
2103 handle.inner.sender.clone(),
2104 port_ref.clone(),
2105 )));
2106 }
2107 Entry::Occupied(_entry) => panic!("port {} already bound", port_ref),
2108 }
2109 }
2110
2111 fn bind_once<M: RemoteMessage>(&self, handle: OncePortHandle<M>) {
2112 let port_id = handle.port_addr().clone();
2113 match self.inner.ports.entry(port_id.port()) {
2114 Entry::Vacant(entry) => {
2115 entry.insert(Arc::new(OnceSender::new(handle.sender, port_id.clone())));
2116 }
2117 Entry::Occupied(_entry) => {}
2118 }
2119 }
2120
2121 pub(crate) fn bind_untyped(&self, port_id: &PortAddr, sender: UntypedUnboundedSender) {
2122 assert_eq!(
2123 port_id.actor_addr(),
2124 *self.actor_addr(),
2125 "port does not belong to mailbox"
2126 );
2127
2128 match self.inner.ports.entry(port_id.port()) {
2129 Entry::Vacant(entry) => {
2130 entry.insert(Arc::new(sender));
2131 }
2132 Entry::Occupied(_entry) => {}
2133 }
2134 }
2135
2136 pub(crate) fn unbind_untyped(&self, port_id: &PortAddr) {
2137 assert_eq!(
2138 port_id.actor_addr(),
2139 *self.actor_addr(),
2140 "port does not belong to mailbox"
2141 );
2142
2143 self.inner.ports.remove(&port_id.port());
2144 }
2145
2146 pub(crate) fn close(&self, status: ActorStatus) {
2147 let mut closed = self.inner.closed.write().unwrap();
2148 if closed.is_some() {
2149 panic!("mailbox with owner {} already closed", self.actor_addr());
2150 }
2151 let _ = closed.insert(status);
2152 }
2153
2154 pub(crate) fn drain(&self) {
2172 self.inner.handler_ingress.drain();
2173 }
2174}
2175
2176impl context::Mailbox for Mailbox {
2177 fn mailbox(&self) -> &Mailbox {
2178 self
2179 }
2180}
2181
2182pub fn open_port<M: Message>(cx: &impl context::Mailbox) -> (PortHandle<M>, PortReceiver<M>) {
2187 cx.mailbox().open_port()
2188}
2189
2190pub fn open_once_port<M: Message>(
2193 cx: &impl context::Mailbox,
2194) -> (OncePortHandle<M>, OncePortReceiver<M>) {
2195 cx.mailbox().open_once_port()
2196}
2197
2198#[async_trait]
2199impl MailboxSender for Mailbox {
2200 fn post_unchecked(
2206 &self,
2207 envelope: MessageEnvelope,
2208 return_handle: PortHandle<Undeliverable<MessageEnvelope>>,
2209 ) {
2210 metrics::MAILBOX_POSTS.add(
2211 1,
2212 hyperactor_telemetry::kv_pairs!(
2213 "actor_id" => envelope.sender.to_string(),
2214 "dest_actor_id" => envelope.dest.actor_addr().to_string(),
2215 ),
2216 );
2217 tracing::trace!(
2218 name = "post",
2219 actor_name = envelope.sender.label().map_or("?", |l| l.as_str()),
2220 actor_id = envelope.sender.to_string(),
2221 "posting message to {}",
2222 envelope.dest
2223 );
2224
2225 if envelope.dest().actor_id() != self.inner.actor_id.id() {
2226 let failure = DeliveryFailure::new(InvalidReference::new(
2227 envelope.dest().actor_addr(),
2228 InvalidReferenceReason::WrongMailboxOwner,
2229 ));
2230 return envelope.undeliverable(failure, return_handle);
2231 }
2232
2233 let port = envelope.dest().port();
2234
2235 let port_sender = match self.inner.ports.get(&port) {
2245 None => {
2246 let failure = unbound_port_delivery_failure(
2247 envelope.dest(),
2248 envelope.data(),
2249 self.inner.next_ephemeral_port.load(Ordering::SeqCst),
2250 );
2251 return envelope.undeliverable(failure, return_handle);
2252 }
2253 Some(ref_) => {
2254 let closed = self.inner.closed.read().unwrap();
2255 if let Some(status) = &*closed {
2256 match status {
2257 ActorStatus::Stopped(reason) => {
2258 tracing::debug!(
2259 owner=%self.inner.actor_id,
2260 %reason,
2261 "mailbox owner is stopped",
2262 );
2263 let failure = DeliveryFailure::new(InvalidReference::new(
2264 envelope.dest().actor_addr(),
2265 InvalidReferenceReason::ActorStopped,
2266 ));
2267 return envelope.undeliverable(failure, return_handle);
2268 }
2269 ActorStatus::Failed(actor_error) => {
2270 tracing::debug!(
2271 owner=%self.inner.actor_id,
2272 %actor_error,
2273 "mailbox owner failed",
2274 );
2275 let failure = DeliveryFailure::new(InvalidReference::new(
2276 envelope.dest().actor_addr(),
2277 InvalidReferenceReason::ActorFailed,
2278 ));
2279 return envelope.undeliverable(failure, return_handle);
2280 }
2281 _ => {
2282 let failure = DeliveryFailure::new(UndeliverableReason::Transport(
2283 TransportFailure::new(
2284 envelope.dest().actor_addr(),
2285 TransportFailureReason::LinkUnavailable(format!(
2286 "mailbox owner {} closed unexpectedly: {:?}",
2287 self.inner.actor_id, status
2288 )),
2289 ),
2290 ));
2291 return envelope.undeliverable(failure, return_handle);
2292 }
2293 }
2294 }
2295 Arc::clone(&*ref_)
2298 }
2299 };
2300 let (metadata, data) = envelope.open();
2303 let MessageMetadata {
2304 mut headers,
2305 sender,
2306 dest,
2307 next_hop,
2308 delivery_failures,
2309 ttl,
2310 return_undeliverable,
2311 } = metadata;
2312
2313 let to_actor_id = hash_to_u64(dest.actor_addr().id());
2314 let message_id = hyperactor_telemetry::generate_message_id(to_actor_id);
2315 headers.set(crate::mailbox::headers::TELEMETRY_MESSAGE_ID, message_id);
2316 if !headers.contains_key(crate::mailbox::headers::SENDER_ACTOR_ID_HASH) {
2319 crate::mailbox::headers::stamp_sender_actor_id_hash(&mut headers, &sender);
2320 }
2321 headers.set(crate::mailbox::headers::TELEMETRY_PORT_INDEX, dest.index());
2322
2323 match port_sender.send_serialized(headers, data) {
2324 Ok(disposition) => {
2325 hyperactor_telemetry::notify_message_status(
2326 hyperactor_telemetry::MessageStatusEvent {
2327 timestamp: std::time::SystemTime::now(),
2328 id: hyperactor_telemetry::generate_status_event_id(message_id),
2329 message_id,
2330 status: "queued".to_string(),
2331 },
2332 );
2333
2334 if disposition == SerializedSendDisposition::DeliveredAndExhausted {
2335 self.inner.ports.remove(&port);
2336 }
2337 }
2338 Err(SerializedSendFailure::Dead { data, headers }) => {
2339 self.inner.ports.remove(&port);
2340 let failure = port_gone_delivery_failure(&dest, &data);
2341
2342 MessageEnvelope::seal(
2343 MessageMetadata {
2344 headers,
2345 sender,
2346 dest,
2347 next_hop,
2348 delivery_failures,
2349 ttl,
2350 return_undeliverable,
2351 },
2352 data,
2353 )
2354 .undeliverable(failure, return_handle)
2355 }
2356 Err(SerializedSendFailure::Error(SerializedSendError {
2357 data,
2358 error: sender_error,
2359 headers,
2360 })) => {
2361 let failure = serialized_send_error_delivery_failure(&dest, &sender_error);
2362
2363 let envelope = MessageEnvelope::seal(
2364 MessageMetadata {
2365 headers,
2366 sender,
2367 dest,
2368 next_hop,
2369 delivery_failures,
2370 ttl,
2371 return_undeliverable,
2372 },
2373 data,
2374 );
2375 envelope.undeliverable(failure, return_handle)
2376 }
2377 }
2378 }
2379}
2380
2381fn unbound_port_delivery_failure(
2382 port: &PortAddr,
2383 data: &wirevalue::Any,
2384 next_ephemeral_port: u64,
2385) -> DeliveryFailure {
2386 if port.is_handler_port() {
2387 DeliveryFailure::new(InvalidReference::new(
2388 port.clone(),
2389 InvalidReferenceReason::HandlerNotBound,
2390 ))
2391 } else {
2392 match port.ephemeral_index() {
2393 Some(index) if index < next_ephemeral_port => port_gone_delivery_failure(port, data),
2394 _ => DeliveryFailure::new(InvalidReference::new(
2395 port.clone(),
2396 InvalidReferenceReason::PortNeverAllocated,
2397 )),
2398 }
2399 }
2400}
2401
2402fn serialized_send_error_delivery_failure(
2403 dest: &PortAddr,
2404 sender_error: &MailboxSenderError,
2405) -> DeliveryFailure {
2406 match sender_error.kind() {
2407 MailboxSenderErrorKind::Deserialize(_, _) => DeliveryFailure::new(InvalidReference::new(
2408 dest.clone(),
2409 InvalidReferenceReason::ProtocolMismatch,
2410 )),
2411 MailboxSenderErrorKind::Invalid => {
2412 let reason = if dest.is_handler_port() {
2413 InvalidReferenceReason::HandlerNotBound
2414 } else {
2415 InvalidReferenceReason::PortNeverAllocated
2416 };
2417 DeliveryFailure::new(InvalidReference::new(dest.clone(), reason))
2418 }
2419 MailboxSenderErrorKind::Closed => DeliveryFailure::new(UndeliverableReason::PortGone(
2420 PortGone::new(dest.clone(), None),
2421 )),
2422 _ => DeliveryFailure::new(UndeliverableReason::Transport(TransportFailure::new(
2423 dest.clone(),
2424 TransportFailureReason::LinkUnavailable(sender_error.to_string()),
2425 ))),
2426 }
2427}
2428
2429fn port_gone_delivery_failure(port: &PortAddr, data: &wirevalue::Any) -> DeliveryFailure {
2430 DeliveryFailure::new(port_gone(port, data))
2431}
2432
2433fn port_gone(port: &PortAddr, data: &wirevalue::Any) -> UndeliverableReason {
2434 UndeliverableReason::PortGone(PortGone::new(
2435 port.clone(),
2436 data.typename().map(str::to_string),
2437 ))
2438}
2439
2440#[derive(Debug, Clone, Copy)]
2441enum PortBindTarget {
2442 Ephemeral(u64),
2443 Handler,
2444}
2445
2446impl PortBindTarget {
2447 fn ephemeral_index(self) -> u64 {
2448 match self {
2449 Self::Ephemeral(port_index) => port_index,
2450 Self::Handler => panic!("handler port handle has no ephemeral port index"),
2451 }
2452 }
2453}
2454
2455struct PortHandleInner<M: Message> {
2458 mailbox: Mailbox,
2459 sender: UnboundedPortSender<M>,
2460 bind_target: PortBindTarget,
2461 bound: Arc<RwLock<Option<PortAddr>>>,
2468 reducer_spec: Option<ReducerSpec>,
2471 streaming_opts: StreamingReducerOpts,
2473}
2474
2475impl<M: Message> fmt::Debug for PortHandleInner<M> {
2476 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2477 f.debug_struct("PortHandleInner")
2478 .field("mailbox", &self.mailbox)
2479 .field("sender", &self.sender)
2480 .field("bind_target", &self.bind_target)
2481 .field("bound", &self.bound)
2482 .field("reducer_spec", &self.reducer_spec)
2483 .field("streaming_opts", &self.streaming_opts)
2484 .finish()
2485 }
2486}
2487
2488#[derive(Debug)]
2496pub struct PortHandle<M: Message> {
2497 inner: Arc<PortHandleInner<M>>,
2498}
2499
2500impl<M: Message> PortHandle<M> {
2501 fn new_full(
2502 mailbox: Mailbox,
2503 port_index: u64,
2504 sender: UnboundedPortSender<M>,
2505 reducer_spec: Option<ReducerSpec>,
2506 streaming_opts: StreamingReducerOpts,
2507 ) -> Self {
2508 Self::new_full_with_target(
2509 mailbox,
2510 sender,
2511 PortBindTarget::Ephemeral(port_index),
2512 reducer_spec,
2513 streaming_opts,
2514 )
2515 }
2516
2517 fn new_full_with_target(
2518 mailbox: Mailbox,
2519 sender: UnboundedPortSender<M>,
2520 bind_target: PortBindTarget,
2521 reducer_spec: Option<ReducerSpec>,
2522 streaming_opts: StreamingReducerOpts,
2523 ) -> Self {
2524 Self {
2525 inner: Arc::new(PortHandleInner {
2526 mailbox,
2527 sender,
2528 bind_target,
2529 bound: Arc::new(RwLock::new(None)),
2530 reducer_spec,
2531 streaming_opts,
2532 }),
2533 }
2534 }
2535
2536 fn new(mailbox: Mailbox, port_index: u64, sender: UnboundedPortSender<M>) -> Self {
2537 Self::new_full(
2538 mailbox,
2539 port_index,
2540 sender,
2541 None,
2542 StreamingReducerOpts::default(),
2543 )
2544 }
2545
2546 pub(crate) fn location(&self) -> PortLocation {
2547 match self.inner.bound.read().unwrap().as_ref() {
2548 Some(port_id) => PortLocation::Bound(port_id.clone()),
2549 None => PortLocation::new_unbound::<M>(self.inner.mailbox.actor_addr().clone()),
2550 }
2551 }
2552
2553 pub fn try_post<C>(&self, cx: &C, message: M) -> Result<(), MailboxSenderError>
2562 where
2563 C: context::Actor,
2564 {
2565 let closed = self.inner.mailbox.inner.closed.read().unwrap();
2566
2567 if let Some(status) = &*closed {
2568 let err = MailboxError {
2569 actor_id: self.inner.mailbox.actor_addr().clone(),
2570 kind: MailboxErrorKind::OwnerTerminated(status.clone()),
2571 };
2572 return Err(MailboxSenderError::new_unbound::<M>(
2573 self.inner.mailbox.actor_addr().clone(),
2574 MailboxSenderErrorKind::Mailbox(err),
2575 ));
2576 }
2577 let mut headers = Flattrs::new();
2578
2579 crate::mailbox::headers::set_send_timestamp(&mut headers);
2580 crate::mailbox::headers::set_rust_message_type::<M>(&mut headers);
2581 let bound_guard = self.inner.bound.read().unwrap();
2586 if let Some(dest) = bound_guard.as_ref() {
2587 let sequencer = cx.instance().sequencer();
2588 let seq_info = sequencer.assign_seq(dest);
2589 if let SeqInfo::Session { seq, .. } = &seq_info {
2593 crate::mailbox::headers::stamp_sender_actor_id_fresh(
2594 &mut headers,
2595 *seq,
2596 dest,
2597 cx.mailbox().actor_addr(),
2598 );
2599 }
2600 headers.set(SEQ_INFO, seq_info);
2601 } else {
2602 headers.set(SEQ_INFO, SeqInfo::Direct);
2603 }
2604 self.inner.sender.send(headers, message).map_err(|err| {
2616 MailboxSenderError::new_unbound::<M>(
2617 self.inner.mailbox.actor_addr().clone(),
2618 classify_sender_error(err),
2619 )
2620 })
2621 }
2622}
2623
2624impl<M> Endpoint<M> for &PortHandle<M>
2625where
2626 M: Message,
2627{
2628 fn endpoint_location(&self) -> EndpointLocation {
2629 self.location().into()
2630 }
2631
2632 fn post<C>(self, cx: &C, message: M)
2633 where
2634 C: context::Actor,
2635 {
2636 if let Err(err) = self.try_post(cx, message) {
2637 cx.instance()
2638 .report_delivery_failure(DeliveryFailureReport::from_send_error::<M>(
2639 cx.mailbox().actor_addr().clone(),
2640 self.endpoint_location(),
2641 &err,
2642 ));
2643 }
2644 }
2645}
2646
2647impl<M: Message> PortHandle<M> {
2648 pub fn contramap<R, F>(&self, unmap: F) -> PortHandle<R>
2651 where
2652 R: Message,
2653 F: Fn(R) -> M + Send + Sync + 'static,
2654 {
2655 let port_index = self.inner.mailbox.inner.allocate_port();
2656 let sender = self.inner.sender.clone();
2657 PortHandle::new(
2658 self.inner.mailbox.clone(),
2659 port_index,
2660 UnboundedPortSender::Func(Arc::new(move |headers, value: R| {
2661 sender.send(headers, unmap(value))
2662 })),
2663 )
2664 }
2665}
2666
2667impl<M: RemoteMessage> PortHandle<M> {
2668 pub fn bind(&self) -> PortRef<M> {
2673 match self.inner.bind_target {
2674 PortBindTarget::Ephemeral(_) => self.bind_ephemeral_port(),
2675 PortBindTarget::Handler => self.bind_handler_port(),
2676 }
2677 }
2678
2679 pub(crate) fn bind_handler_port(&self) -> PortRef<M> {
2685 self.bind_to_port(Port::handler::<M>(), |mailbox, handle| {
2686 mailbox.bind_to_handler_port(handle);
2687 })
2688 }
2689
2690 pub(crate) fn bind_control_port(&self, port: ControlPort) -> PortRef<M> {
2695 self.bind_to_port(Port::control(port), |mailbox, handle| {
2696 mailbox.bind_to_control_port(handle, port);
2697 })
2698 }
2699
2700 fn bind_ephemeral_port(&self) -> PortRef<M> {
2701 let port_addr = {
2702 let mut guard = self.inner.bound.write().unwrap();
2703 match guard.as_ref() {
2704 Some(existing) => existing.clone(),
2705 None => {
2706 let port_addr = self.inner.mailbox.bind(self).into_port_addr();
2707 *guard = Some(port_addr.clone());
2708 port_addr
2709 }
2710 }
2711 };
2712 self.port_ref(port_addr)
2713 }
2714
2715 fn bind_to_port(&self, port: Port, bind: impl FnOnce(&Mailbox, &PortHandle<M>)) -> PortRef<M> {
2716 let port_id = self.inner.mailbox.actor_addr().port_addr(port);
2717 {
2718 let mut guard = self.inner.bound.write().unwrap();
2719 match guard.as_ref() {
2720 Some(existing) if existing == &port_id => {}
2721 Some(existing) => panic!(
2722 "could not bind port handle {:?} as {port_id}: already bound to {existing}",
2723 self.inner.bind_target
2724 ),
2725 None => {
2726 bind(&self.inner.mailbox, self);
2727 *guard = Some(port_id.clone());
2728 }
2729 }
2730 }
2731 self.port_ref(port_id)
2732 }
2733
2734 fn port_ref(&self, port_addr: PortAddr) -> PortRef<M> {
2735 PortRef::attest_reducible(
2736 port_addr,
2737 self.inner.reducer_spec.clone(),
2738 self.inner.streaming_opts.clone(),
2739 )
2740 }
2741}
2742
2743impl<M: Message> Clone for PortHandle<M> {
2744 fn clone(&self) -> Self {
2745 Self {
2746 inner: Arc::clone(&self.inner),
2747 }
2748 }
2749}
2750
2751impl<M: Message> fmt::Display for PortHandle<M> {
2752 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2753 fmt::Display::fmt(&self.location(), f)
2754 }
2755}
2756
2757#[derive(Debug)]
2760pub struct IdleFlushPortHandle<M: Message> {
2761 port: PortHandle<M>,
2762 reducer_opts: IdleFlushReducerOpts,
2763}
2764
2765impl<M: RemoteMessage> IdleFlushPortHandle<M> {
2766 pub fn bind(self) -> IdleFlushPortRef<M> {
2769 self.port.bind().into_idle_flush(self.reducer_opts)
2770 }
2771}
2772
2773#[derive(Debug)]
2775pub struct OncePortHandle<M: Message> {
2776 mailbox: Mailbox,
2777 port_id: PortAddr,
2778 sender: oneshot::Sender<M>,
2779 reducer_spec: Option<ReducerSpec>,
2780}
2781
2782impl<M: Message> OncePortHandle<M> {
2783 pub fn port_addr(&self) -> &PortAddr {
2786 &self.port_id
2787 }
2788
2789 pub fn try_post<C>(self, _cx: &C, message: M) -> Result<(), MailboxSenderError>
2794 where
2795 C: context::Actor,
2796 {
2797 assert!(
2800 !self.port_addr().is_handler_port(),
2801 "OncePortHandle currently does not support handler ports; a \
2802 prerequisite of that support is to assign seq to messages \
2803 if the port is a handler port."
2804 );
2805
2806 let actor_id = self.mailbox.actor_addr().clone();
2807 self.sender.send(message).map_err(|_| {
2808 MailboxSenderError::new_unbound::<M>(actor_id, MailboxSenderErrorKind::Closed)
2813 })
2814 }
2815}
2816
2817impl<M> Endpoint<M> for OncePortHandle<M>
2818where
2819 M: Message,
2820{
2821 fn endpoint_location(&self) -> EndpointLocation {
2822 EndpointLocation::Port(self.port_id.clone())
2823 }
2824
2825 fn post<C>(self, cx: &C, message: M)
2826 where
2827 C: context::Actor,
2828 {
2829 let endpoint_location = self.endpoint_location();
2830 if let Err(err) = self.try_post(cx, message) {
2831 cx.instance()
2832 .report_delivery_failure(DeliveryFailureReport::from_send_error::<M>(
2833 cx.mailbox().actor_addr().clone(),
2834 endpoint_location,
2835 &err,
2836 ));
2837 }
2838 }
2839}
2840
2841impl<M: RemoteMessage> OncePortHandle<M> {
2842 pub fn bind(self) -> OncePortRef<M> {
2847 let port_id: PortAddr = self.port_addr().clone();
2848 let reducer_spec = self.reducer_spec.clone();
2849 self.mailbox.clone().bind_once(self);
2850 OncePortRef::attest_reducible(port_id, reducer_spec)
2851 }
2852}
2853
2854impl<M: Message> fmt::Display for OncePortHandle<M> {
2855 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2856 fmt::Display::fmt(&self.port_addr(), f)
2857 }
2858}
2859
2860#[derive(Debug)]
2863pub struct PortReceiver<M> {
2864 receiver: SequencedReceiver<SequencedEnvelope<M>>,
2865 port_id: PortAddr,
2866 coalesce: bool,
2869 mailbox: Mailbox,
2872}
2873
2874impl<M> PortReceiver<M> {
2875 fn new(
2876 receiver: SequencedReceiver<SequencedEnvelope<M>>,
2877 port_id: PortAddr,
2878 coalesce: bool,
2879 mailbox: Mailbox,
2880 ) -> Self {
2881 Self {
2882 receiver,
2883 port_id,
2884 coalesce,
2885 mailbox,
2886 }
2887 }
2888
2889 #[allow(clippy::result_large_err)] pub fn try_recv(&mut self) -> Result<Option<M>, MailboxError> {
2894 let mut next = self.receiver.try_recv();
2895 if self.coalesce
2897 && let Some(latest) = self.drain().pop()
2898 {
2899 next = Ok(latest);
2900 }
2901 match next {
2902 Ok(msg) => Ok(Some(msg)),
2903 Err(mpsc::error::TryRecvError::Empty) => Ok(None),
2904 Err(mpsc::error::TryRecvError::Disconnected) => Err(MailboxError::new(
2905 self.actor_addr().clone(),
2906 MailboxErrorKind::Closed,
2907 )),
2908 }
2909 }
2910
2911 pub async fn recv(&mut self) -> Result<M, MailboxError> {
2914 let mut next = self.receiver.recv().await;
2915 if self.coalesce
2918 && let Some(latest) = self.drain().pop()
2919 {
2920 next = Some(latest);
2921 }
2922 next.ok_or(MailboxError::new(
2923 self.actor_addr().clone(),
2924 MailboxErrorKind::Closed,
2925 ))
2926 }
2927
2928 pub fn drain(&mut self) -> Vec<M> {
2930 let mut drained: Vec<M> = Vec::new();
2931 while let Ok(msg) = self.receiver.try_recv() {
2932 if self.coalesce {
2934 drained.pop();
2935 }
2936 drained.push(msg);
2937 }
2938 drained
2939 }
2940
2941 fn port(&self) -> Port {
2942 self.port_id.port()
2943 }
2944
2945 fn actor_addr(&self) -> ActorAddr {
2946 self.port_id.actor_addr()
2947 }
2948}
2949
2950impl<M> Drop for PortReceiver<M> {
2951 fn drop(&mut self) {
2952 self.mailbox.inner.ports.remove(&self.port());
2956 self.mailbox.inner.ordering_snapshots.remove(&self.port());
2957 }
2958}
2959
2960impl<M> Unpin for PortReceiver<M> {}
2961
2962impl<M> Stream for PortReceiver<M> {
2963 type Item = Result<M, MailboxError>;
2964
2965 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
2966 std::pin::pin!(self.recv()).poll(cx).map(Some)
2967 }
2968}
2969
2970pub struct OncePortReceiver<M> {
2972 receiver: Option<oneshot::Receiver<M>>,
2973 port_id: PortAddr,
2974
2975 mailbox: Mailbox,
2978}
2979
2980impl<M> OncePortReceiver<M> {
2981 pub async fn recv(mut self) -> Result<M, MailboxError> {
2985 std::mem::take(&mut self.receiver)
2986 .unwrap()
2987 .await
2988 .map_err(|err| {
2989 MailboxError::new(
2990 self.actor_addr().clone(),
2991 MailboxErrorKind::Recv(self.port_id.clone(), err.into()),
2992 )
2993 })
2994 }
2995
2996 fn port(&self) -> Port {
2997 self.port_id.port()
2998 }
2999
3000 fn actor_addr(&self) -> ActorAddr {
3001 self.port_id.actor_addr()
3002 }
3003}
3004
3005impl<M> Drop for OncePortReceiver<M> {
3006 fn drop(&mut self) {
3007 self.mailbox.inner.ports.remove(&self.port());
3011 }
3012}
3013
3014#[derive(Clone, Copy, Debug, PartialEq, Eq)]
3015pub(crate) enum SerializedSendDisposition {
3016 Delivered,
3017 DeliveredAndExhausted,
3018}
3019
3020pub(crate) struct SerializedSendError {
3022 pub(crate) headers: Flattrs,
3024 pub(crate) data: wirevalue::Any,
3026 pub(crate) error: MailboxSenderError,
3028}
3029
3030pub(crate) enum SerializedSendFailure {
3031 Dead {
3032 headers: Flattrs,
3033 data: wirevalue::Any,
3034 },
3035 Error(SerializedSendError),
3036}
3037
3038trait SerializedSender: Send + Sync {
3043 fn as_any(&self) -> &dyn Any;
3049
3050 fn send_serialized(
3057 &self,
3058 headers: Flattrs,
3059 serialized: wirevalue::Any,
3060 ) -> Result<SerializedSendDisposition, SerializedSendFailure>;
3061}
3062
3063#[derive(Debug, thiserror::Error)]
3064#[error("handler port closed")]
3065struct HandlerPortClosedError;
3066
3067fn classify_sender_error(err: anyhow::Error) -> MailboxSenderErrorKind {
3068 if err.is::<HandlerPortClosedError>() {
3069 MailboxSenderErrorKind::Closed
3070 } else {
3071 MailboxSenderErrorKind::Other(err)
3072 }
3073}
3074
3075enum UnboundedPortSender<M: Message> {
3077 Sequenced(mpsc::UnboundedSender<SequencedEnvelope<M>>),
3079 Func(Arc<dyn Fn(Flattrs, M) -> Result<(), anyhow::Error> + Send + Sync>),
3081 Handler(Arc<HandlerPortSender<M>>),
3083}
3084
3085impl<M: Message> UnboundedPortSender<M> {
3086 fn send(&self, headers: Flattrs, message: M) -> Result<(), anyhow::Error> {
3087 match self {
3088 Self::Sequenced(sender) => {
3089 let seq_info = headers.get(SEQ_INFO).unwrap_or(SeqInfo::Direct);
3090 if !seq_info.is_valid() {
3091 return Err(anyhow::anyhow!("sequenced port send has invalid SEQ_INFO"));
3092 }
3093 let sender_addr = headers.get(crate::mailbox::headers::SENDER_ACTOR_ID);
3094 sender
3095 .send(SequencedEnvelope::new(seq_info, sender_addr, message))
3096 .map_err(anyhow::Error::from)
3097 }
3098 Self::Func(func) => func(headers, message),
3099 Self::Handler(sender) => sender.send(headers, message),
3100 }
3101 }
3102}
3103
3104impl<M: Message> Clone for UnboundedPortSender<M> {
3107 fn clone(&self) -> Self {
3108 match self {
3109 Self::Sequenced(sender) => Self::Sequenced(sender.clone()),
3110 Self::Func(func) => Self::Func(func.clone()),
3111 Self::Handler(sender) => Self::Handler(sender.clone()),
3112 }
3113 }
3114}
3115
3116impl<M: Message> Debug for UnboundedPortSender<M> {
3117 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
3118 match self {
3119 Self::Sequenced(q) => f
3120 .debug_tuple("UnboundedPortSender::Sequenced")
3121 .field(q)
3122 .finish(),
3123 Self::Func(_) => f
3124 .debug_tuple("UnboundedPortSender::Func")
3125 .field(&"..")
3126 .finish(),
3127 Self::Handler(_) => f
3128 .debug_tuple("UnboundedPortSender::Handler")
3129 .field(&"..")
3130 .finish(),
3131 }
3132 }
3133}
3134
3135const HANDLER_INGRESS_DRAINING: usize = 1usize << (usize::BITS as usize - 1);
3136const HANDLER_INGRESS_ACTIVE_MASK: usize = !HANDLER_INGRESS_DRAINING;
3137
3138struct HandlerIngressGate {
3139 state: AtomicUsize,
3140 wait_lock: Mutex<()>,
3141 drained: Condvar,
3142}
3143
3144struct HandlerIngressGuard {
3145 gate: Arc<HandlerIngressGate>,
3146}
3147
3148impl HandlerIngressGate {
3149 fn new() -> Self {
3150 Self {
3151 state: AtomicUsize::new(0),
3152 wait_lock: Mutex::new(()),
3153 drained: Condvar::new(),
3154 }
3155 }
3156
3157 fn try_enter(self: &Arc<Self>) -> Result<HandlerIngressGuard, HandlerPortClosedError> {
3158 let mut state = self.state.load(Ordering::Acquire);
3159 loop {
3160 if state & HANDLER_INGRESS_DRAINING != 0 {
3161 return Err(HandlerPortClosedError);
3162 }
3163
3164 let active = state & HANDLER_INGRESS_ACTIVE_MASK;
3165 assert!(
3166 active < HANDLER_INGRESS_ACTIVE_MASK,
3167 "too many active handler ingress sends"
3168 );
3169
3170 match self.state.compare_exchange_weak(
3171 state,
3172 state + 1,
3173 Ordering::AcqRel,
3174 Ordering::Acquire,
3175 ) {
3176 Ok(_) => {
3177 return Ok(HandlerIngressGuard {
3178 gate: Arc::clone(self),
3179 });
3180 }
3181 Err(next_state) => state = next_state,
3182 }
3183 }
3184 }
3185
3186 fn drain(&self) {
3187 let mut state = self.state.load(Ordering::Acquire);
3188 loop {
3189 if state & HANDLER_INGRESS_DRAINING != 0 {
3190 break;
3191 }
3192 match self.state.compare_exchange_weak(
3193 state,
3194 state | HANDLER_INGRESS_DRAINING,
3195 Ordering::AcqRel,
3196 Ordering::Acquire,
3197 ) {
3198 Ok(_) => break,
3199 Err(next_state) => state = next_state,
3200 }
3201 }
3202
3203 let mut wait_guard = self.wait_lock.lock().unwrap();
3204 while self.state.load(Ordering::Acquire) & HANDLER_INGRESS_ACTIVE_MASK != 0 {
3205 wait_guard = self.drained.wait(wait_guard).unwrap();
3206 }
3207 }
3208}
3209
3210impl Drop for HandlerIngressGuard {
3211 fn drop(&mut self) {
3212 let previous = self.gate.state.fetch_sub(1, Ordering::AcqRel);
3213 assert!(
3214 previous & HANDLER_INGRESS_ACTIVE_MASK != 0,
3215 "handler ingress active count underflow"
3216 );
3217 if previous & HANDLER_INGRESS_DRAINING != 0 && previous & HANDLER_INGRESS_ACTIVE_MASK == 1 {
3218 let _wait_guard = self.gate.wait_lock.lock().unwrap();
3224 self.gate.drained.notify_all();
3225 }
3226 }
3227}
3228
3229struct HandlerPortSender<M: Message> {
3230 sender: UnboundedPortSender<M>,
3231 gate: Arc<HandlerIngressGate>,
3232}
3233
3234impl<M: Message> HandlerPortSender<M> {
3235 fn new(sender: UnboundedPortSender<M>, gate: Arc<HandlerIngressGate>) -> Self {
3236 Self { sender, gate }
3237 }
3238
3239 fn send(&self, headers: Flattrs, message: M) -> Result<(), anyhow::Error> {
3240 let _guard = self.gate.try_enter()?;
3241 self.sender.send(headers, message)
3242 }
3243}
3244
3245struct UnboundedSender<M: Message> {
3246 sender: UnboundedPortSender<M>,
3247 port_id: PortAddr,
3248}
3249
3250impl<M: Message> UnboundedSender<M> {
3251 fn new(sender: UnboundedPortSender<M>, port_id: PortAddr) -> Self {
3254 Self { sender, port_id }
3255 }
3256
3257 #[allow(dead_code)]
3258 fn send(&self, headers: Flattrs, message: M) -> Result<(), MailboxSenderError> {
3259 self.sender.send(headers, message).map_err(|err| {
3260 MailboxSenderError::new_bound(self.port_id.clone(), classify_sender_error(err))
3261 })
3262 }
3263}
3264
3265impl<M: Message> Clone for UnboundedSender<M> {
3269 fn clone(&self) -> Self {
3270 Self {
3271 sender: self.sender.clone(),
3272 port_id: self.port_id.clone(),
3273 }
3274 }
3275}
3276
3277impl<M: RemoteMessage> SerializedSender for UnboundedSender<M> {
3278 fn as_any(&self) -> &dyn Any {
3279 self
3280 }
3281
3282 fn send_serialized(
3283 &self,
3284 headers: Flattrs,
3285 serialized: wirevalue::Any,
3286 ) -> Result<SerializedSendDisposition, SerializedSendFailure> {
3287 match serialized.deserialized_unchecked() {
3292 Ok(message) => match self.sender.send(headers.clone(), message) {
3293 Ok(()) => Ok(SerializedSendDisposition::Delivered),
3294 Err(_) if matches!(&self.sender, UnboundedPortSender::Sequenced(_)) => {
3295 Err(SerializedSendFailure::Dead {
3296 data: serialized,
3297 headers,
3298 })
3299 }
3300 Err(err) => Err(SerializedSendFailure::Error(SerializedSendError {
3301 data: serialized,
3302 error: MailboxSenderError::new_bound(
3303 self.port_id.clone(),
3304 classify_sender_error(err),
3305 ),
3306 headers,
3307 })),
3308 },
3309 Err(err) => Err(SerializedSendFailure::Error(SerializedSendError {
3310 data: serialized,
3311 error: MailboxSenderError::new_bound(
3312 self.port_id.clone(),
3313 MailboxSenderErrorKind::Deserialize(M::typename(), err.into()),
3314 ),
3315 headers,
3316 })),
3317 }
3318 }
3319}
3320
3321#[derive(Debug)]
3324struct OnceSender<M: Message> {
3325 sender: Arc<Mutex<Option<oneshot::Sender<M>>>>,
3326 port_id: PortAddr,
3327}
3328
3329impl<M: Message> OnceSender<M> {
3330 fn new(sender: oneshot::Sender<M>, port_id: PortAddr) -> Self {
3333 Self {
3334 sender: Arc::new(Mutex::new(Some(sender))),
3335 port_id,
3336 }
3337 }
3338
3339 fn send_once(&self, message: M) -> Result<SerializedSendDisposition, MailboxSenderError> {
3340 match self.sender.lock().unwrap().take() {
3342 None => Err(MailboxSenderError::new_bound(
3343 self.port_id.clone(),
3344 MailboxSenderErrorKind::Closed,
3345 )),
3346 Some(sender) => {
3347 sender.send(message).map_err(|_| {
3348 MailboxSenderError::new_bound(
3353 self.port_id.clone(),
3354 MailboxSenderErrorKind::Closed,
3355 )
3356 })?;
3357 Ok(SerializedSendDisposition::DeliveredAndExhausted)
3358 }
3359 }
3360 }
3361}
3362
3363impl<M: Message> Clone for OnceSender<M> {
3367 fn clone(&self) -> Self {
3368 Self {
3369 sender: self.sender.clone(),
3370 port_id: self.port_id.clone(),
3371 }
3372 }
3373}
3374
3375impl<M: RemoteMessage> SerializedSender for OnceSender<M> {
3376 fn as_any(&self) -> &dyn Any {
3377 self
3378 }
3379
3380 fn send_serialized(
3381 &self,
3382 headers: Flattrs,
3383 serialized: wirevalue::Any,
3384 ) -> Result<SerializedSendDisposition, SerializedSendFailure> {
3385 match serialized.deserialized() {
3386 Ok(message) => self
3387 .send_once(message)
3388 .map_err(|_| SerializedSendFailure::Dead {
3389 data: serialized,
3390 headers,
3391 }),
3392 Err(err) => Err(SerializedSendFailure::Error(SerializedSendError {
3393 data: serialized,
3394 error: MailboxSenderError::new_bound(
3395 self.port_id.clone(),
3396 MailboxSenderErrorKind::Deserialize(M::typename(), err.into()),
3397 ),
3398 headers,
3399 })),
3400 }
3401 }
3402}
3403
3404pub(crate) struct UntypedUnboundedSender {
3406 pub(crate) sender: Box<
3407 dyn Fn(Flattrs, wirevalue::Any) -> Result<SerializedSendDisposition, SerializedSendFailure>
3408 + Send
3409 + Sync,
3410 >,
3411}
3412
3413impl SerializedSender for UntypedUnboundedSender {
3414 fn as_any(&self) -> &dyn Any {
3415 self
3416 }
3417
3418 fn send_serialized(
3419 &self,
3420 headers: Flattrs,
3421 serialized: wirevalue::Any,
3422 ) -> Result<SerializedSendDisposition, SerializedSendFailure> {
3423 (self.sender)(headers, serialized)
3424 }
3425}
3426
3427struct State {
3429 actor_id: ActorAddr,
3431
3432 ports: DashMap<Port, Arc<dyn SerializedSender>>,
3436
3437 ordering_snapshots: DashMap<Port, Arc<dyn Fn() -> OrderingSnapshot + Send + Sync>>,
3439
3440 next_ephemeral_port: AtomicU64,
3442
3443 closed: RwLock<Option<ActorStatus>>,
3446
3447 handler_ingress: Arc<HandlerIngressGate>,
3449
3450 return_handler_init: Once,
3452
3453 return_handler_hook: OnceLock<Box<dyn Fn() + Send + Sync + 'static>>,
3455}
3456
3457impl State {
3458 fn new(actor_id: ActorAddr) -> Self {
3460 Self {
3461 actor_id,
3462 ports: DashMap::new(),
3463 ordering_snapshots: DashMap::new(),
3464 next_ephemeral_port: AtomicU64::new(0),
3465 closed: RwLock::new(None),
3466 handler_ingress: Arc::new(HandlerIngressGate::new()),
3467 return_handler_init: Once::new(),
3468 return_handler_hook: OnceLock::new(),
3469 }
3470 }
3471
3472 fn allocate_port(&self) -> u64 {
3474 self.next_ephemeral_port.fetch_add(1, Ordering::SeqCst)
3475 }
3476}
3477
3478impl fmt::Debug for State {
3479 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
3480 f.debug_struct("State")
3481 .field("actor_id", &self.actor_id)
3482 .field(
3483 "open_ports",
3484 &self
3485 .ports
3486 .iter()
3487 .map(|e| e.key().clone())
3488 .collect::<Vec<_>>(),
3489 )
3490 .field("next_ephemeral_port", &self.next_ephemeral_port)
3491 .finish()
3492 }
3493}
3494
3495#[derive(Clone)]
3504pub struct MailboxMuxer {
3505 mailboxes: Arc<DashMap<ActorId, ActorMailbox>>,
3506 status_sender: Arc<OnceLock<Box<dyn MailboxSender + Send + Sync>>>,
3507}
3508
3509type PendingMessage = (MessageEnvelope, PortHandle<Undeliverable<MessageEnvelope>>);
3510
3511enum ActorMailbox {
3512 Bound(Arc<dyn MailboxSender + Send + Sync>),
3514 Buffering(Vec<PendingMessage>),
3517}
3518
3519impl Default for MailboxMuxer {
3520 fn default() -> Self {
3521 Self::new()
3522 }
3523}
3524
3525impl MailboxMuxer {
3526 pub fn new() -> Self {
3528 Self {
3529 mailboxes: Arc::new(DashMap::new()),
3530 status_sender: Arc::new(OnceLock::new()),
3531 }
3532 }
3533
3534 pub fn bind(&self, actor_id: ActorId, sender: impl MailboxSender + 'static) -> bool {
3540 let sender: Arc<dyn MailboxSender + Send + Sync> = Arc::new(sender);
3541 match self.mailboxes.entry(actor_id) {
3542 Entry::Vacant(entry) => {
3543 entry.insert(ActorMailbox::Bound(sender));
3544 true
3545 }
3546 Entry::Occupied(mut entry) => {
3547 let ActorMailbox::Buffering(pending) = entry.get_mut() else {
3548 return false;
3549 };
3550 for (envelope, return_handle) in std::mem::take(pending) {
3553 sender.post(envelope, return_handle);
3554 }
3555 entry.insert(ActorMailbox::Bound(sender));
3556 true
3557 }
3558 }
3559 }
3560
3561 pub fn bind_mailbox(&self, mailbox: Mailbox) -> bool {
3563 self.bind(mailbox.actor_addr().id().clone(), mailbox)
3564 }
3565
3566 pub fn bind_status(&self, sender: impl MailboxSender + 'static) -> bool {
3569 self.status_sender.set(Box::new(sender)).is_ok()
3570 }
3571
3572 #[allow(dead_code)]
3576 pub(crate) fn unbind(&self, actor_id: &ActorId) {
3577 self.mailboxes.remove_if(actor_id, |_, mailbox| {
3578 matches!(mailbox, ActorMailbox::Bound(_))
3579 });
3580 }
3581
3582 fn post_unbound(
3585 &self,
3586 envelope: MessageEnvelope,
3587 return_handle: PortHandle<Undeliverable<MessageEnvelope>>,
3588 ) {
3589 let timeout = hyperactor_config::global::get(crate::config::PENDING_ACTOR_DELIVERY_TIMEOUT);
3590 match self.mailboxes.entry(envelope.dest().actor_id().clone()) {
3591 Entry::Occupied(mut entry) => match entry.get_mut() {
3592 ActorMailbox::Bound(sender) => sender.post(envelope, return_handle),
3593 ActorMailbox::Buffering(pending) => pending.push((envelope, return_handle)),
3594 },
3595 Entry::Vacant(_) if timeout.is_zero() => {
3596 let failure = actor_not_exist(&envelope);
3597 envelope.undeliverable(failure, return_handle);
3598 }
3599 Entry::Vacant(entry) => {
3600 let actor_id = entry.key().clone();
3601 entry.insert(ActorMailbox::Buffering(vec![(envelope, return_handle)]));
3602 let mailboxes = Arc::downgrade(&self.mailboxes);
3603 crate::init::get_runtime().spawn(async move {
3604 tokio::time::sleep(timeout).await;
3605 let Some(mailboxes) = mailboxes.upgrade() else {
3606 return;
3607 };
3608 let Some((actor_id, ActorMailbox::Buffering(expired))) = mailboxes
3609 .remove_if(&actor_id, |_, mailbox| {
3610 matches!(mailbox, ActorMailbox::Buffering(_))
3611 })
3612 else {
3613 return;
3614 };
3615 tracing::warn!(
3616 %actor_id,
3617 count = expired.len(),
3618 ?timeout,
3619 "actor did not bind in time; returning buffered messages",
3620 );
3621 for (envelope, return_handle) in expired {
3622 let failure = actor_not_exist(&envelope);
3623 envelope.undeliverable(failure, return_handle);
3624 }
3625 });
3626 }
3627 }
3628 }
3629}
3630
3631fn actor_not_exist(envelope: &MessageEnvelope) -> DeliveryFailure {
3632 DeliveryFailure::new(InvalidReference::new(
3633 envelope.dest().actor_addr(),
3634 InvalidReferenceReason::ActorNotExist,
3635 ))
3636}
3637
3638#[async_trait]
3639impl MailboxSender for MailboxMuxer {
3640 fn post_unchecked(
3641 &self,
3642 envelope: MessageEnvelope,
3643 return_handle: PortHandle<Undeliverable<MessageEnvelope>>,
3644 ) {
3645 if envelope.dest().is_control_port_kind(ControlPort::Status)
3646 && let Some(sender) = self.status_sender.get()
3647 {
3648 sender.post(envelope, return_handle);
3649 return;
3650 }
3651
3652 if let Some(mailbox) = self.mailboxes.get(envelope.dest().actor_id())
3653 && let ActorMailbox::Bound(sender) = mailbox.value()
3654 {
3655 return sender.post(envelope, return_handle);
3656 }
3657 self.post_unbound(envelope, return_handle);
3658 }
3659
3660 async fn flush(&self) -> Result<(), anyhow::Error> {
3661 let senders: Vec<_> = self
3662 .mailboxes
3663 .iter()
3664 .filter_map(|mailbox| match mailbox.value() {
3665 ActorMailbox::Bound(sender) => Some(Arc::clone(sender)),
3666 ActorMailbox::Buffering(_) => None,
3667 })
3668 .collect();
3669 for sender in senders {
3670 sender.flush().await?;
3671 }
3672 Ok(())
3673 }
3674}
3675
3676#[derive(Clone)]
3679pub struct MailboxRouter {
3680 entries: Arc<RwLock<BTreeMap<Addr, Arc<dyn MailboxSender + Send + Sync>>>>,
3681}
3682
3683impl Default for MailboxRouter {
3684 fn default() -> Self {
3685 Self::new()
3686 }
3687}
3688
3689impl MailboxRouter {
3690 pub fn new() -> Self {
3692 Self {
3693 entries: Arc::new(RwLock::new(BTreeMap::new())),
3694 }
3695 }
3696
3697 pub fn downgrade(&self) -> WeakMailboxRouter {
3699 WeakMailboxRouter(Arc::downgrade(&self.entries))
3700 }
3701
3702 pub fn fallback(&self, default: BoxedMailboxSender) -> BoxedMailboxSender {
3706 FallbackMailboxRouter {
3707 router: self.clone(),
3708 default,
3709 }
3710 .into_boxed()
3711 }
3712
3713 pub fn bind(&self, dest: impl Into<Addr>, sender: impl MailboxSender + 'static) {
3717 let dest = dest.into();
3718 let mut w = self.entries.write().unwrap();
3719 w.insert(dest, Arc::new(sender));
3720 }
3721
3722 pub fn unbind(&self, dest: &Addr) {
3726 let mut w = self.entries.write().unwrap();
3727 w.remove(dest);
3728 }
3729
3730 fn sender(&self, actor_ref: &ActorAddr) -> Option<Arc<dyn MailboxSender + Send + Sync>> {
3731 let reference = Addr::from(actor_ref.clone());
3732 match self
3733 .entries
3734 .read()
3735 .unwrap()
3736 .lower_bound(Excluded(&reference))
3737 .prev()
3738 {
3739 None => None,
3740 Some((key, sender)) if key.is_prefix_of(&reference) => Some(sender.clone()),
3741 Some(_) => None,
3742 }
3743 }
3744}
3745
3746#[async_trait]
3747impl MailboxSender for MailboxRouter {
3748 fn post_unchecked(
3749 &self,
3750 envelope: MessageEnvelope,
3751 return_handle: PortHandle<Undeliverable<MessageEnvelope>>,
3752 ) {
3753 let dest_actor_ref = envelope.dest().actor_addr();
3754 match self.sender(&dest_actor_ref) {
3755 None => {
3756 let target = envelope.dest().clone();
3757 let failure = DeliveryFailure::new(UndeliverableReason::Transport(
3758 TransportFailure::new(target, TransportFailureReason::NoRoute),
3759 ));
3760 envelope.undeliverable(failure, return_handle)
3761 }
3762 Some(sender) => sender.post(envelope, return_handle),
3763 }
3764 }
3765
3766 async fn flush(&self) -> Result<(), anyhow::Error> {
3767 let senders: Vec<_> = self.entries.read().unwrap().values().cloned().collect();
3768 let futs: Vec<_> = senders.iter().map(|s| s.flush()).collect();
3769 futures::future::try_join_all(futs).await?;
3770 Ok(())
3771 }
3772}
3773
3774#[derive(Clone)]
3777pub struct FallbackMailboxRouter {
3778 router: MailboxRouter,
3779 default: BoxedMailboxSender,
3780}
3781
3782impl FallbackMailboxRouter {
3783 pub fn default_sender(&self) -> &BoxedMailboxSender {
3785 &self.default
3786 }
3787}
3788
3789#[async_trait]
3790impl MailboxSender for FallbackMailboxRouter {
3791 fn post_unchecked(
3792 &self,
3793 envelope: MessageEnvelope,
3794 return_handle: PortHandle<Undeliverable<MessageEnvelope>>,
3795 ) {
3796 let dest_actor_ref = envelope.dest().actor_addr();
3797 match self.router.sender(&dest_actor_ref) {
3798 Some(sender) => sender.post(envelope, return_handle),
3799 None => self.default.post(envelope, return_handle),
3800 }
3801 }
3802
3803 async fn flush(&self) -> Result<(), anyhow::Error> {
3804 let (r1, r2) = futures::future::join(self.router.flush(), self.default.flush()).await;
3805 r1?;
3806 r2?;
3807 Ok(())
3808 }
3809}
3810
3811#[derive(Debug, Clone)]
3820pub struct WeakMailboxRouter(Weak<RwLock<BTreeMap<Addr, Arc<dyn MailboxSender + Send + Sync>>>>);
3821
3822impl WeakMailboxRouter {
3823 pub fn upgrade(&self) -> Option<MailboxRouter> {
3825 self.0.upgrade().map(|entries| MailboxRouter { entries })
3826 }
3827}
3828
3829#[async_trait]
3830impl MailboxSender for WeakMailboxRouter {
3831 fn post_unchecked(
3832 &self,
3833 envelope: MessageEnvelope,
3834 return_handle: PortHandle<Undeliverable<MessageEnvelope>>,
3835 ) {
3836 match self.upgrade() {
3837 Some(router) => router.post(envelope, return_handle),
3838 None => {
3839 let target = envelope.dest().clone();
3840 let failure =
3841 DeliveryFailure::new(UndeliverableReason::Transport(TransportFailure::new(
3842 target,
3843 TransportFailureReason::LinkUnavailable(
3844 "mailbox router is gone".to_string(),
3845 ),
3846 )));
3847 envelope.undeliverable(failure, return_handle)
3848 }
3849 }
3850 }
3851
3852 async fn flush(&self) -> Result<(), anyhow::Error> {
3853 match self.upgrade() {
3854 Some(router) => router.flush().await,
3855 None => Ok(()),
3856 }
3857 }
3858}
3859
3860fn is_stale_session_close(status: &TxStatus) -> bool {
3870 matches!(status, TxStatus::Closed(CloseReason::SequenceMismatch(_)))
3871}
3872
3873#[derive(Clone)]
3887pub struct DialMailboxRouter {
3888 address_book: Arc<RwLock<BTreeMap<Addr, ChannelAddr>>>,
3889 sender_cache: Arc<DashMap<ChannelAddr, Arc<MailboxClient>>>,
3890
3891 default: BoxedMailboxSender,
3894
3895 direct_addressed_remote_only: bool,
3898}
3899
3900impl Default for DialMailboxRouter {
3901 fn default() -> Self {
3902 Self::new()
3903 }
3904}
3905
3906impl DialMailboxRouter {
3907 pub fn new() -> Self {
3909 Self::new_with_default(BoxedMailboxSender::new(UnroutableMailboxSender))
3910 }
3911
3912 pub fn new_with_default(default: BoxedMailboxSender) -> Self {
3917 Self {
3918 address_book: Arc::new(RwLock::new(BTreeMap::new())),
3919 sender_cache: Arc::new(DashMap::new()),
3920 default,
3921 direct_addressed_remote_only: false,
3922 }
3923 }
3924
3925 pub fn new_with_default_direct_addressed_remote_only(default: BoxedMailboxSender) -> Self {
3930 Self {
3931 address_book: Arc::new(RwLock::new(BTreeMap::new())),
3932 sender_cache: Arc::new(DashMap::new()),
3933 default,
3934 direct_addressed_remote_only: true,
3935 }
3936 }
3937
3938 pub fn bind(&self, dest: impl Into<Addr>, addr: ChannelAddr) {
3944 let dest = dest.into();
3945 let addr = addr.into_dial_addr();
3946 if let Ok(mut w) = self.address_book.write() {
3947 if let Some(old_addr) = w.insert(dest.clone(), addr.clone())
3948 && old_addr != addr
3949 {
3950 tracing::info!("rebinding {:?} from {:?} to {:?}", dest, old_addr, addr);
3951 self.sender_cache.remove(&old_addr);
3952 }
3953 } else {
3954 tracing::error!("address book poisoned during bind of {:?}", dest);
3955 }
3956 }
3957
3958 pub fn unbind(&self, dest: &Addr) {
3964 if let Ok(mut w) = self.address_book.write() {
3965 let to_remove: Vec<(Addr, ChannelAddr)> = w
3966 .range(dest..)
3967 .take_while(|(key, _)| dest.is_prefix_of(key))
3968 .map(|(key, addr)| (key.clone(), addr.clone()))
3969 .collect();
3970
3971 for (key, addr) in to_remove {
3972 tracing::info!("unbinding {:?} from {:?}", key, addr);
3973 w.remove(&key);
3974 self.sender_cache.remove(&addr);
3975 }
3976 } else {
3977 tracing::error!("address book poisoned during unbind of {:?}", dest);
3978 }
3979 }
3980
3981 pub fn lookup_addr(&self, actor_ref: &ActorAddr) -> Option<ChannelAddr> {
3983 let address_book = self.address_book.read().unwrap();
3984 let reference = Addr::from(actor_ref.clone());
3985 let found = address_book.lower_bound(Excluded(&reference)).prev();
3986
3987 if let Some((key, addr)) = found
3990 && key.is_prefix_of(&reference)
3991 {
3992 Some(addr.clone().into_dial_addr())
3993 } else {
3994 let addr = actor_ref.addr().clone().into_dial_addr();
3995 if self.direct_addressed_remote_only {
3996 addr.transport().is_remote().then_some(addr)
3997 } else {
3998 Some(addr)
3999 }
4000 }
4001 }
4002
4003 pub fn prefixes(&self) -> BTreeSet<Addr> {
4006 let addrs = self.address_book.read().unwrap();
4007 let mut prefixes: BTreeSet<Addr> = BTreeSet::new();
4008 for reference in addrs.keys() {
4009 match prefixes.lower_bound(Excluded(reference)).peek_prev() {
4010 Some(candidate) if candidate.is_prefix_of(reference) => (),
4011 _ => {
4012 prefixes.insert(reference.clone());
4013 }
4014 }
4015 }
4016
4017 prefixes
4018 }
4019
4020 fn dial(
4021 &self,
4022 addr: &ChannelAddr,
4023 actor_ref: &ActorAddr,
4024 ) -> Result<Arc<MailboxClient>, MailboxSenderError> {
4025 loop {
4039 match self.sender_cache.entry(addr.clone()) {
4040 Entry::Occupied(entry) => {
4041 let status = entry.get().tx_status().borrow().clone();
4042 if is_stale_session_close(&status) {
4043 tracing::info!(
4044 ?addr,
4045 reason = ?status.as_closed(),
4046 "evicting stale-session MailboxClient from DialMailboxRouter cache"
4047 );
4048 entry.remove();
4049 continue;
4050 }
4051 return Ok(entry.get().clone());
4052 }
4053 Entry::Vacant(entry) => {
4054 let tx = channel::dial(addr.clone()).map_err(|err| {
4055 MailboxSenderError::new_unbound_type(
4056 actor_ref.clone(),
4057 MailboxSenderErrorKind::Channel(err),
4058 "unknown",
4059 )
4060 })?;
4061 let sender = Arc::new(MailboxClient::new(tx));
4062 return Ok(entry.insert(sender).value().clone());
4063 }
4064 }
4065 }
4066 }
4067}
4068
4069#[async_trait]
4070impl MailboxSender for DialMailboxRouter {
4071 fn post_unchecked(
4072 &self,
4073 envelope: MessageEnvelope,
4074 return_handle: PortHandle<Undeliverable<MessageEnvelope>>,
4075 ) {
4076 let dest_actor_ref = envelope.dest().actor_addr();
4077 let Some(addr) = self.lookup_addr(&dest_actor_ref) else {
4078 self.default.post(envelope, return_handle);
4079 return;
4080 };
4081
4082 match self.dial(&addr, &dest_actor_ref) {
4083 Err(err) => {
4084 let target = envelope.dest().clone();
4085 let failure =
4086 DeliveryFailure::new(UndeliverableReason::Transport(TransportFailure::new(
4087 target,
4088 TransportFailureReason::DialFailed {
4089 addr,
4090 error: err.to_string(),
4091 },
4092 )));
4093 envelope.undeliverable(failure, return_handle)
4094 }
4095 Ok(sender) => sender.post(envelope, return_handle),
4096 }
4097 }
4098
4099 async fn flush(&self) -> Result<(), anyhow::Error> {
4100 let senders: Vec<_> = self
4101 .sender_cache
4102 .iter()
4103 .map(|entry| entry.value().clone())
4104 .collect();
4105 let mut futs: Vec<_> = senders.iter().map(|s| s.flush()).collect();
4106 futs.push(self.default.flush());
4107 futures::future::try_join_all(futs).await?;
4108 Ok(())
4109 }
4110}
4111
4112#[derive(Debug)]
4115pub struct UnroutableMailboxSender;
4116
4117#[async_trait]
4118impl MailboxSender for UnroutableMailboxSender {
4119 fn post_unchecked(
4120 &self,
4121 envelope: MessageEnvelope,
4122 return_handle: PortHandle<Undeliverable<MessageEnvelope>>,
4123 ) {
4124 let target = envelope.dest().clone();
4125 let failure = DeliveryFailure::new(UndeliverableReason::Transport(TransportFailure::new(
4126 target,
4127 TransportFailureReason::NoRoute,
4128 )));
4129 envelope.undeliverable(failure, return_handle);
4130 }
4131}
4132
4133#[cfg(test)]
4134mod tests {
4135
4136 use std::assert_matches;
4137 use std::mem::drop;
4138 use std::sync::atomic::AtomicUsize;
4139 use std::time::Duration;
4140
4141 use async_trait::async_trait;
4142 use hyperactor_config::NonZeroUsize;
4143 use timed_test::async_timed_test;
4144
4145 use super::*;
4146 use crate as hyperactor;
4147 use crate::Actor;
4148 use crate::ActorRef;
4149 use crate::Handler;
4150 use crate::accum;
4151 use crate::accum::ReducerMode;
4152 use crate::channel::ChannelTransport;
4153 use crate::context::Actor as _;
4154 use crate::context::Mailbox as MailboxContext;
4155 use crate::context::MailboxExt as _;
4156 use crate::endpoint::Endpoint as _;
4157 use crate::proc::Proc;
4158 use crate::testing::ids::test_actor_id;
4159 use crate::testing::ids::test_port_id;
4160 use crate::testing::ids::test_proc_id;
4161
4162 fn test_proc_ref(name: &str) -> Addr {
4163 Addr::Proc(test_proc_id(name))
4164 }
4165
4166 fn test_actor_ref(proc_name: &str, actor_name: &str) -> Addr {
4167 Addr::Actor(test_actor_id(proc_name, actor_name))
4168 }
4169
4170 fn root_transport_failure(envelope: &MessageEnvelope) -> &TransportFailure {
4171 let root_failure = envelope
4172 .root_delivery_failure()
4173 .expect("expected root delivery failure");
4174 let DeliveryFailureKind::Undeliverable(UndeliverableReason::Transport(transport)) =
4175 &root_failure.kind
4176 else {
4177 panic!("expected transport failure, got {root_failure}");
4178 };
4179 transport
4180 }
4181
4182 fn root_invalid_reference(envelope: &MessageEnvelope) -> &InvalidReference {
4183 let root_failure = envelope
4184 .root_delivery_failure()
4185 .expect("expected root delivery failure");
4186 let DeliveryFailureKind::InvalidReference(invalid_reference) = &root_failure.kind else {
4187 panic!("expected invalid reference, got {root_failure}");
4188 };
4189 invalid_reference
4190 }
4191
4192 struct ClosedChannelTx {
4193 addr: ChannelAddr,
4194 status: watch::Receiver<TxStatus>,
4195 }
4196
4197 impl ClosedChannelTx {
4198 fn new(addr: ChannelAddr) -> Self {
4199 let (_sender, status) = watch::channel(TxStatus::Closed(CloseReason::Other(
4200 "test channel closed".into(),
4201 )));
4202 Self { addr, status }
4203 }
4204 }
4205
4206 #[async_trait]
4207 impl channel::Tx<MessageEnvelope> for ClosedChannelTx {
4208 fn do_post(&self, message: MessageEnvelope, completion: CompletionSink<MessageEnvelope>) {
4209 completion.reject(SendError {
4210 error: ChannelError::Closed,
4211 message,
4212 reason: None,
4213 });
4214 }
4215
4216 fn addr(&self) -> ChannelAddr {
4217 self.addr.clone()
4218 }
4219
4220 fn status(&self) -> &watch::Receiver<TxStatus> {
4221 &self.status
4222 }
4223 }
4224
4225 #[test]
4226 fn test_error() {
4227 use crate::testing::ids::test_actor_id;
4228 let err = MailboxError::new(
4229 test_actor_id("myworld_2", "myactor"),
4230 MailboxErrorKind::Closed,
4231 );
4232 let err_str = format!("{err}");
4234 assert!(
4235 err_str.contains("mailbox closed"),
4236 "expected error: {}",
4237 err_str
4238 );
4239 assert!(
4240 err_str.contains("@"),
4241 "expected ref-style location separator in {err_str}"
4242 );
4243 }
4244
4245 #[test]
4246 fn test_error_msg_renders_structured_delivery_failure() {
4247 let sender = test_actor_id("0", "sender");
4248 let dest = test_port_id("0", "dest", 42);
4249 let mut envelope = MessageEnvelope::serialize(sender, dest.clone(), &42u64, Flattrs::new())
4250 .expect("serialize");
4251 envelope.push_delivery_failure(DeliveryFailure::new(InvalidReference::new(
4252 dest,
4253 InvalidReferenceReason::PortNeverAllocated,
4254 )));
4255
4256 let error = envelope.error_msg().expect("expected error");
4257 assert!(error.contains("delivery failure: invalid reference"));
4258 assert!(error.contains("port never allocated"));
4259 }
4260
4261 #[test]
4262 fn test_delivery_failure_rendering_bounds_attrs() {
4263 use hyperactor_config::attrs::declare_attrs;
4264
4265 declare_attrs! {
4266 attr TEST_DELIVERY_FAILURE_ATTR: String;
4267 }
4268
4269 let target = test_port_id("0", "dest", 42);
4270 let mut attrs = Flattrs::new();
4271 let large_value = "x".repeat(MAX_RENDERED_DELIVERY_FAILURE_ATTRS_LEN * 2);
4272 attrs.set(TEST_DELIVERY_FAILURE_ATTR, large_value.clone());
4273 let failure = DeliveryFailure::with_attrs(
4274 InvalidReference::new(target, InvalidReferenceReason::PortNeverAllocated),
4275 attrs,
4276 );
4277 let rendered = failure.render_bounded();
4278
4279 assert!(rendered.contains("port never allocated"));
4280 assert!(rendered.contains("..."));
4281 assert!(
4282 !rendered.contains(&large_value),
4283 "rendering must not include unbounded attr values"
4284 );
4285 }
4286
4287 #[tokio::test]
4288 async fn test_mailbox_basic() {
4289 let mbox = Mailbox::new(test_actor_id("0", "test"));
4290 let (port, mut receiver) = mbox.open_port::<u64>();
4291 let port = port.bind();
4292
4293 mbox.serialize_and_send(&port, 123, monitored_return_handle())
4294 .unwrap();
4295 mbox.serialize_and_send(&port, 321, monitored_return_handle())
4296 .unwrap();
4297 assert_eq!(receiver.recv().await.unwrap(), 123u64);
4298 assert_eq!(receiver.recv().await.unwrap(), 321u64);
4299
4300 let serialized = wirevalue::Any::serialize(&999u64).unwrap();
4301 mbox.post(
4302 MessageEnvelope::new_unknown(port.port_addr().clone(), serialized),
4303 monitored_return_handle(),
4304 );
4305 assert_eq!(receiver.recv().await.unwrap(), 999u64);
4306 }
4307
4308 #[tokio::test]
4309 async fn test_mailbox_rejects_messages_for_other_actors() {
4310 let mbox = Mailbox::new(test_actor_id("0", "owner"));
4311 let dest = test_actor_id("0", "other").port_addr(Port::from(1234));
4312 let envelope =
4313 MessageEnvelope::serialize(mbox.actor_addr().clone(), dest, &42u64, Flattrs::new())
4314 .expect("serialize");
4315 let (return_handle, mut return_rx) = undeliverable::new_undeliverable_port();
4316
4317 mbox.post(envelope, return_handle);
4318
4319 let Undeliverable::Returned(undelivered) =
4320 tokio::time::timeout(Duration::from_secs(1), return_rx.recv())
4321 .await
4322 .expect("timed out waiting for undeliverable")
4323 .expect("return port closed")
4324 else {
4325 panic!("expected returned message");
4326 };
4327 assert!(
4328 undelivered
4329 .error_msg()
4330 .expect("expected error")
4331 .contains("wrong mailbox owner")
4332 );
4333 let root_failure = undelivered
4334 .root_delivery_failure()
4335 .expect("expected root delivery failure");
4336 let DeliveryFailureKind::InvalidReference(invalid_reference) = &root_failure.kind else {
4337 panic!("expected invalid reference, got {root_failure}");
4338 };
4339 assert_eq!(
4340 invalid_reference.reason,
4341 InvalidReferenceReason::WrongMailboxOwner
4342 );
4343 }
4344
4345 #[tokio::test]
4346 async fn test_ephemeral_port_orders_raw_and_serialized_sends() {
4347 let proc = Proc::isolated();
4348 let client = proc.client("client");
4349 let (port_handle, mut receiver) = client.open_port::<u64>();
4350 let port = port_handle.bind();
4351 let session_id = client.instance().sequencer().session_id();
4352
4353 let mut headers = Flattrs::new();
4354 headers.set(SEQ_INFO, SeqInfo::Session { session_id, seq: 2 });
4355 let envelope = MessageEnvelope::new(
4356 client.mailbox().actor_addr().clone(),
4357 port.port_addr().clone(),
4358 wirevalue::Any::serialize(&2u64).unwrap(),
4359 headers,
4360 );
4361 client.mailbox().post(envelope, monitored_return_handle());
4362
4363 port_handle.try_post(&client, 1u64).unwrap();
4364
4365 assert_eq!(receiver.recv().await.unwrap(), 1);
4366 assert_eq!(receiver.recv().await.unwrap(), 2);
4367 }
4368
4369 #[tokio::test]
4370 async fn test_ttl_expiration_records_root_delivery_failure() {
4371 let mbox = Mailbox::new(test_actor_id("0", "test"));
4372 let (port, _) = mbox.open_port::<u64>();
4373 let port_ref = port.bind();
4374 let envelope = MessageEnvelope::serialize(
4375 mbox.actor_addr().clone(),
4376 port_ref.port_addr().clone(),
4377 &42u64,
4378 Flattrs::new(),
4379 )
4380 .expect("serialize")
4381 .set_ttl(0);
4382 let (return_handle, mut return_rx) = undeliverable::new_undeliverable_port();
4383
4384 mbox.post(envelope, return_handle);
4385
4386 let undelivered = tokio::time::timeout(Duration::from_secs(1), return_rx.recv())
4387 .await
4388 .expect("timed out waiting for undeliverable")
4389 .expect("return port closed")
4390 .into_message()
4391 .expect("expected returned envelope");
4392 let root_failure = undelivered
4393 .root_delivery_failure()
4394 .expect("expected root delivery failure");
4395 assert!(
4396 matches!(root_failure.kind, DeliveryFailureKind::Expired(_)),
4397 "expected expired delivery failure, got {root_failure}"
4398 );
4399 }
4400
4401 #[tokio::test]
4402 async fn test_missing_handler_port_records_invalid_reference() {
4403 let mbox = Mailbox::new(test_actor_id("0", "test"));
4404 let dest = mbox.actor_addr().port_addr(Port::handler::<TestMessage>());
4405 let envelope = MessageEnvelope::serialize(
4406 mbox.actor_addr().clone(),
4407 dest,
4408 &TestMessage,
4409 Flattrs::new(),
4410 )
4411 .expect("serialize");
4412 let (return_handle, mut return_rx) = undeliverable::new_undeliverable_port();
4413
4414 mbox.post(envelope, return_handle);
4415
4416 let undelivered = tokio::time::timeout(Duration::from_secs(1), return_rx.recv())
4417 .await
4418 .expect("timed out waiting for undeliverable")
4419 .expect("return port closed")
4420 .into_message()
4421 .expect("expected returned envelope");
4422 let root_failure = undelivered
4423 .root_delivery_failure()
4424 .expect("expected root delivery failure");
4425 let DeliveryFailureKind::InvalidReference(invalid_reference) = &root_failure.kind else {
4426 panic!("expected invalid reference, got {root_failure}");
4427 };
4428 assert_eq!(
4429 invalid_reference.reason,
4430 InvalidReferenceReason::HandlerNotBound
4431 );
4432 }
4433
4434 #[tokio::test]
4435 async fn test_missing_dropped_port_records_recipient_gone() {
4436 let mbox = Mailbox::new(test_actor_id("0", "test"));
4437 let (port, receiver) = mbox.open_port::<u64>();
4438 let port_ref = port.bind();
4439 drop(receiver);
4440 let envelope = MessageEnvelope::serialize(
4441 mbox.actor_addr().clone(),
4442 port_ref.port_addr().clone(),
4443 &42u64,
4444 Flattrs::new(),
4445 )
4446 .expect("serialize");
4447 let (return_handle, mut return_rx) = undeliverable::new_undeliverable_port();
4448
4449 mbox.post(envelope, return_handle);
4450
4451 let undelivered = tokio::time::timeout(Duration::from_secs(1), return_rx.recv())
4452 .await
4453 .expect("timed out waiting for undeliverable")
4454 .expect("return port closed")
4455 .into_message()
4456 .expect("expected returned envelope");
4457 let root_failure = undelivered
4458 .root_delivery_failure()
4459 .expect("expected root delivery failure");
4460 let DeliveryFailureKind::Undeliverable(UndeliverableReason::PortGone(port_gone)) =
4461 &root_failure.kind
4462 else {
4463 panic!("expected port gone, got {root_failure}");
4464 };
4465 assert_eq!(port_gone.port, *port_ref.port_addr());
4466 }
4467
4468 #[tokio::test]
4469 async fn test_missing_never_allocated_port_records_invalid_reference() {
4470 let mbox = Mailbox::new(test_actor_id("0", "test"));
4471 let dest = mbox.actor_addr().port_addr(Port::from(0));
4472 let envelope =
4473 MessageEnvelope::serialize(mbox.actor_addr().clone(), dest, &42u64, Flattrs::new())
4474 .expect("serialize");
4475 let (return_handle, mut return_rx) = undeliverable::new_undeliverable_port();
4476
4477 mbox.post(envelope, return_handle);
4478
4479 let undelivered = tokio::time::timeout(Duration::from_secs(1), return_rx.recv())
4480 .await
4481 .expect("timed out waiting for undeliverable")
4482 .expect("return port closed")
4483 .into_message()
4484 .expect("expected returned envelope");
4485 let root_failure = undelivered
4486 .root_delivery_failure()
4487 .expect("expected root delivery failure");
4488 let DeliveryFailureKind::InvalidReference(invalid_reference) = &root_failure.kind else {
4489 panic!("expected invalid reference, got {root_failure}");
4490 };
4491 assert_eq!(
4492 invalid_reference.reason,
4493 InvalidReferenceReason::PortNeverAllocated
4494 );
4495 }
4496
4497 #[tokio::test]
4498 async fn test_mailbox_accum() {
4499 let proc = Proc::isolated();
4500 let client = proc.client("client");
4501 let (port, mut receiver) = client
4502 .mailbox()
4503 .open_accum_port(accum::join_semilattice::<accum::Max<i64>>());
4504
4505 for i in -3..4 {
4506 port.post(&client, accum::Max(i));
4507 let received: accum::Max<i64> = receiver.recv().await.unwrap();
4508 let msg = received.get();
4509 assert_eq!(msg, &i);
4510 }
4511 for i in -3..4 {
4513 port.post(&client, accum::Max(i));
4514 assert_eq!(receiver.recv().await.unwrap().get(), &3);
4515 }
4516 port.post(&client, accum::Max(4));
4518 assert_eq!(receiver.recv().await.unwrap().get(), &4);
4519
4520 for i in 5..10 {
4522 port.post(&client, accum::Max(i));
4523 }
4524 assert_eq!(receiver.recv().await.unwrap().get(), &9);
4525 port.post(&client, accum::Max(1));
4526 port.post(&client, accum::Max(3));
4527 port.post(&client, accum::Max(2));
4528 assert_eq!(receiver.recv().await.unwrap().get(), &9);
4529 }
4530
4531 #[test]
4532 fn test_port_and_reducer() {
4533 let mbox = Mailbox::new(test_actor_id("0", "test"));
4534 {
4536 let accumulator = accum::join_semilattice::<accum::Max<u64>>();
4537 let reducer_spec = accumulator.reducer_spec().unwrap();
4538 let (port, _) = mbox.open_accum_port(accum::join_semilattice::<accum::Max<u64>>());
4539 assert_eq!(port.inner.reducer_spec, Some(reducer_spec.clone()));
4540 let port_ref = port.bind();
4541 assert_eq!(port_ref.reducer_spec(), &Some(reducer_spec));
4542 }
4543 {
4545 let accumulator = accum::join_semilattice::<accum::Max<u64>>();
4546 let reducer_spec = accumulator.reducer_spec().unwrap();
4547 let reducer_opts = IdleFlushReducerOpts {
4548 idle_timeout: Duration::from_millis(50),
4549 abandon_timeout: Duration::from_secs(30),
4550 expected_updates_per_destination: NonZeroUsize::MIN,
4551 };
4552 let (port, _) = mbox.open_idle_flush_accum_port(accumulator, reducer_opts.clone());
4553 let port_ref = port.bind();
4554 let repr = crate::ref_::IdleFlushPortRefRepr::try_from(&port_ref)
4555 .expect("idle-flush port reference should have a representation");
4556
4557 assert_eq!(port_ref.reducer_spec(), &Some(reducer_spec));
4558 assert_eq!(repr.reducer_opts(), &reducer_opts);
4559 }
4560 {
4562 let (port, _) = mbox.open_port::<u64>();
4563 assert_eq!(port.inner.reducer_spec, None);
4564 let port_ref = port.bind();
4565 assert_eq!(port_ref.reducer_spec(), &None);
4566 }
4567 }
4568
4569 #[tokio::test]
4570 async fn test_mailbox_once() {
4571 let proc = Proc::isolated();
4572 let client = proc.client("client");
4573
4574 let (port, receiver) = client.open_once_port::<u64>();
4575
4576 port.post(&client, 123u64);
4579 assert_eq!(receiver.recv().await.unwrap(), 123u64);
4580
4581 }
4592
4593 #[cfg(any())]
4594 #[tokio::test]
4595 async fn test_mailbox_receiver_drop() {
4596 let mbox = Mailbox::new(test_actor_id("0", "test"));
4597 let (port, mut receiver) = mbox.open_port::<u64>();
4598 let port = port.bind();
4600 mbox.serialize_and_send(&port, 123u64, monitored_return_handle())
4601 .unwrap();
4602 assert_eq!(receiver.recv().await.unwrap(), 123u64);
4603 drop(receiver);
4604 let Err(err) = mbox.serialize_and_send(&port, 123u64, monitored_return_handle()) else {
4605 panic!();
4606 };
4607
4608 assert_matches!(err.kind(), MailboxSenderErrorKind::Closed);
4609 assert_matches!(err.location(), PortLocation::Bound(bound) if *bound == *port.port_addr());
4610 }
4611
4612 #[tokio::test]
4613 async fn test_mailbox_type_mismatch_does_not_evict_unbounded_port() {
4614 let mbox = Mailbox::new(test_actor_id("0", "test"));
4615 let (port, mut receiver) = mbox.open_port::<u64>();
4616 let port = port.bind();
4617 let port_index = port.port_addr().index();
4618 let target: Addr = port.port_addr().clone().into();
4619 let (return_handle, mut return_receiver) =
4620 crate::mailbox::undeliverable::new_undeliverable_port();
4621
4622 let wrong_message = wirevalue::Any::serialize(&TestMessage).unwrap();
4623 mbox.post(
4624 MessageEnvelope::new_unknown(port.port_addr().clone(), wrong_message),
4625 return_handle.clone(),
4626 );
4627
4628 let envelope = tokio::time::timeout(Duration::from_secs(1), return_receiver.recv())
4629 .await
4630 .expect("undeliverable mismatch should arrive")
4631 .unwrap()
4632 .into_message()
4633 .expect("expected returned envelope");
4634 assert!(
4635 envelope
4636 .error_msg()
4637 .is_some_and(|message| message.contains("protocol mismatch")),
4638 "expected protocol mismatch in {envelope}",
4639 );
4640 let invalid_reference = root_invalid_reference(&envelope);
4641 assert_eq!(invalid_reference.target, target);
4642 assert_eq!(
4643 invalid_reference.reason,
4644 InvalidReferenceReason::ProtocolMismatch
4645 );
4646 assert!(
4647 mbox.inner.ports.contains_key(&Port::from(port_index)),
4648 "deserialization mismatch should not evict reusable port",
4649 );
4650
4651 mbox.serialize_and_send(&port, 123u64, return_handle)
4652 .unwrap();
4653 assert_eq!(
4654 tokio::time::timeout(Duration::from_secs(1), receiver.recv())
4655 .await
4656 .expect("valid message should still be delivered")
4657 .unwrap(),
4658 123u64
4659 );
4660 }
4661
4662 #[tokio::test]
4663 async fn test_mailbox_closed_unbounded_port_is_removed_after_send_failure() {
4664 let mbox = Mailbox::new(test_actor_id("0", "test"));
4665 let port_index = mbox.allocate_port();
4666 let port_id = mbox.actor_addr().port_addr(Port::from(port_index));
4667 let port = crate::PortRef::attest(port_id.clone());
4668 let (return_handle, mut return_receiver) =
4669 crate::mailbox::undeliverable::new_undeliverable_port();
4670 let (sender, receiver) = sequenced_unbounded::<SequencedEnvelope<u64>>();
4671
4672 drop(receiver);
4673
4674 mbox.inner.ports.insert(
4675 Port::from(port_index),
4676 Arc::new(UnboundedSender::new(
4677 UnboundedPortSender::Sequenced(sender),
4678 port_id,
4679 )),
4680 );
4681
4682 mbox.serialize_and_send(&port, 123u64, return_handle.clone())
4683 .unwrap();
4684
4685 let envelope = tokio::time::timeout(Duration::from_secs(1), return_receiver.recv())
4686 .await
4687 .expect("closed port should produce undeliverable")
4688 .unwrap()
4689 .into_message()
4690 .expect("expected returned envelope");
4691 let first_error = envelope.error_msg().expect("expected delivery error");
4692 assert!(
4693 first_error.contains("port gone"),
4694 "expected port-gone error in {envelope}",
4695 );
4696 assert!(
4697 !mbox.inner.ports.contains_key(&Port::from(port_index)),
4698 "dead reusable port should be removed after send failure",
4699 );
4700
4701 mbox.serialize_and_send(&port, 456u64, return_handle)
4702 .unwrap();
4703 let envelope = tokio::time::timeout(Duration::from_secs(1), return_receiver.recv())
4704 .await
4705 .expect("removed port should produce unbound undeliverable")
4706 .unwrap()
4707 .into_message()
4708 .expect("expected returned envelope");
4709 let second_error = envelope.error_msg().expect("expected delivery error");
4710 assert_eq!(
4711 first_error, second_error,
4712 "dead-port undeliverable should match unbound-port undeliverable exactly",
4713 );
4714 }
4715
4716 #[tokio::test]
4717 async fn test_mailbox_once_type_mismatch_preserves_sender_until_delivery() {
4718 let mbox = Mailbox::new(test_actor_id("0", "test"));
4719 let (port, receiver) = mbox.open_once_port::<u64>();
4720 let port = port.bind();
4721 let port_index = port.port_addr().index();
4722 let target: Addr = port.port_addr().clone().into();
4723 let (return_handle, mut return_receiver) =
4724 crate::mailbox::undeliverable::new_undeliverable_port();
4725
4726 let wrong_message = wirevalue::Any::serialize(&TestMessage).unwrap();
4727 mbox.post(
4728 MessageEnvelope::new_unknown(port.port_addr().clone(), wrong_message),
4729 return_handle.clone(),
4730 );
4731
4732 let envelope = tokio::time::timeout(Duration::from_secs(1), return_receiver.recv())
4733 .await
4734 .expect("once-port mismatch should arrive")
4735 .unwrap()
4736 .into_message()
4737 .expect("expected returned envelope");
4738 assert!(
4739 envelope
4740 .error_msg()
4741 .is_some_and(|message| message.contains("protocol mismatch")),
4742 "expected protocol mismatch in {envelope}",
4743 );
4744 let invalid_reference = root_invalid_reference(&envelope);
4745 assert_eq!(invalid_reference.target, target);
4746 assert_eq!(
4747 invalid_reference.reason,
4748 InvalidReferenceReason::ProtocolMismatch
4749 );
4750 assert!(
4751 mbox.inner.ports.contains_key(&Port::from(port_index)),
4752 "once port should survive deserialization mismatch before delivery",
4753 );
4754
4755 mbox.serialize_and_send_once(port, 123u64, return_handle)
4756 .unwrap();
4757 assert_eq!(
4758 tokio::time::timeout(Duration::from_secs(1), receiver.recv())
4759 .await
4760 .expect("valid once message should still be delivered")
4761 .unwrap(),
4762 123u64
4763 );
4764 assert!(
4765 !mbox.inner.ports.contains_key(&Port::from(port_index)),
4766 "successful once send should remove the sender entry",
4767 );
4768 }
4769
4770 #[tokio::test]
4771 async fn test_drain() {
4772 let mbox = Mailbox::new(test_actor_id("0", "test"));
4773
4774 let (port, mut receiver) = mbox.open_port();
4775 let port = port.bind();
4776
4777 for i in 0..10 {
4778 mbox.serialize_and_send(&port, i, monitored_return_handle())
4779 .unwrap();
4780 }
4781
4782 for i in 0..10 {
4783 assert_eq!(receiver.recv().await.unwrap(), i);
4784 }
4785
4786 assert!(receiver.drain().is_empty());
4787 }
4788
4789 #[tokio::test]
4790 async fn test_mailbox_muxer() {
4791 let config = hyperactor_config::global::lock();
4792 let _config_guard = config.override_key(
4793 crate::config::PENDING_ACTOR_DELIVERY_TIMEOUT,
4794 Duration::from_millis(100),
4795 );
4796 let muxer = MailboxMuxer::new();
4797
4798 let mbox0 = Mailbox::new(test_actor_id("0", "actor1"));
4799 let mbox1 = Mailbox::new(test_actor_id("0", "actor2"));
4800
4801 muxer.bind(mbox0.actor_addr().id().clone(), mbox0.clone());
4802 muxer.bind(mbox1.actor_addr().id().clone(), mbox1.clone());
4803
4804 let (port, receiver) = mbox0.open_once_port::<u64>();
4805
4806 let muxer_sender = muxer.clone();
4807 let proc = Proc::configured(test_proc_id("0"), BoxedMailboxSender::new(muxer));
4808 let client = proc.client("client");
4809
4810 port.post(&client, 123u64);
4811 assert_eq!(receiver.recv().await.unwrap(), 123u64);
4812
4813 let missing_actor = test_actor_id("0", "missing_actor");
4814 let missing_dest = missing_actor.port_addr(Port::from(1234));
4815 let envelope = MessageEnvelope::serialize(
4816 client.self_addr().clone(),
4817 missing_dest,
4818 &456u64,
4819 Flattrs::new(),
4820 )
4821 .expect("serialize");
4822 let (return_handle, mut return_rx) = undeliverable::new_undeliverable_port();
4823
4824 muxer_sender.post(envelope, return_handle);
4825
4826 let undelivered = tokio::time::timeout(Duration::from_secs(1), return_rx.recv())
4827 .await
4828 .expect("timed out waiting for undeliverable")
4829 .expect("return port closed")
4830 .into_message()
4831 .expect("expected returned envelope");
4832 let root_failure = undelivered
4833 .root_delivery_failure()
4834 .expect("expected root delivery failure");
4835 let DeliveryFailureKind::InvalidReference(invalid_reference) = &root_failure.kind else {
4836 panic!("expected invalid reference, got {root_failure}");
4837 };
4838 assert_eq!(invalid_reference.target, Addr::Actor(missing_actor));
4839 assert_eq!(
4840 invalid_reference.reason,
4841 InvalidReferenceReason::ActorNotExist
4842 );
4843
4844 }
4854
4855 fn muxer_envelope(dest: PortAddr, value: u64) -> MessageEnvelope {
4856 MessageEnvelope::serialize(test_actor_id("0", "sender"), dest, &value, Flattrs::new())
4857 .expect("serialize")
4858 }
4859
4860 #[tokio::test]
4861 async fn muxer_buffers_until_bind() {
4862 let muxer = MailboxMuxer::new();
4863 let mbox = Mailbox::new(test_actor_id("0", "late"));
4864 let (port, mut receiver) = mbox.open_port::<u64>();
4865 let dest = port.bind().port_addr().clone();
4866 let (return_handle, mut return_rx) = undeliverable::new_undeliverable_port();
4867
4868 muxer.post(muxer_envelope(dest.clone(), 1), return_handle.clone());
4869 muxer.post(muxer_envelope(dest.clone(), 2), return_handle.clone());
4870 assert!(
4871 receiver.try_recv().unwrap().is_none(),
4872 "messages must wait for the mailbox to bind"
4873 );
4874
4875 assert!(muxer.bind_mailbox(mbox.clone()));
4876 muxer.post(muxer_envelope(dest, 3), return_handle.clone());
4877
4878 for expected in 1..=3u64 {
4879 assert_eq!(receiver.recv().await.unwrap(), expected);
4880 }
4881 assert!(
4882 return_rx.try_recv().unwrap().is_none(),
4883 "no message should bounce"
4884 );
4885 assert!(
4886 !muxer.bind_mailbox(mbox),
4887 "a second bind must not replace the first"
4888 );
4889 }
4890
4891 #[tokio::test]
4892 async fn muxer_zero_timeout_bounces_immediately() {
4893 let config = hyperactor_config::global::lock();
4894 let _config_guard = config.override_key(
4895 crate::config::PENDING_ACTOR_DELIVERY_TIMEOUT,
4896 Duration::ZERO,
4897 );
4898 let muxer = MailboxMuxer::new();
4899 let missing_actor = test_actor_id("0", "missing_actor");
4900 let (return_handle, mut return_rx) = undeliverable::new_undeliverable_port();
4901
4902 muxer.post(
4903 muxer_envelope(missing_actor.port_addr(Port::from(1234)), 1),
4904 return_handle,
4905 );
4906
4907 let undelivered = return_rx
4908 .try_recv()
4909 .unwrap()
4910 .expect("zero timeout must bounce without buffering")
4911 .into_message()
4912 .expect("expected returned envelope");
4913 let root_failure = undelivered
4914 .root_delivery_failure()
4915 .expect("expected root delivery failure");
4916 let DeliveryFailureKind::InvalidReference(invalid_reference) = &root_failure.kind else {
4917 panic!("expected invalid reference, got {root_failure}");
4918 };
4919 assert_eq!(
4920 invalid_reference.reason,
4921 InvalidReferenceReason::ActorNotExist
4922 );
4923 }
4924
4925 #[tokio::test]
4926 async fn test_local_client_server() {
4927 let mbox = Mailbox::new(test_actor_id("0", "actor0"));
4928 let (addr, rx) =
4929 channel::serve(ChannelAddr::any(ChannelTransport::Local)).expect("serve local");
4930 let tx = channel::dial(addr).expect("dial local");
4931 let serve_handle = mbox.clone().serve(rx);
4932 let client = MailboxClient::new(tx);
4933
4934 let (port, receiver) = mbox.open_once_port::<u64>();
4935 let port = port.bind();
4936
4937 client
4938 .serialize_and_send_once(port, 123u64, monitored_return_handle())
4939 .unwrap();
4940 assert_eq!(receiver.recv().await.unwrap(), 123u64);
4941 serve_handle.stop("fromt test");
4942 serve_handle.await.unwrap().unwrap();
4943 }
4944
4945 #[tokio::test]
4946 async fn test_mailbox_client_records_channel_closed_failure() {
4947 let mbox = Mailbox::new(test_actor_id("0", "actor0"));
4948 let client = MailboxClient::new(ClosedChannelTx::new(ChannelAddr::Local(0)));
4949 let addr = client.addr.clone();
4950
4951 let (port, _receiver) = mbox.open_once_port::<u64>();
4952 let port = port.bind();
4953 let target: Addr = port.port_addr().clone().into();
4954 let (return_handle, mut return_receiver) =
4955 crate::mailbox::undeliverable::new_undeliverable_port();
4956
4957 client
4958 .serialize_and_send_once(port, 123u64, return_handle)
4959 .unwrap();
4960
4961 let undelivered = tokio::time::timeout(Duration::from_secs(1), return_receiver.recv())
4962 .await
4963 .expect("timed out waiting for undeliverable")
4964 .expect("return port closed")
4965 .into_message()
4966 .expect("expected returned envelope");
4967 let root_failure = undelivered
4968 .root_delivery_failure()
4969 .expect("expected root delivery failure");
4970 let DeliveryFailureKind::Undeliverable(UndeliverableReason::Transport(transport)) =
4971 &root_failure.kind
4972 else {
4973 panic!("expected transport failure, got {root_failure}");
4974 };
4975 assert_eq!(transport.target, target);
4976 assert_eq!(
4977 transport.reason,
4978 TransportFailureReason::ChannelClosed { addr }
4979 );
4980 }
4981
4982 #[tokio::test]
4983 async fn test_mailbox_router() {
4984 let mbox0 = Mailbox::new(test_actor_id("world0_0", "actor0"));
4985 let mbox1 = Mailbox::new(test_actor_id("world1_0", "actor0"));
4986 let mbox2 = Mailbox::new(test_actor_id("world1_1", "actor0"));
4987 let mbox3 = Mailbox::new(test_actor_id("world1_1", "actor1"));
4988
4989 let comms: Vec<(OncePortRef<u64>, OncePortReceiver<u64>)> =
4990 [&mbox0, &mbox1, &mbox2, &mbox3]
4991 .into_iter()
4992 .map(|mbox| {
4993 let (port, receiver) = mbox.open_once_port::<u64>();
4994 (port.bind(), receiver)
4995 })
4996 .collect();
4997
4998 let router = MailboxRouter::new();
4999
5000 router.bind(test_proc_ref("world0_0"), mbox0);
5001 router.bind(test_proc_ref("world1_0"), mbox1);
5002 router.bind(test_proc_ref("world1_1"), mbox2);
5003 router.bind(test_actor_ref("world1_1", "actor1"), mbox3);
5004
5005 for (i, (port, receiver)) in comms.into_iter().enumerate() {
5006 router
5007 .serialize_and_send_once(port, i as u64, monitored_return_handle())
5008 .unwrap();
5009 assert_eq!(receiver.recv().await.unwrap(), i as u64);
5010 }
5011
5012 let mbox4 = Mailbox::new(test_actor_id("fallback_0", "actor"));
5015
5016 let (return_handle, mut return_receiver) =
5017 crate::mailbox::undeliverable::new_undeliverable_port();
5018 let (port, _receiver) = mbox4.open_once_port();
5019 let port = port.bind();
5020 let target: Addr = port.port_addr().clone().into();
5021 router
5022 .serialize_and_send_once(port, 0, return_handle.clone())
5023 .unwrap();
5024 let undelivered = return_receiver
5025 .recv()
5026 .await
5027 .unwrap()
5028 .into_message()
5029 .expect("expected returned envelope");
5030 let transport = root_transport_failure(&undelivered);
5031 assert_eq!(transport.target, target);
5032 assert_eq!(transport.reason, TransportFailureReason::NoRoute);
5033
5034 let router = router.fallback(mbox4.clone().into_boxed());
5035 let (port, receiver) = mbox4.open_once_port();
5036 router
5037 .serialize_and_send_once(port.bind(), 0, return_handle)
5038 .unwrap();
5039 assert_eq!(receiver.recv().await.unwrap(), 0);
5040 }
5041
5042 #[tokio::test]
5043 async fn test_weak_mailbox_router_records_link_unavailable_failure() {
5044 let router = MailboxRouter::new();
5045 let weak_router = router.downgrade();
5046 drop(router);
5047
5048 let mbox = Mailbox::new(test_actor_id("0", "actor0"));
5049 let (port, _receiver) = mbox.open_once_port::<u64>();
5050 let port = port.bind();
5051 let target: Addr = port.port_addr().clone().into();
5052 let (return_handle, mut return_receiver) =
5053 crate::mailbox::undeliverable::new_undeliverable_port();
5054
5055 weak_router
5056 .serialize_and_send_once(port, 123u64, return_handle)
5057 .unwrap();
5058
5059 let undelivered = return_receiver
5060 .recv()
5061 .await
5062 .unwrap()
5063 .into_message()
5064 .expect("expected returned envelope");
5065 let transport = root_transport_failure(&undelivered);
5066 assert_eq!(transport.target, target);
5067 assert_eq!(
5068 transport.reason,
5069 TransportFailureReason::LinkUnavailable("mailbox router is gone".to_string())
5070 );
5071 }
5072
5073 #[tokio::test]
5074 async fn test_unroutable_mailbox_sender_records_no_route_failure() {
5075 let mbox = Mailbox::new(test_actor_id("0", "actor0"));
5076 let (port, _receiver) = mbox.open_once_port::<u64>();
5077 let port = port.bind();
5078 let target: Addr = port.port_addr().clone().into();
5079 let (return_handle, mut return_receiver) =
5080 crate::mailbox::undeliverable::new_undeliverable_port();
5081
5082 UnroutableMailboxSender
5083 .serialize_and_send_once(port, 123u64, return_handle)
5084 .unwrap();
5085
5086 let undelivered = return_receiver
5087 .recv()
5088 .await
5089 .unwrap()
5090 .into_message()
5091 .expect("expected returned envelope");
5092 let transport = root_transport_failure(&undelivered);
5093 assert_eq!(transport.target, target);
5094 assert_eq!(transport.reason, TransportFailureReason::NoRoute);
5095 }
5096
5097 #[tokio::test]
5098 async fn test_dial_mailbox_router() {
5099 let router = DialMailboxRouter::new();
5100
5101 router.bind(test_proc_ref("world0_0"), "unix!@1".parse().unwrap());
5102 router.bind(test_proc_ref("world1_0"), "unix!@2".parse().unwrap());
5103 router.bind(test_proc_ref("world1_1"), "unix!@3".parse().unwrap());
5104 router.bind(
5105 test_actor_ref("world1_1", "actor1"),
5106 "unix!@4".parse().unwrap(),
5107 );
5108 let direct_actor_ref: ActorAddr =
5112 ProcAddr::singleton("unix:@4".parse().unwrap(), "my_proc").actor_addr("my_actor");
5113 router.bind(
5114 Addr::Actor(direct_actor_ref.clone()),
5115 "unix:@5".parse().unwrap(),
5116 );
5117
5118 router
5120 .lookup_addr(&test_actor_id("world0_0", "actor"))
5121 .unwrap();
5122 router
5123 .lookup_addr(&test_actor_id("world1_0", "actor"))
5124 .unwrap();
5125
5126 let actor_id = direct_actor_ref;
5127 assert_eq!(
5128 router.lookup_addr(&actor_id).unwrap(),
5129 "unix!@5".parse().unwrap(),
5130 );
5131 router.unbind(&actor_id.clone().into());
5132 assert_eq!(
5133 router.lookup_addr(&actor_id).unwrap(),
5134 "unix!@4".parse().unwrap(),
5135 );
5136
5137 let fallback = ChannelAddr::any(ChannelTransport::Local);
5142 router.unbind(&test_proc_ref("world1_0"));
5143 router.unbind(&test_proc_ref("world1_1"));
5144 assert_eq!(
5145 router
5146 .lookup_addr(&test_actor_id("world1_0", "actor1"))
5147 .unwrap(),
5148 fallback,
5149 );
5150 assert_eq!(
5151 router
5152 .lookup_addr(&test_actor_id("world1_1", "actor1"))
5153 .unwrap(),
5154 fallback,
5155 );
5156 router
5157 .lookup_addr(&test_actor_id("world0_0", "actor"))
5158 .unwrap();
5159 router.unbind(&test_proc_ref("world0_0"));
5160 assert_eq!(
5161 router
5162 .lookup_addr(&test_actor_id("world0_0", "actor"))
5163 .unwrap(),
5164 fallback,
5165 );
5166 }
5167
5168 #[test]
5169 fn test_dial_mailbox_router_canonicalizes_alias_addresses() {
5170 let router = DialMailboxRouter::new();
5171 let dial_to = ChannelAddr::from_zmq_url("tcp://127.0.0.1:9000").unwrap();
5172 let alias = ChannelAddr::from_zmq_url("tcp://127.0.0.1:9000@tcp://0.0.0.0:9000").unwrap();
5173
5174 router.bind(test_proc_ref("world_alias"), alias.clone());
5175 assert_eq!(
5176 router
5177 .lookup_addr(&test_actor_id("world_alias", "actor"))
5178 .unwrap(),
5179 dial_to
5180 );
5181
5182 let direct_actor_ref = ProcAddr::singleton(alias, "direct_alias").actor_addr("actor");
5183 assert_eq!(router.lookup_addr(&direct_actor_ref).unwrap(), dial_to);
5184 }
5185
5186 #[tokio::test]
5187 async fn test_dial_mailbox_router_records_dial_failure() {
5188 let router = DialMailboxRouter::new();
5189 let addr = ChannelAddr::Local(9_876_543_210);
5190 let mbox = Mailbox::new(test_actor_id("world0_0", "actor0"));
5191 router.bind(test_proc_ref("world0_0"), addr.clone());
5192
5193 let (port, _receiver) = mbox.open_once_port::<u64>();
5194 let port = port.bind();
5195 let target: Addr = port.port_addr().clone().into();
5196 let (return_handle, mut return_receiver) =
5197 crate::mailbox::undeliverable::new_undeliverable_port();
5198
5199 router
5200 .serialize_and_send_once(port, 123u64, return_handle)
5201 .unwrap();
5202
5203 let undelivered = return_receiver
5204 .recv()
5205 .await
5206 .unwrap()
5207 .into_message()
5208 .expect("expected returned envelope");
5209 let transport = root_transport_failure(&undelivered);
5210 assert_eq!(transport.target, target);
5211 let TransportFailureReason::DialFailed {
5212 addr: failure_addr,
5213 error,
5214 } = &transport.reason
5215 else {
5216 panic!("expected dial failure, got {}", transport.reason);
5217 };
5218 assert_eq!(failure_addr, &addr);
5219 assert!(
5220 error.contains("channel closed"),
5221 "unexpected error: {error}"
5222 );
5223 }
5224
5225 #[cfg(any())]
5226 #[tokio::test]
5227 async fn test_dial_mailbox_router_default() {
5228 let mbox0 = Mailbox::new(test_actor_id("world0_0", "actor0"));
5229 let mbox1 = Mailbox::new(test_actor_id("world1_0", "actor0"));
5230 let mbox2 = Mailbox::new(test_actor_id("world1_1", "actor0"));
5231 let mbox3 = Mailbox::new(test_actor_id("world1_1", "actor1"));
5232
5233 let root = MailboxRouter::new();
5236 let world0_router = DialMailboxRouter::new_with_default(root.boxed());
5237 let world1_router = DialMailboxRouter::new_with_default(root.boxed());
5238
5239 root.bind(test_proc_ref("world0"), world0_router.clone());
5240 root.bind(test_proc_ref("world1"), world1_router.clone());
5241
5242 let mailboxes = [&mbox0, &mbox1, &mbox2, &mbox3];
5243
5244 let mut handles = Vec::new(); for mbox in mailboxes.iter() {
5246 let (addr, rx) = channel::serve(ChannelAddr::any(ChannelTransport::Local)).unwrap();
5247 let handle = (*mbox).clone().serve(rx);
5248 handles.push(handle);
5249
5250 eprintln!("{}: {}", mbox.actor_addr(), addr);
5251 if mbox
5252 .actor_addr()
5253 .proc_addr()
5254 .label()
5255 .is_some_and(|l| l.as_str().starts_with("world0"))
5256 {
5257 world0_router.bind(Addr::from(mbox.actor_addr().clone()), addr);
5258 } else {
5259 world1_router.bind(Addr::from(mbox.actor_addr().clone()), addr);
5260 }
5261 }
5262
5263 for router in [root.boxed(), world0_router.boxed(), world1_router.boxed()] {
5265 for mbox in mailboxes.iter() {
5266 let (port, receiver) = mbox.open_once_port::<u64>();
5267 let port = port.bind();
5268 router
5269 .serialize_and_send_once(port, 123u64, monitored_return_handle())
5270 .unwrap();
5271 assert_eq!(receiver.recv().await.unwrap(), 123u64);
5272 }
5273 }
5274 }
5275
5276 #[test]
5277 fn test_is_stale_session_close() {
5278 let stale = TxStatus::Closed(CloseReason::SequenceMismatch(
5280 "out-of-sequence message, expected seq 0, got 7".into(),
5281 ));
5282 assert!(is_stale_session_close(&stale));
5283
5284 let oversize = TxStatus::Closed(CloseReason::OversizedFrame {
5287 size: 55_001_392,
5288 max: 50_000_000,
5289 });
5290 assert!(!is_stale_session_close(&oversize));
5291
5292 let generic = TxStatus::Closed(CloseReason::Other("test teardown".into()));
5293 assert!(!is_stale_session_close(&generic));
5294
5295 assert!(!is_stale_session_close(&TxStatus::Active));
5297 }
5298
5299 #[tokio::test]
5300 async fn test_dial_router_keeps_client_closed_for_non_stale_reason() {
5301 let mbox = Mailbox::new(test_actor_id("non_stale_close_0", "actor0"));
5308 let (addr, rx) =
5309 channel::serve::<MessageEnvelope>(ChannelAddr::any(ChannelTransport::Local)).unwrap();
5310 let h = mbox.clone().serve(rx);
5311
5312 let router = DialMailboxRouter::new();
5313 router.bind(Addr::from(mbox.actor_addr().clone()), addr.clone());
5314
5315 let client1 = router.dial(&addr, mbox.actor_addr()).unwrap();
5316 let mut status = client1.tx_status().clone();
5317
5318 let (port, receiver) = mbox.open_once_port::<u64>();
5325 let port = port.bind();
5326 router
5327 .serialize_and_send_once(port, 67u64, monitored_return_handle())
5328 .unwrap();
5329 assert_eq!(receiver.recv().await.unwrap(), 67u64);
5330 client1.flush().await.unwrap();
5331
5332 h.stop("test teardown");
5336 let _ = h.await;
5337 while !status.borrow_and_update().is_closed() {
5338 status.changed().await.unwrap();
5339 }
5340 assert!(!is_stale_session_close(&status.borrow()));
5341
5342 let client2 = router.dial(&addr, mbox.actor_addr()).unwrap();
5343 assert!(
5344 Arc::ptr_eq(&client1, &client2),
5345 "router must not evict a client closed for a non-stale reason"
5346 );
5347 let client3 = router.dial(&addr, mbox.actor_addr()).unwrap();
5348 assert!(Arc::ptr_eq(&client1, &client3));
5349 }
5350
5351 #[tokio::test]
5352 async fn test_enqueue_port() {
5353 let proc = Proc::isolated();
5354 let client = proc.client("client");
5355
5356 let count = Arc::new(AtomicUsize::new(0));
5357 let count_clone = count.clone();
5358 let port = client.mailbox().open_enqueue_port(move |_, n| {
5359 count_clone.fetch_add(n, Ordering::SeqCst);
5360 Ok(())
5361 });
5362
5363 port.post(&client, 10);
5364 port.post(&client, 5);
5365 port.post(&client, 1);
5366 port.post(&client, 0);
5367
5368 assert_eq!(count.load(Ordering::SeqCst), 16);
5369 }
5370
5371 #[derive(Clone, Debug, Serialize, Deserialize, typeuri::Named)]
5372 struct TestMessage;
5373
5374 #[derive(Clone, Debug, Serialize, Deserialize, typeuri::Named)]
5375 #[named(name = "some::custom::path")]
5376 struct TestMessage2;
5377
5378 #[test]
5379 fn test_remote_message_macros() {
5380 assert_eq!(
5381 TestMessage::typename(),
5382 "hyperactor::mailbox::tests::TestMessage"
5383 );
5384 assert_eq!(TestMessage2::typename(), "some::custom::path");
5385 }
5386
5387 #[test]
5388 fn test_message_envelope_display() {
5389 #[derive(typeuri::Named, Serialize, Deserialize)]
5390 struct MyTest {
5391 a: u64,
5392 b: String,
5393 }
5394 wirevalue::register_type!(MyTest);
5395
5396 let envelope = MessageEnvelope::serialize(
5397 test_actor_id("source_0", "actor"),
5398 test_port_id("dest_1", "actor", 123),
5399 &MyTest {
5400 a: 123,
5401 b: "hello".into(),
5402 },
5403 Flattrs::new(),
5404 )
5405 .unwrap();
5406
5407 assert!(format!("{}", envelope).contains("MyTest{\"a\":123,\"b\":\"hello\"}"));
5409 }
5410
5411 #[derive(Debug, Default)]
5412 struct Foo;
5413
5414 impl Actor for Foo {}
5415
5416 #[tokio::test]
5419 async fn test_actor_delivery_failure() {
5420 use crate::actor::ActorStatus;
5423 use crate::testing::proc_supervison::ProcSupervisionCoordinator;
5424
5425 let proc_forwarder = BoxedMailboxSender::new(DialMailboxRouter::new_with_default(
5426 BoxedMailboxSender::new(PanickingMailboxSender),
5427 ));
5428 let proc_id = test_proc_id("quux_0");
5429 let mut proc = Proc::configured(proc_id.clone(), proc_forwarder);
5430 let (_reported, _coordinator) = ProcSupervisionCoordinator::set(&proc).await.unwrap();
5431 let client = proc.client("client");
5432
5433 let foo = proc.spawn(Foo);
5434 let return_handle = foo.port::<Undeliverable<MessageEnvelope>>();
5435 let message = MessageEnvelope::new(
5436 foo.actor_addr().clone(),
5437 test_port_id("corge_0", "bar", 9999),
5438 wirevalue::Any::serialize(&1u64).unwrap(),
5439 Flattrs::new(),
5440 );
5441 return_handle.post(&client, Undeliverable::Returned(message));
5442
5443 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
5444
5445 let foo_status = foo.status();
5446 assert!(matches!(*foo_status.borrow(), ActorStatus::Failed(_)));
5447 let ActorStatus::Failed(ref msg) = *foo_status.borrow() else {
5448 unreachable!()
5449 };
5450 let msg_str = msg.to_string();
5451 assert!(
5456 msg_str.contains("undeliverable message to"),
5457 "expected destination-named top line, got:\n{msg_str}"
5458 );
5459 assert!(
5460 !msg_str.contains("undeliverable message error"),
5461 "retired neutral fallback must not appear, got:\n{msg_str}"
5462 );
5463 assert!(msg_str.contains("sender:") && msg_str.contains("quux_0"));
5464 assert!(msg_str.contains("dest:") && msg_str.contains("corge_0"));
5465
5466 proc.destroy_and_wait(tokio::time::Duration::from_secs(1), "test cleanup")
5467 .await
5468 .unwrap();
5469 }
5470
5471 #[tokio::test]
5472 async fn test_detached_return_handle() {
5473 let (return_handle, mut return_receiver) =
5474 crate::mailbox::undeliverable::new_undeliverable_port();
5475 let envelope = MessageEnvelope::new(
5477 test_actor_id("foo_0", "bar"),
5478 test_port_id("baz_0", "corge", 9999),
5479 wirevalue::Any::serialize(&1u64).unwrap(),
5480 Flattrs::new(),
5481 );
5482 let proc = Proc::isolated();
5483 let client = proc.client("client");
5484 return_handle.post(&client, Undeliverable::Returned(envelope.clone()));
5485 assert!(
5487 tokio::time::timeout(tokio::time::Duration::from_secs(1), return_receiver.recv())
5488 .await
5489 .is_ok()
5490 );
5491 let monitor_handle = tokio::spawn(async move {
5494 while let Ok(Undeliverable::Returned(mut envelope)) = return_receiver.recv().await {
5495 envelope.push_delivery_failure(DeliveryFailure::new(
5496 UndeliverableReason::Transport(TransportFailure::new(
5497 envelope.dest().clone(),
5498 TransportFailureReason::LinkUnavailable(
5499 "returned in unit test".to_string(),
5500 ),
5501 )),
5502 ));
5503 UndeliverableMailboxSender
5504 .post(envelope, monitored_return_handle());
5505 }
5506 });
5507 drop(return_handle);
5508 assert!(
5509 tokio::time::timeout(tokio::time::Duration::from_secs(1), monitor_handle)
5510 .await
5511 .is_ok()
5512 );
5513 }
5514
5515 async fn verify_receiver(coalesce: bool, drop_sender: bool) {
5516 fn create_receiver<M>(
5517 coalesce: bool,
5518 ) -> (mpsc::UnboundedSender<SequencedEnvelope<M>>, PortReceiver<M>) {
5519 let dummy_actor_ref: ActorAddr = test_actor_id("world_0", "actor");
5522 let dummy_state = State::new(dummy_actor_ref.clone());
5523 let dummy_port_id = dummy_actor_ref.port_addr(Port::from(0));
5524 let (sender, receiver) = sequenced_unbounded::<SequencedEnvelope<M>>();
5525 let receiver = PortReceiver::new(
5526 receiver,
5527 dummy_port_id,
5528 coalesce,
5529 Mailbox {
5530 inner: Arc::new(dummy_state),
5531 },
5532 );
5533 (sender, receiver)
5534 }
5535
5536 fn send_direct<M>(sender: &mpsc::UnboundedSender<SequencedEnvelope<M>>, message: M) {
5537 sender
5538 .send(SequencedEnvelope::new(SeqInfo::Direct, None, message))
5539 .unwrap();
5540 }
5541
5542 {
5544 let (sender, mut receiver) = create_receiver::<u64>(coalesce);
5545 assert!(receiver.drain().is_empty());
5546
5547 send_direct(&sender, 0);
5548 send_direct(&sender, 1);
5549 send_direct(&sender, 2);
5550 send_direct(&sender, 3);
5551 send_direct(&sender, 4);
5552 send_direct(&sender, 5);
5553 send_direct(&sender, 6);
5554 send_direct(&sender, 7);
5555
5556 if drop_sender {
5557 drop(sender);
5558 }
5559
5560 if !coalesce {
5561 assert_eq!(receiver.drain(), vec![0, 1, 2, 3, 4, 5, 6, 7]);
5562 } else {
5563 assert_eq!(receiver.drain(), vec![7]);
5564 }
5565
5566 assert!(receiver.drain().is_empty());
5567 assert!(receiver.drain().is_empty());
5568 }
5569
5570 {
5572 let (sender, mut receiver) = create_receiver::<u64>(coalesce);
5573 assert!(receiver.try_recv().unwrap().is_none());
5574
5575 send_direct(&sender, 0);
5576 send_direct(&sender, 1);
5577 send_direct(&sender, 2);
5578 send_direct(&sender, 3);
5579
5580 if drop_sender {
5581 drop(sender);
5582 }
5583
5584 if !coalesce {
5585 assert_eq!(receiver.try_recv().unwrap().unwrap(), 0);
5586 assert_eq!(receiver.try_recv().unwrap().unwrap(), 1);
5587 assert_eq!(receiver.try_recv().unwrap().unwrap(), 2);
5588 }
5589 assert_eq!(receiver.try_recv().unwrap().unwrap(), 3);
5590 if drop_sender {
5591 assert_matches!(
5592 receiver.try_recv().unwrap_err().kind(),
5593 MailboxErrorKind::Closed
5594 );
5595 assert_matches!(
5597 receiver.try_recv().unwrap_err().kind(),
5598 MailboxErrorKind::Closed
5599 );
5600 } else {
5601 assert!(receiver.try_recv().unwrap().is_none());
5602 assert!(receiver.try_recv().unwrap().is_none());
5604 }
5605 }
5606 {
5608 let (sender, mut receiver) = create_receiver::<u64>(coalesce);
5609 assert!(
5610 tokio::time::timeout(tokio::time::Duration::from_secs(1), receiver.recv())
5611 .await
5612 .is_err()
5613 );
5614
5615 send_direct(&sender, 4);
5616 send_direct(&sender, 5);
5617 send_direct(&sender, 6);
5618 send_direct(&sender, 7);
5619
5620 if drop_sender {
5621 drop(sender);
5622 }
5623
5624 if !coalesce {
5625 assert_eq!(receiver.recv().await.unwrap(), 4);
5626 assert_eq!(receiver.recv().await.unwrap(), 5);
5627 assert_eq!(receiver.recv().await.unwrap(), 6);
5628 }
5629 assert_eq!(receiver.recv().await.unwrap(), 7);
5630 if drop_sender {
5631 assert_matches!(
5632 receiver.recv().await.unwrap_err().kind(),
5633 MailboxErrorKind::Closed
5634 );
5635 assert_matches!(
5637 receiver.recv().await.unwrap_err().kind(),
5638 MailboxErrorKind::Closed
5639 );
5640 } else {
5641 assert!(
5642 tokio::time::timeout(tokio::time::Duration::from_secs(1), receiver.recv())
5643 .await
5644 .is_err()
5645 );
5646 }
5647 }
5648 }
5649
5650 #[tokio::test]
5651 async fn test_receiver_basic_default() {
5652 verify_receiver(false, false).await
5653 }
5654
5655 #[tokio::test]
5656 async fn test_receiver_basic_latest() {
5657 verify_receiver(true, false).await
5658 }
5659
5660 #[tokio::test]
5661 async fn test_receiver_after_sender_drop_default() {
5662 verify_receiver(false, true).await
5663 }
5664
5665 #[tokio::test]
5666 async fn test_receiver_after_sender_drop_latest() {
5667 verify_receiver(true, true).await
5668 }
5669
5670 struct Setup {
5671 receiver: PortReceiver<u64>,
5672 actor0: crate::Client,
5673 actor1: crate::Client,
5674 port_id: PortAddr,
5675 port_id1: PortAddr,
5676 port_id2: PortAddr,
5677 port_id2_1: PortAddr,
5678 }
5679
5680 async fn setup_split_port_ids(
5681 reducer_spec: Option<ReducerSpec>,
5682 reducer_mode: ReducerMode,
5683 ) -> Setup {
5684 let proc = Proc::isolated();
5685 let actor0 = proc.client("actor0");
5686 let actor1 = proc.client("actor1");
5687
5688 let (port_handle, receiver) = actor0.open_port::<u64>();
5690 let port_id = port_handle.bind().port_addr().clone();
5691
5692 let port_id1 = port_id
5694 .split(&actor1, reducer_spec.clone(), reducer_mode.clone(), true)
5695 .unwrap();
5696 let port_id2 = port_id
5697 .split(&actor1, reducer_spec.clone(), reducer_mode.clone(), true)
5698 .unwrap();
5699
5700 let port_id2_1 = port_id2
5702 .split(&actor1, reducer_spec, reducer_mode.clone(), true)
5703 .unwrap();
5704
5705 Setup {
5706 receiver,
5707 actor0,
5708 actor1,
5709 port_id,
5710 port_id1,
5711 port_id2,
5712 port_id2_1,
5713 }
5714 }
5715
5716 fn post(cx: &impl context::Actor, port_id: PortAddr, msg: u64) {
5717 let serialized = wirevalue::Any::serialize(&msg).unwrap();
5718 port_id.send(cx, serialized);
5719 }
5720
5721 #[async_timed_test(timeout_secs = 30)]
5722 #[cfg_attr(not(fbcode_build), ignore)]
5724 async fn test_split_port_id_no_reducer() {
5725 let Setup {
5726 mut receiver,
5727 actor0,
5728 actor1,
5729 port_id,
5730 port_id1,
5731 port_id2,
5732 port_id2_1,
5733 ..
5734 } = setup_split_port_ids(None, ReducerMode::default()).await;
5735 post(&actor0, port_id.clone(), 1);
5737 assert_eq!(receiver.recv().await.unwrap(), 1);
5738 post(&actor1, port_id1.clone(), 2);
5739 assert_eq!(receiver.recv().await.unwrap(), 2);
5740 post(&actor1, port_id2.clone(), 3);
5741 assert_eq!(receiver.recv().await.unwrap(), 3);
5742 post(&actor1, port_id2_1.clone(), 4);
5743 assert_eq!(receiver.recv().await.unwrap(), 4);
5744
5745 tokio::time::sleep(Duration::from_secs(2)).await;
5747 let msg = receiver.try_recv().unwrap();
5748 assert_eq!(msg, None);
5749 }
5750
5751 async fn wait_for(
5752 receiver: &mut PortReceiver<u64>,
5753 expected_size: usize,
5754 timeout_duration: Duration,
5755 ) -> anyhow::Result<Vec<u64>> {
5756 let mut messeges = vec![];
5757
5758 tokio::time::timeout(timeout_duration, async {
5759 loop {
5760 let msg = receiver.recv().await.unwrap();
5761 messeges.push(msg);
5762 if messeges.len() == expected_size {
5763 break;
5764 }
5765 }
5766 })
5767 .await?;
5768 Ok(messeges)
5769 }
5770
5771 #[async_timed_test(timeout_secs = 30)]
5772 async fn test_split_port_id_sum_reducer() {
5773 let config = hyperactor_config::global::lock();
5774 let _config_guard = config.override_key(crate::config::SPLIT_MAX_BUFFER_SIZE, 1);
5775
5776 let sum_accumulator = accum::sum::<u64>();
5777 let reducer_spec = sum_accumulator.reducer_spec();
5778 let Setup {
5779 mut receiver,
5780 actor0,
5781 actor1,
5782 port_id,
5783 port_id1,
5784 port_id2,
5785 port_id2_1,
5786 ..
5787 } = setup_split_port_ids(reducer_spec, ReducerMode::default()).await;
5788 post(&actor0, port_id.clone(), 4);
5789 post(&actor1, port_id1.clone(), 2);
5790 post(&actor1, port_id2.clone(), 3);
5791 post(&actor1, port_id2_1.clone(), 1);
5792 let mut messages = wait_for(&mut receiver, 4, Duration::from_secs(2))
5793 .await
5794 .unwrap();
5795 messages.sort();
5798 assert_eq!(messages, vec![1, 2, 3, 4]);
5799
5800 tokio::time::sleep(Duration::from_secs(2)).await;
5802 let msg = receiver.try_recv().unwrap();
5803 assert_eq!(msg, None);
5804 }
5805
5806 #[async_timed_test(timeout_secs = 30)]
5807 #[cfg_attr(not(fbcode_build), ignore)]
5809 async fn test_split_port_id_every_n_messages() {
5810 let config = hyperactor_config::global::lock();
5811 let _config_guard =
5812 config.override_key(crate::config::SPLIT_MAX_BUFFER_AGE, Duration::from_mins(10));
5813 let proc = Proc::isolated();
5814 let actor = proc.client("actor");
5815 let (port_handle, mut receiver) = actor.open_port::<u64>();
5816 let port_id = port_handle.bind().port_addr().clone();
5817 let reducer_spec = accum::sum::<u64>().reducer_spec();
5819 let split_port_id = port_id
5820 .split(
5821 &actor,
5822 reducer_spec,
5823 ReducerMode::Streaming(accum::StreamingReducerOpts {
5824 max_update_interval: Some(Duration::from_mins(10)),
5825 initial_update_interval: Some(Duration::from_mins(10)),
5826 }),
5827 true,
5828 )
5829 .unwrap();
5830
5831 for msg in [1, 5, 3, 4, 2, 91, 92, 93, 94] {
5833 post(&actor, split_port_id.clone(), msg);
5834 }
5835 let messages = wait_for(&mut receiver, 1, Duration::from_secs(2))
5838 .await
5839 .unwrap();
5840 assert_eq!(messages, vec![15]);
5841
5842 tokio::time::sleep(Duration::from_secs(2)).await;
5845 let msg = receiver.try_recv().unwrap();
5846 assert_eq!(msg, None);
5847 }
5848
5849 #[async_timed_test(timeout_secs = 30)]
5850 async fn test_split_port_timeout_flush() {
5851 let config = hyperactor_config::global::lock();
5852 let _config_guard = config.override_key(crate::config::SPLIT_MAX_BUFFER_SIZE, 100);
5853
5854 let Setup {
5855 mut receiver,
5856 actor0: _actor0,
5857 actor1,
5858 port_id: _,
5859 port_id1,
5860 ..
5861 } = setup_split_port_ids(
5862 Some(accum::sum::<u64>().reducer_spec().unwrap()),
5863 ReducerMode::Streaming(accum::StreamingReducerOpts {
5864 max_update_interval: Some(Duration::from_millis(50)),
5865 initial_update_interval: Some(Duration::from_millis(50)),
5866 }),
5867 )
5868 .await;
5869
5870 post(&actor1, port_id1.clone(), 10);
5871 post(&actor1, port_id1.clone(), 20);
5872 post(&actor1, port_id1.clone(), 30);
5873
5874 tokio::time::sleep(Duration::from_millis(10)).await;
5876 let msg = receiver.try_recv().unwrap();
5877 assert_eq!(msg, None);
5878
5879 tokio::time::sleep(Duration::from_millis(100)).await;
5881
5882 let msg = receiver.recv().await.unwrap();
5884 assert_eq!(msg, 60); let msg = receiver.try_recv().unwrap();
5888 assert_eq!(msg, None);
5889 }
5890
5891 #[async_timed_test(timeout_secs = 30)]
5892 async fn test_split_port_timeout_and_size_flush() {
5893 let config = hyperactor_config::global::lock();
5894 let _config_guard = config.override_key(crate::config::SPLIT_MAX_BUFFER_SIZE, 3);
5895
5896 let Setup {
5897 mut receiver,
5898 actor0: _actor0,
5899 actor1,
5900 port_id: _,
5901 port_id1,
5902 ..
5903 } = setup_split_port_ids(
5904 Some(accum::sum::<u64>().reducer_spec().unwrap()),
5905 ReducerMode::Streaming(accum::StreamingReducerOpts {
5906 max_update_interval: Some(Duration::from_millis(50)),
5907 initial_update_interval: Some(Duration::from_millis(50)),
5908 }),
5909 )
5910 .await;
5911
5912 post(&actor1, port_id1.clone(), 10);
5913 post(&actor1, port_id1.clone(), 20);
5914 post(&actor1, port_id1.clone(), 30);
5915 post(&actor1, port_id1.clone(), 40);
5916
5917 let msg = receiver.recv().await.unwrap();
5919 assert_eq!(msg, 60);
5920
5921 let msg = receiver.recv().await.unwrap();
5923 assert_eq!(msg, 40);
5924
5925 let msg = receiver.try_recv().unwrap();
5927 assert_eq!(msg, None);
5928 }
5929
5930 #[async_timed_test(timeout_secs = 30)]
5931 async fn test_split_port_idle_flush_complete() {
5932 let proc = Proc::isolated();
5933 let actor = proc.client("actor");
5934
5935 let (port_handle, mut receiver) = actor.open_port::<u64>();
5936
5937 let port_id = port_handle.bind().port_addr().clone();
5938
5939 let split_port_id = port_id
5940 .split(
5941 &actor,
5942 accum::sum::<u64>().reducer_spec(),
5943 ReducerMode::IdleFlush {
5944 expected: NonZeroUsize::new(3).expect("3 is non-zero"),
5945 idle_timeout: Duration::from_secs(10),
5946 abandon_timeout: Duration::from_secs(30),
5947 },
5948 true,
5949 )
5950 .unwrap();
5951
5952 post(&actor, split_port_id.clone(), 10);
5953 post(&actor, split_port_id.clone(), 20);
5954
5955 assert_eq!(
5956 receiver.try_recv().unwrap(),
5957 None,
5958 "the reducer should not emit before receiving all expected updates"
5959 );
5960
5961 post(&actor, split_port_id, 30);
5962
5963 let reduced = tokio::time::timeout(Duration::from_secs(2), receiver.recv())
5964 .await
5965 .expect("complete idle-flush result should arrive")
5966 .unwrap();
5967
5968 assert_eq!(reduced, 60, "10 + 20 + 30 should reduce to 60");
5969 }
5970
5971 #[async_timed_test(timeout_secs = 30)]
5972 async fn test_split_port_idle_flush_partial() {
5973 let proc = Proc::isolated();
5974 let actor = proc.client("actor");
5975 let (port_handle, mut receiver) = actor.open_port::<u64>();
5976 let port_id = port_handle.bind().port_addr().clone();
5977 let split_port_id = port_id
5978 .split(
5979 &actor,
5980 accum::sum::<u64>().reducer_spec(),
5981 ReducerMode::IdleFlush {
5982 expected: NonZeroUsize::new(3).expect("3 is non-zero"),
5983 idle_timeout: Duration::from_millis(50),
5984 abandon_timeout: Duration::from_secs(30),
5985 },
5986 true,
5987 )
5988 .unwrap();
5989
5990 post(&actor, split_port_id.clone(), 10);
5991 post(&actor, split_port_id, 20);
5992
5993 let reduced = tokio::time::timeout(Duration::from_secs(2), receiver.recv())
5994 .await
5995 .expect("partial idle-flush result should arrive after the idle interval")
5996 .unwrap();
5997 assert_eq!(reduced, 30, "10 + 20 should reduce to the partial value 30");
5998 }
5999
6000 #[tokio::test(start_paused = true)]
6001 async fn test_split_port_idle_flush_restarts_idle_timeout() {
6002 let proc = Proc::isolated();
6003 let actor = proc.client("actor");
6004 let (port_handle, mut receiver) = actor.open_port::<u64>();
6005 let port_id = port_handle.bind().port_addr().clone();
6006 let split_port_id = port_id
6007 .split(
6008 &actor,
6009 accum::sum::<u64>().reducer_spec(),
6010 ReducerMode::IdleFlush {
6011 expected: NonZeroUsize::new(6).expect("6 is non-zero"),
6012 idle_timeout: Duration::from_millis(50),
6013 abandon_timeout: Duration::from_secs(30),
6014 },
6015 true,
6016 )
6017 .unwrap();
6018
6019 post(&actor, split_port_id.clone(), 10);
6020 tokio::task::yield_now().await;
6021
6022 for update in [20, 30, 40, 50] {
6023 tokio::time::advance(Duration::from_millis(25)).await;
6024 post(&actor, split_port_id.clone(), update);
6025 tokio::task::yield_now().await;
6026 }
6027
6028 assert_eq!(
6029 receiver.try_recv().unwrap(),
6030 None,
6031 "the reducer should not flush while replies arrive within the idle timeout"
6032 );
6033
6034 tokio::time::advance(Duration::from_millis(49)).await;
6035 tokio::task::yield_now().await;
6036 assert_eq!(
6037 receiver.try_recv().unwrap(),
6038 None,
6039 "the final reply should restart the full idle timeout"
6040 );
6041
6042 tokio::time::advance(Duration::from_millis(11)).await;
6043 let reduced = receiver.recv().await.unwrap();
6044 assert_eq!(reduced, 150, "10 + 20 + 30 + 40 + 50 should reduce to 150");
6045 }
6046
6047 #[tokio::test(start_paused = true)]
6048 async fn test_split_port_idle_flush_abandons_incomplete_port() {
6049 let proc = Proc::isolated();
6050 let actor = proc.client("actor");
6051 let (port_handle, mut receiver) = actor.open_port::<u64>();
6052 let port_id = port_handle.bind().port_addr().clone();
6053 let split_port_id = port_id
6054 .split(
6055 &actor,
6056 accum::sum::<u64>().reducer_spec(),
6057 ReducerMode::IdleFlush {
6058 expected: NonZeroUsize::new(2).expect("2 is non-zero"),
6059 idle_timeout: Duration::from_millis(50),
6060 abandon_timeout: Duration::from_millis(200),
6061 },
6062 false,
6063 )
6064 .unwrap();
6065
6066 assert!(
6067 actor
6068 .mailbox()
6069 .inner
6070 .ports
6071 .contains_key(&split_port_id.port()),
6072 "the split port should initially be bound"
6073 );
6074
6075 post(&actor, split_port_id.clone(), 10);
6076 tokio::task::yield_now().await;
6077 tokio::time::advance(Duration::from_millis(50)).await;
6078 assert_eq!(receiver.recv().await.unwrap(), 10);
6079
6080 tokio::time::advance(Duration::from_millis(149)).await;
6081 tokio::task::yield_now().await;
6082 assert!(
6083 actor
6084 .mailbox()
6085 .inner
6086 .ports
6087 .contains_key(&split_port_id.port()),
6088 "the incomplete split port should remain bound before abandonment"
6089 );
6090
6091 tokio::time::advance(Duration::from_millis(2)).await;
6092 tokio::task::yield_now().await;
6093 assert!(
6094 !actor
6095 .mailbox()
6096 .inner
6097 .ports
6098 .contains_key(&split_port_id.port()),
6099 "the incomplete split port should be removed after abandonment"
6100 );
6101 }
6102
6103 #[async_timed_test(timeout_secs = 30)]
6104 async fn test_split_port_once_mode_basic() {
6105 let proc = Proc::isolated();
6106 let actor = proc.client("actor");
6107 let (port_handle, mut receiver) = actor.open_port::<u64>();
6108 let port_id = port_handle.bind().port_addr().clone();
6109
6110 let reducer_spec = accum::sum::<u64>().reducer_spec();
6112 let split_port_id = port_id
6113 .split(&actor, reducer_spec, ReducerMode::Once(3), true)
6114 .unwrap();
6115
6116 post(&actor, split_port_id.clone(), 10);
6118 post(&actor, split_port_id.clone(), 20);
6119 post(&actor, split_port_id.clone(), 30);
6120
6121 let msg = receiver.recv().await.unwrap();
6123 assert_eq!(msg, 60); tokio::time::sleep(Duration::from_millis(100)).await;
6127 let msg = receiver.try_recv().unwrap();
6128 assert_eq!(msg, None);
6129 }
6130
6131 #[derive(Debug)]
6132 #[hyperactor::export(handlers = [u64])]
6133 struct SplitPortReceivingActor {
6134 received: PortRef<String>,
6135 }
6136
6137 impl Actor for SplitPortReceivingActor {}
6138
6139 #[async_trait]
6140 impl Handler<u64> for SplitPortReceivingActor {
6141 async fn handle(&mut self, cx: &crate::Context<Self>, msg: u64) -> anyhow::Result<()> {
6142 let endpoint = cx
6143 .headers()
6144 .get(headers::OPERATION_ENDPOINT)
6145 .unwrap_or_default();
6146 self.received
6147 .post(cx, format!("OPERATION_ENDPOINT={endpoint} sum={msg}"));
6148 Ok(())
6149 }
6150 }
6151
6152 #[async_timed_test(timeout_secs = 30)]
6153 async fn test_split_port_preserves_operation_context_headers() {
6154 let proc = Proc::isolated();
6155 let client = proc.client("client");
6156 let (received_handle, mut observed_rx) = client.open_port::<String>();
6157 let capture_handle = proc.spawn_with_label(
6158 "split_port_receiver",
6159 SplitPortReceivingActor {
6160 received: received_handle.bind(),
6161 },
6162 );
6163 let capture_ref: ActorRef<SplitPortReceivingActor> = capture_handle.bind();
6164 let port_id = capture_ref.port::<u64>().port_addr().clone();
6165
6166 let split_port_id = port_id
6167 .split(
6168 &client,
6169 accum::sum::<u64>().reducer_spec(),
6171 ReducerMode::Once(2),
6172 true,
6173 )
6174 .unwrap();
6175
6176 let mut headers = Flattrs::new();
6177 headers.set(headers::OPERATION_ENDPOINT, "endpoint.call()".to_string());
6178 client.post(
6179 split_port_id.clone(),
6180 headers.clone(),
6181 wirevalue::Any::serialize(&1u64).unwrap(),
6183 true,
6184 crate::context::SeqInfoPolicy::AssignNew,
6185 );
6186 client.post(
6187 split_port_id,
6188 headers,
6189 wirevalue::Any::serialize(&2u64).unwrap(),
6191 true,
6192 crate::context::SeqInfoPolicy::AssignNew,
6193 );
6194
6195 assert_eq!(
6196 observed_rx.recv().await.unwrap(),
6197 "OPERATION_ENDPOINT=endpoint.call() sum=3"
6198 );
6199 }
6200
6201 #[async_timed_test(timeout_secs = 30)]
6202 async fn test_split_port_once_mode_teardown() {
6203 let proc = Proc::isolated();
6204 let actor = proc.client("actor");
6205 let (port_handle, mut receiver) = actor.open_port::<u64>();
6206 let port_id = port_handle.bind().port_addr().clone();
6207
6208 let (undeliverable_handle, mut undeliverable_receiver) =
6210 undeliverable::new_undeliverable_port();
6211
6212 let reducer_spec = accum::sum::<u64>().reducer_spec();
6214 let split_port_id = port_id
6215 .split(&actor, reducer_spec, ReducerMode::Once(3), true)
6216 .unwrap();
6217
6218 post(&actor, split_port_id.clone(), 10);
6220 post(&actor, split_port_id.clone(), 20);
6221 post(&actor, split_port_id.clone(), 30);
6222
6223 let msg = receiver.recv().await.unwrap();
6225 assert_eq!(msg, 60); let serialized = wirevalue::Any::serialize(&100u64).unwrap();
6229 let envelope = MessageEnvelope::new(
6230 actor.mailbox().actor_addr().clone(),
6231 split_port_id.clone(),
6232 serialized,
6233 Flattrs::new(),
6234 );
6235 actor.mailbox().post(envelope, undeliverable_handle);
6236
6237 let undeliverable =
6239 tokio::time::timeout(Duration::from_secs(2), undeliverable_receiver.recv())
6240 .await
6241 .expect("should receive undeliverable message")
6242 .expect("undeliverable receiver closed");
6243
6244 let split_port_ref: PortAddr = split_port_id;
6246 assert_eq!(
6247 undeliverable
6248 .into_message()
6249 .expect("expected returned envelope")
6250 .dest(),
6251 &split_port_ref
6252 );
6253
6254 let msg = receiver.try_recv().unwrap();
6256 assert_eq!(msg, None);
6257 }
6258
6259 #[test]
6260 fn test_dial_mailbox_router_prefixes_empty() {
6261 assert_eq!(DialMailboxRouter::new().prefixes().len(), 0);
6262 }
6263
6264 #[test]
6265 fn test_dial_mailbox_router_prefixes_single_entry() {
6266 let router = DialMailboxRouter::new();
6267 router.bind(test_proc_ref("world0"), "unix!@1".parse().unwrap());
6268
6269 let prefixes: Vec<Addr> = router.prefixes().into_iter().collect();
6270 assert_eq!(prefixes.len(), 1);
6271 assert_eq!(prefixes[0], test_proc_ref("world0"));
6272 }
6273
6274 #[test]
6275 fn test_dial_mailbox_router_prefixes_no_overlap() {
6276 let router = DialMailboxRouter::new();
6277 router.bind(test_proc_ref("world0"), "unix!@1".parse().unwrap());
6278 router.bind(test_proc_ref("world1"), "unix!@2".parse().unwrap());
6279 router.bind(test_proc_ref("world2"), "unix!@3".parse().unwrap());
6280
6281 let mut prefixes: Vec<Addr> = router.prefixes().into_iter().collect();
6282 prefixes.sort();
6283
6284 let mut expected = vec![
6285 test_proc_ref("world0"),
6286 test_proc_ref("world1"),
6287 test_proc_ref("world2"),
6288 ];
6289 expected.sort();
6290
6291 assert_eq!(prefixes, expected);
6292 }
6293
6294 #[test]
6295 fn test_dial_mailbox_router_prefixes_with_overlaps() {
6296 let router = DialMailboxRouter::new();
6297 router.bind(test_proc_ref("world0"), "unix!@1".parse().unwrap());
6299 router.bind(test_proc_ref("world0_0"), "unix!@2".parse().unwrap());
6300 router.bind(test_proc_ref("world0_1"), "unix!@3".parse().unwrap());
6301 router.bind(test_proc_ref("world1"), "unix!@4".parse().unwrap());
6302 router.bind(test_proc_ref("world1_0"), "unix!@5".parse().unwrap());
6303
6304 let mut prefixes: Vec<Addr> = router.prefixes().into_iter().collect();
6305 prefixes.sort();
6306
6307 let mut expected = vec![
6308 test_proc_ref("world0"),
6309 test_proc_ref("world0_0"),
6310 test_proc_ref("world0_1"),
6311 test_proc_ref("world1"),
6312 test_proc_ref("world1_0"),
6313 ];
6314 expected.sort();
6315
6316 assert_eq!(prefixes, expected);
6317 }
6318
6319 #[test]
6320 fn test_dial_mailbox_router_prefixes_complex_hierarchy() {
6321 let router = DialMailboxRouter::new();
6322 router.bind(test_proc_ref("world0"), "unix!@1".parse().unwrap());
6324 router.bind(test_proc_ref("world0_0"), "unix!@2".parse().unwrap());
6325 router.bind(
6326 test_actor_ref("world0_0", "actor1"),
6327 "unix!@3".parse().unwrap(),
6328 );
6329 router.bind(test_proc_ref("world1_0"), "unix!@4".parse().unwrap());
6330 router.bind(test_proc_ref("world1_1"), "unix!@5".parse().unwrap());
6331 router.bind(
6332 test_actor_ref("world2_0", "actor0"),
6333 "unix!@6".parse().unwrap(),
6334 );
6335
6336 let mut prefixes: Vec<Addr> = router.prefixes().into_iter().collect();
6337 prefixes.sort();
6338
6339 let mut expected = vec![
6346 test_proc_ref("world0"),
6347 test_proc_ref("world0_0"),
6348 test_proc_ref("world1_0"),
6349 test_proc_ref("world1_1"),
6350 test_actor_ref("world2_0", "actor0"),
6351 ];
6352 expected.sort();
6353
6354 assert_eq!(prefixes, expected);
6355 }
6356
6357 #[test]
6358 fn test_dial_mailbox_router_prefixes_same_level() {
6359 let router = DialMailboxRouter::new();
6360 router.bind(test_proc_ref("world0_0"), "unix!@1".parse().unwrap());
6361 router.bind(test_proc_ref("world0_1"), "unix!@2".parse().unwrap());
6362 router.bind(test_proc_ref("world0_2"), "unix!@3".parse().unwrap());
6363
6364 let mut prefixes: Vec<Addr> = router.prefixes().into_iter().collect();
6365 prefixes.sort();
6366
6367 let mut expected = vec![
6369 test_proc_ref("world0_0"),
6370 test_proc_ref("world0_1"),
6371 test_proc_ref("world0_2"),
6372 ];
6373 expected.sort();
6374
6375 assert_eq!(prefixes, expected);
6376 }
6377
6378 #[derive(Clone, Debug)]
6382 struct AsyncLoopForwarder;
6383
6384 #[async_trait]
6385 impl MailboxSender for AsyncLoopForwarder {
6386 fn post_unchecked(
6387 &self,
6388 envelope: MessageEnvelope,
6389 return_handle: PortHandle<Undeliverable<MessageEnvelope>>,
6390 ) {
6391 let me = self.clone();
6392 tokio::spawn(async move {
6393 me.post(envelope, return_handle);
6395 });
6396 }
6397 }
6398
6399 #[tokio::test]
6400 async fn message_ttl_expires_in_routing_loop_returns_to_sender() {
6401 let actor_id = test_actor_id("world_0", "ttl_actor");
6402 let (ret_port, mut ret_rx) = undeliverable::new_undeliverable_port();
6403
6404 let remote_actor = test_actor_id("remote_world_1", "remote");
6405 let dest = remote_actor.port_addr(4242.into());
6406
6407 let payload = 1234_u64;
6410 let envelope =
6411 MessageEnvelope::serialize(actor_id.clone(), dest.clone(), &payload, Flattrs::new())
6412 .expect("serialize");
6413
6414 AsyncLoopForwarder.post(envelope, ret_port.clone());
6415
6416 let undelivered = tokio::time::timeout(Duration::from_secs(5), ret_rx.recv())
6418 .await
6419 .expect("timed out waiting for undeliverable")
6420 .expect("channel closed")
6421 .into_message()
6422 .expect("expected returned envelope");
6423
6424 let got: u64 = undelivered.deserialized().expect("deserialize");
6426 assert_eq!(got, payload, "payload preserved");
6427 }
6428
6429 #[tokio::test]
6430 async fn message_ttl_success_local_delivery() {
6431 let actor_id = test_actor_id("world_0", "ttl_actor");
6432 let mailbox = Mailbox::new(actor_id.clone());
6433 let (_undeliverable_tx, mut undeliverable_rx) =
6434 mailbox.bind_handler_port::<Undeliverable<MessageEnvelope>>();
6435
6436 let (user_port, mut user_rx) = mailbox.open_port::<u64>();
6438
6439 let payload = 0xC0FFEE_u64;
6441 let envelope = MessageEnvelope::serialize(
6442 actor_id.clone(),
6443 user_port.bind().port_addr().clone(),
6444 &payload,
6445 Flattrs::new(),
6446 )
6447 .expect("serialize");
6448
6449 let return_handle = mailbox
6452 .bound_return_handle()
6453 .unwrap_or(monitored_return_handle());
6454 mailbox.post(envelope, return_handle);
6455
6456 let got = tokio::time::timeout(Duration::from_secs(1), user_rx.recv())
6458 .await
6459 .expect("timed out waiting for local delivery")
6460 .expect("user port closed");
6461 assert_eq!(got, payload);
6462
6463 let no_undeliverable =
6465 tokio::time::timeout(Duration::from_millis(100), undeliverable_rx.recv()).await;
6466 assert!(
6467 no_undeliverable.is_err(),
6468 "unexpected undeliverable returned on successful local delivery"
6469 );
6470 }
6471
6472 #[tokio::test]
6473 async fn test_port_contramap() {
6474 let proc = Proc::isolated();
6475 let client = proc.client("client");
6476 let (handle, mut rx) = client.open_port();
6477
6478 handle
6479 .contramap(|m| (1, m))
6480 .post(&client, "hello".to_string());
6481 assert_eq!(rx.recv().await.unwrap(), (1, "hello".to_string()));
6482 }
6483
6484 #[test]
6485 fn test_bind_open_port_uses_ephemeral_port() {
6486 let mbox = Mailbox::new(test_actor_id("0", "test"));
6487 let (handle, _rx) = mbox.open_port::<String>();
6488 let ephemeral_port = mbox
6489 .actor_addr()
6490 .port_addr(Port::from(handle.inner.bind_target.ephemeral_index()));
6491 let handler_port = mbox.actor_addr().port_addr(Port::handler::<String>());
6492
6493 let port_ref = handle.bind();
6494
6495 assert_eq!(port_ref.port_addr(), &ephemeral_port);
6496 assert_ne!(port_ref.port_addr(), &handler_port);
6497 }
6498
6499 #[test]
6500 fn test_bind_handler_port_handle_twice_is_idempotent() {
6501 let mbox = Mailbox::new(test_actor_id("0", "test"));
6502 let default_port = mbox.actor_addr().port_addr(Port::handler::<String>());
6503 let handle = mbox.open_handler_enqueue_port(|_, _message: String| Ok(()));
6504 assert_matches!(handle.inner.bind_target, PortBindTarget::Handler);
6505
6506 let first = handle.bind();
6507 let second = handle.bind();
6508
6509 assert_eq!(first.port_addr(), &default_port);
6510 assert_eq!(second.port_addr(), first.port_addr());
6511 assert_matches!(handle.location(), PortLocation::Bound(port) if port == default_port);
6512 }
6513
6514 #[test]
6515 fn test_bind_handler_port_helper_returns_handler_bound_handle() {
6516 let mbox = Mailbox::new(test_actor_id("0", "test"));
6517 let default_port = mbox.actor_addr().port_addr(Port::handler::<String>());
6518 let (handle, _rx) = mbox.bind_handler_port::<String>();
6519 assert_matches!(handle.inner.bind_target, PortBindTarget::Handler);
6520
6521 let port_ref = handle.bind();
6522
6523 assert_eq!(port_ref.port_addr(), &default_port);
6524 assert_matches!(handle.location(), PortLocation::Bound(port) if port == default_port);
6525 }
6526
6527 #[test]
6528 #[should_panic(expected = "already bound")]
6529 fn test_bind_port_handle_to_handler_port_when_already_bound() {
6530 let mbox = Mailbox::new(test_actor_id("0", "test"));
6531 let (handle, _rx) = mbox.open_port::<String>();
6532 handle.bind();
6534 assert_matches!(handle.location(), PortLocation::Bound(port) if port.index() == handle.inner.bind_target.ephemeral_index());
6535 handle.bind_handler_port();
6537 }
6538
6539 #[tokio::test]
6540 async fn test_mailbox_post_fails_when_actor_stopped() {
6541 let actor_id = test_actor_id("0", "stopped_actor");
6542
6543 let mailbox = Mailbox::new(actor_id.clone());
6544
6545 mailbox.close(ActorStatus::Stopped("test stop".to_string()));
6546
6547 let (user_port, _user_rx) = mailbox.open_port::<u64>();
6548
6549 let (return_handle, mut return_rx) = undeliverable::new_undeliverable_port();
6552
6553 let envelope = MessageEnvelope::serialize(
6554 actor_id.clone(),
6555 user_port.bind().port_addr().clone(),
6556 &42u64,
6557 Flattrs::new(),
6558 )
6559 .expect("serialize");
6560
6561 mailbox.post(envelope, return_handle);
6562
6563 let undelivered = tokio::time::timeout(Duration::from_secs(1), return_rx.recv())
6564 .await
6565 .expect("timed out waiting for undeliverable")
6566 .expect("return port closed")
6567 .into_message()
6568 .expect("expected returned envelope");
6569
6570 let err = undelivered.error_msg().expect("expected error");
6571 assert!(
6572 err.contains("actor stopped"),
6573 "error should indicate actor stopped: {}",
6574 err
6575 );
6576 let root_failure = undelivered
6577 .root_delivery_failure()
6578 .expect("expected root delivery failure");
6579 let DeliveryFailureKind::InvalidReference(invalid_reference) = &root_failure.kind else {
6580 panic!("expected invalid reference, got {root_failure}");
6581 };
6582 assert_eq!(
6583 invalid_reference.reason,
6584 InvalidReferenceReason::ActorStopped
6585 );
6586 }
6587
6588 #[tokio::test]
6589 async fn test_mailbox_post_fails_when_actor_failed() {
6590 use crate::actor::ActorErrorKind;
6591
6592 let actor_id = test_actor_id("0", "failed_actor");
6593
6594 let mailbox = Mailbox::new(actor_id.clone());
6595
6596 let (user_port, _user_rx) = mailbox.open_port::<u64>();
6597
6598 mailbox.close(ActorStatus::Failed(ActorErrorKind::Generic(
6599 "test failure".to_string(),
6600 )));
6601
6602 let (return_handle, mut return_rx) = undeliverable::new_undeliverable_port();
6605
6606 let envelope = MessageEnvelope::serialize(
6607 actor_id.clone(),
6608 user_port.bind().port_addr().clone(),
6609 &42u64,
6610 Flattrs::new(),
6611 )
6612 .expect("serialize");
6613
6614 mailbox.post(envelope, return_handle);
6615
6616 let undelivered = tokio::time::timeout(Duration::from_secs(1), return_rx.recv())
6617 .await
6618 .expect("timed out waiting for undeliverable")
6619 .expect("return port closed")
6620 .into_message()
6621 .expect("expected returned envelope");
6622
6623 let err = undelivered.error_msg().expect("expected error");
6624 assert!(
6625 err.contains("actor failed"),
6626 "error should indicate actor failed: {}",
6627 err
6628 );
6629 let root_failure = undelivered
6630 .root_delivery_failure()
6631 .expect("expected root delivery failure");
6632 let DeliveryFailureKind::InvalidReference(invalid_reference) = &root_failure.kind else {
6633 panic!("expected invalid reference, got {root_failure}");
6634 };
6635 assert_eq!(
6636 invalid_reference.reason,
6637 InvalidReferenceReason::ActorFailed
6638 );
6639 }
6640
6641 #[tokio::test]
6642 async fn test_port_handle_send_fails_when_actor_stopped() {
6643 let actor_id = test_actor_id("0", "stopped_actor");
6644
6645 let mailbox = Mailbox::new(actor_id.clone());
6646
6647 let (port_handle, _rx) = mailbox.open_port::<u64>();
6648 let proc = Proc::isolated();
6649 let client = proc.client("client");
6650
6651 mailbox.close(ActorStatus::Stopped("test stop".to_string()));
6652
6653 let err = port_handle.try_post(&client, 42u64).unwrap_err();
6654 assert_matches!(
6655 err.kind(),
6656 MailboxSenderErrorKind::Mailbox(mailbox_err)
6657 if matches!(mailbox_err.kind(), MailboxErrorKind::OwnerTerminated(ActorStatus::Stopped(reason)) if reason == "test stop")
6658 );
6659 }
6660
6661 #[tokio::test]
6662 async fn test_port_handle_send_fails_when_actor_failed() {
6663 use crate::actor::ActorErrorKind;
6664
6665 let actor_id = test_actor_id("0", "failed_actor");
6666
6667 let mailbox = Mailbox::new(actor_id.clone());
6668
6669 let (port_handle, _rx) = mailbox.open_port::<u64>();
6670 let proc = Proc::isolated();
6671 let client = proc.client("client");
6672
6673 mailbox.close(ActorStatus::Failed(ActorErrorKind::Generic(
6674 "test failure".to_string(),
6675 )));
6676
6677 let err = port_handle.try_post(&client, 42u64).unwrap_err();
6678 assert_matches!(
6679 err.kind(),
6680 MailboxSenderErrorKind::Mailbox(mailbox_err)
6681 if matches!(mailbox_err.kind(), MailboxErrorKind::OwnerTerminated(ActorStatus::Failed(ActorErrorKind::Generic(msg))) if msg == "test failure")
6682 );
6683 }
6684
6685 #[async_timed_test(timeout_secs = 30)]
6686 async fn test_open_reduce_port() {
6687 let proc = Proc::isolated();
6688 let client = proc.client("client");
6689
6690 let (port_handle, receiver) = client.mailbox().open_reduce_port(accum::sum::<u64>());
6692
6693 let port_ref = port_handle.bind();
6695 assert!(port_ref.reducer_spec().is_some());
6696
6697 port_ref.post(&client, 42);
6699
6700 let result = receiver.recv().await.unwrap();
6702 assert_eq!(result, 42);
6703 }
6704
6705 #[async_timed_test(timeout_secs = 30)]
6706 async fn test_open_reduce_port_reducer_spec_preserved() {
6707 let proc = Proc::isolated();
6708 let client = proc.client("client");
6709
6710 let (sum_handle, _) = client.mailbox().open_reduce_port(accum::sum::<u64>());
6712 let sum_ref = sum_handle.bind();
6713 let sum_typehash = sum_ref.reducer_spec().as_ref().unwrap().typehash;
6714
6715 let (max_handle, _) = client
6716 .mailbox()
6717 .open_reduce_port(accum::join_semilattice::<accum::Max<u64>>());
6718 let max_ref = max_handle.bind();
6719 let max_typehash = max_ref.reducer_spec().as_ref().unwrap().typehash;
6720
6721 assert_ne!(sum_typehash, max_typehash);
6723 }
6724
6725 #[tokio::test]
6730 async fn test_flush_over_unix_channel() {
6731 let mbox = Mailbox::new(test_actor_id("0", "actor0"));
6732
6733 let (addr, rx) = channel::serve(ChannelAddr::any(ChannelTransport::Unix)).unwrap();
6735 let serve_handle = mbox.clone().serve(rx);
6736
6737 let client = MailboxClient::dial(addr).unwrap();
6739
6740 let (port, mut receiver) = mbox.open_port::<u64>();
6742 let port = port.bind();
6743
6744 for i in 0..10u64 {
6746 client
6747 .serialize_and_send(&port, i, monitored_return_handle())
6748 .unwrap();
6749 }
6750
6751 client.flush().await.unwrap();
6754
6755 for i in 0..10u64 {
6757 let msg = receiver
6758 .try_recv()
6759 .expect("message should be available after flush")
6760 .expect("receiver should not be empty after flush");
6761 assert_eq!(msg, i);
6762 }
6763
6764 serve_handle.stop("test done");
6765 serve_handle.await.unwrap().unwrap();
6766 }
6767
6768 #[test]
6769 fn test_drain_waits_for_active_handler_enqueue() {
6770 let mailbox = Mailbox::new(test_actor_id("drain", "actor"));
6771 let (entered_tx, entered_rx) = std::sync::mpsc::channel();
6772 let release = Arc::new((std::sync::Mutex::new(false), std::sync::Condvar::new()));
6773 let delivered = Arc::new(AtomicUsize::new(0));
6774
6775 let port = mailbox.open_handler_enqueue_port({
6776 let release = Arc::clone(&release);
6777 let delivered = Arc::clone(&delivered);
6778 move |_headers, _message: u64| {
6779 entered_tx.send(()).unwrap();
6780 let (lock, cvar) = &*release;
6781 let mut released = lock.lock().unwrap();
6782 while !*released {
6783 released = cvar.wait(released).unwrap();
6784 }
6785 delivered.fetch_add(1, Ordering::SeqCst);
6786 Ok(())
6787 }
6788 });
6789
6790 let sender = port.inner.sender.clone();
6791 let sender_thread = std::thread::spawn(move || sender.send(Flattrs::new(), 1u64).unwrap());
6792 entered_rx.recv_timeout(Duration::from_secs(1)).unwrap();
6793
6794 let (drained_tx, drained_rx) = std::sync::mpsc::channel();
6795 let drain_thread = std::thread::spawn({
6796 let mailbox = mailbox.clone();
6797 move || {
6798 mailbox.drain();
6799 drained_tx.send(()).unwrap();
6800 }
6801 });
6802
6803 let deadline = std::time::Instant::now() + Duration::from_secs(1);
6804 while mailbox.inner.handler_ingress.state.load(Ordering::Acquire) & HANDLER_INGRESS_DRAINING
6805 == 0
6806 {
6807 assert!(std::time::Instant::now() < deadline, "drain did not start");
6808 std::thread::yield_now();
6809 }
6810 assert_matches!(
6811 drained_rx.try_recv(),
6812 Err(std::sync::mpsc::TryRecvError::Empty)
6813 );
6814
6815 let (lock, cvar) = &*release;
6816 *lock.lock().unwrap() = true;
6817 cvar.notify_all();
6818
6819 sender_thread.join().unwrap();
6820 drained_rx.recv_timeout(Duration::from_secs(1)).unwrap();
6821 drain_thread.join().unwrap();
6822 assert_eq!(delivered.load(Ordering::SeqCst), 1);
6823
6824 let err = port.inner.sender.send(Flattrs::new(), 2u64).unwrap_err();
6825 assert!(err.is::<HandlerPortClosedError>());
6826 }
6827
6828 fn drive_abandonment_log(payload_sentinel: &str) -> (crate::ActorAddr, crate::PortAddr) {
6834 use hyperactor_config::declare_attrs;
6835
6836 declare_attrs! {
6837 attr UM_TEST_HEADER: u64;
6840 }
6841
6842 let sender = test_actor_id("um_proc", "um_sender");
6843 let dest = test_port_id("um_dest_proc", "um_dest", 42);
6844
6845 let mut headers = Flattrs::new();
6846 headers.set(UM_TEST_HEADER, 0xC0FFEEu64);
6847
6848 let envelope = MessageEnvelope::new(
6849 sender.clone(),
6850 dest.clone(),
6851 wirevalue::Any::serialize(&payload_sentinel.to_string()).unwrap(),
6852 headers,
6853 );
6854
6855 let (return_handle, _rx) = crate::mailbox::undeliverable::new_undeliverable_port();
6856 UndeliverableMailboxSender.post_unchecked(envelope, return_handle);
6857 (sender, dest)
6858 }
6859
6860 #[tracing_test::traced_test]
6864 #[test]
6865 fn test_um1_bounded_fields() {
6866 let payload_sentinel = "um1_payload_sentinel_5b7a9c3d";
6867 let (_sender, _dest) = drive_abandonment_log(payload_sentinel);
6868
6869 let buf = tracing_test::internal::global_buf().lock().unwrap();
6870 let logs = std::str::from_utf8(&buf).expect("logs are utf-8");
6871
6872 assert!(
6874 logs.contains("message_type="),
6875 "UM-1: expected message_type field, got:\n{logs}"
6876 );
6877 assert!(
6878 logs.contains("data_len="),
6879 "UM-1: expected data_len field, got:\n{logs}"
6880 );
6881 assert!(
6884 !logs.contains(payload_sentinel),
6885 "UM-1: payload body leaked into the log:\n{logs}"
6886 );
6887 assert!(
6891 !logs.contains(" headers="),
6892 "UM-1: unbounded headers field leaked into the log:\n{logs}"
6893 );
6894 }
6895
6896 #[tracing_test::traced_test]
6900 #[test]
6901 fn test_um2_compat_fields_preserved() {
6902 let (sender, _dest) = drive_abandonment_log("um2_payload");
6903
6904 let buf = tracing_test::internal::global_buf().lock().unwrap();
6905 let logs = std::str::from_utf8(&buf).expect("logs are utf-8");
6906
6907 let actor_name = sender.log_name();
6910 let actor_id = sender.to_string();
6911 assert!(
6912 logs.contains("actor_name=") && logs.contains(actor_name),
6913 "UM-2: expected actor_name={actor_name} on the log, got:\n{logs}"
6914 );
6915 assert!(
6916 logs.contains("actor_id=") && logs.contains(&actor_id),
6917 "UM-2: expected actor_id={actor_id} on the log, got:\n{logs}"
6918 );
6919 }
6920
6921 #[tracing_test::traced_test]
6923 #[test]
6924 fn test_um3_destination_format() {
6925 let (_sender, dest) = drive_abandonment_log("um3_payload");
6926
6927 let buf = tracing_test::internal::global_buf().lock().unwrap();
6928 let logs = std::str::from_utf8(&buf).expect("logs are utf-8");
6929
6930 assert!(
6931 logs.contains(&format!("message not delivered to {}", dest)),
6932 "UM-3: expected destination-naming format string, got:\n{logs}"
6933 );
6934 }
6935
6936 #[tracing_test::traced_test]
6940 #[test]
6941 fn test_um3b_operation_format_with_operation_context() {
6942 let sender = test_actor_id("um_proc", "um_sender");
6943 let dest = test_port_id("um_dest_proc", "um_dest", 42);
6944
6945 let mut headers = Flattrs::new();
6946 headers.set(
6947 headers::OPERATION_ENDPOINT,
6948 "training.buffer.sample()".to_string(),
6949 );
6950 headers.set(headers::OPERATION_ADVERB, "call_one".to_string());
6951
6952 let envelope = MessageEnvelope::new(
6953 sender,
6954 dest,
6955 wirevalue::Any::serialize(&"um3b_payload".to_string()).unwrap(),
6956 headers,
6957 );
6958
6959 let (return_handle, _rx) = crate::mailbox::undeliverable::new_undeliverable_port();
6960 UndeliverableMailboxSender.post_unchecked(envelope, return_handle);
6961
6962 let buf = tracing_test::internal::global_buf().lock().unwrap();
6963 let logs = std::str::from_utf8(&buf).expect("logs are utf-8");
6964
6965 assert!(
6966 logs.contains("abandoned message for training.buffer.sample()"),
6967 "UM-3b: expected operation-naming format string, got:\n{logs}"
6968 );
6969 assert!(
6970 logs.contains("endpoint=") && logs.contains("training.buffer.sample()"),
6971 "UM-3b: expected endpoint field with the caller's operation, got:\n{logs}"
6972 );
6973 assert!(
6974 logs.contains("adverb=") && logs.contains("call_one"),
6975 "UM-3b: expected adverb field, got:\n{logs}"
6976 );
6977 assert!(
6980 !logs.contains("message not delivered to"),
6981 "UM-3b: unexpected destination-naming format string:\n{logs}"
6982 );
6983 }
6984}