1use std::collections::HashMap;
25use std::fmt;
26use std::sync::Arc;
27use std::sync::OnceLock;
28use std::sync::RwLock;
29use std::sync::Weak;
30use std::sync::atomic::AtomicU64;
31use std::sync::atomic::Ordering as AtomicOrdering;
32use std::time::Duration;
33
34use async_trait::async_trait;
35use futures::StreamExt as _;
36use serde::Deserialize;
37use serde::Serialize;
38use tokio::sync::watch;
39use tokio::task::JoinSet;
40use tokio_util::sync::CancellationToken;
41
42use crate::Location;
43use crate::PortAddr;
44use crate::ProcAddr;
45use crate::ProcId;
46use crate::channel;
47use crate::channel::ChannelAddr;
48use crate::channel::ChannelError;
49use crate::channel::ChannelTransport;
50use crate::channel::Rx;
51use crate::channel::Tx;
52use crate::id::Uid;
53use crate::mailbox::BoxedMailboxSender;
54use crate::mailbox::DeliveryFailure;
55use crate::mailbox::DialMailboxRouter;
56use crate::mailbox::IntoBoxedMailboxSender as _;
57use crate::mailbox::MailboxClient;
58use crate::mailbox::MailboxSender as _;
59use crate::mailbox::MailboxServer as _;
60use crate::mailbox::MailboxServerError;
61use crate::mailbox::MailboxServerHandle;
62use crate::mailbox::MessageEnvelope;
63use crate::mailbox::PortHandle;
64use crate::mailbox::TransportFailure;
65use crate::mailbox::TransportFailureReason;
66use crate::mailbox::Undeliverable;
67use crate::mailbox::UndeliverableReason;
68use crate::mailbox::UnroutableMailboxSender;
69use crate::proc::Proc;
70use crate::proc::WeakProc;
71
72const ATTACH_CONTROL_LABEL: &str = "attach";
86
87const ATTACH_HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(30);
92
93#[derive(Debug, Clone, Serialize, Deserialize, typeuri::Named)]
98pub(crate) struct AttachRequest {
99 pub(crate) uid: Uid,
101}
102wirevalue::register_type!(AttachRequest);
103
104#[derive(Debug, Clone, Serialize, Deserialize, typeuri::Named)]
106pub(crate) enum AttachAck {
107 Accepted {
110 location: Location,
112 },
113 Rejected {
115 reason: String,
117 },
118}
119wirevalue::register_type!(AttachAck);
120
121#[derive(Debug, Serialize, Deserialize, typeuri::Named)]
124#[expect(
125 clippy::large_enum_variant,
126 reason = "wire-protocol enum; boxing Envelope would ripple through channel/networking destructure sites"
127)]
128pub(crate) enum AttachWire {
129 Ack(AttachAck),
131 Envelope(MessageEnvelope),
133}
134wirevalue::register_type!(AttachWire);
135
136pub(crate) struct AttachRx(pub(crate) channel::duplex::DuplexRx<AttachWire>);
140
141#[async_trait]
142impl channel::Rx<MessageEnvelope> for AttachRx {
143 async fn recv(&mut self) -> Result<MessageEnvelope, ChannelError> {
144 match self.0.recv().await? {
145 AttachWire::Envelope(envelope) => Ok(envelope),
146 AttachWire::Ack(_) => Err(ChannelError::Other(anyhow::anyhow!(
147 "unexpected attach ack after handshake"
148 ))),
149 }
150 }
151
152 fn addr(&self) -> ChannelAddr {
153 self.0.addr()
154 }
155
156 async fn join(self) {
157 self.0.join().await
158 }
159}
160
161#[derive(Clone)]
166pub(crate) struct AttachTx(pub(crate) channel::duplex::DuplexTx<AttachWire>);
167
168#[async_trait]
169impl channel::Tx<MessageEnvelope> for AttachTx {
170 fn do_post(
171 &self,
172 envelope: MessageEnvelope,
173 completion: channel::CompletionSink<MessageEnvelope>,
174 ) {
175 let completion = completion.contramap_rejected(
176 |channel::SendError {
177 error,
178 message,
179 reason,
180 }| {
181 let AttachWire::Envelope(envelope) = message else {
182 return None;
183 };
184 Some(channel::SendError {
185 error,
186 message: envelope,
187 reason,
188 })
189 },
190 );
191 self.0.do_post(AttachWire::Envelope(envelope), completion);
192 }
193
194 fn addr(&self) -> ChannelAddr {
195 self.0.addr()
196 }
197
198 fn status(&self) -> &watch::Receiver<channel::TxStatus> {
199 self.0.status()
200 }
201}
202
203struct PreboundAcceptServer {
204 inner: channel::duplex::DuplexServer<MessageEnvelope, AttachWire>,
205}
206
207impl PreboundAcceptServer {
208 fn duplex(
209 addr: ChannelAddr,
210 listener: Option<std::net::TcpListener>,
211 ) -> Result<Self, channel::ServerError> {
212 let inner = channel::duplex::serve::<MessageEnvelope, AttachWire>(addr, listener)?;
213 Ok(Self { inner })
214 }
215
216 fn addr(&self) -> &ChannelAddr {
217 self.inner.addr()
218 }
219}
220
221fn build_control_envelope<T>(payload: &T) -> anyhow::Result<MessageEnvelope>
229where
230 T: serde::Serialize + typeuri::Named,
231{
232 let signal_actor_id = crate::ActorAddr::root(
233 ProcAddr::singleton(
234 ChannelAddr::any(channel::ChannelTransport::Local),
235 ATTACH_CONTROL_LABEL,
236 ),
237 crate::id::Label::strip(ATTACH_CONTROL_LABEL),
238 );
239 let signal_port = signal_actor_id.port_addr(crate::port::Port::from(0u64));
240 let mut envelope =
241 MessageEnvelope::serialize(signal_actor_id, signal_port, payload, Default::default())?;
242 envelope.set_return_undeliverable(false);
243 Ok(envelope)
244}
245
246#[derive(Clone)]
248pub struct Gateway {
249 inner: Arc<GatewayState>,
250}
251
252#[cfg(not(hyperactor_verify_auto_traits))]
255mod _send_sync_shortcut {
256 use super::*;
257
258 unsafe impl Send for GatewayState {}
260 unsafe impl Sync for GatewayState {}
262}
263
264const _: fn() = || {
265 fn assert<T: Send + Sync + ?Sized>() {}
266 assert::<GatewayState>();
267};
268
269#[must_use = "dropping the AttachedProcGuard immediately detaches the proc"]
281pub struct AttachedProcGuard {
282 gateway: Weak<GatewayState>,
283 proc_id: ProcId,
284 weak: WeakProc,
285}
286
287impl fmt::Debug for AttachedProcGuard {
288 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
289 f.debug_struct("AttachedProcGuard")
290 .field("proc_id", &self.proc_id)
291 .finish()
292 }
293}
294
295impl Drop for AttachedProcGuard {
296 fn drop(&mut self) {
297 let Some(state) = self.gateway.upgrade() else {
298 return;
299 };
300 let mut procs = state.procs.write().unwrap();
301 if procs
302 .get(&self.proc_id)
303 .is_some_and(|weak| weak.ptr_eq(&self.weak))
304 {
305 procs.remove(&self.proc_id);
306 }
307 }
308}
309
310#[must_use = "dropping the PeerAttachGuard immediately removes the peer entry"]
313pub struct PeerAttachGuard {
314 gateway: Weak<GatewayState>,
315 uid: Uid,
316}
317
318impl fmt::Debug for PeerAttachGuard {
319 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
320 f.debug_struct("PeerAttachGuard")
321 .field("uid", &self.uid)
322 .finish()
323 }
324}
325
326impl Drop for PeerAttachGuard {
327 fn drop(&mut self) {
328 let Some(state) = self.gateway.upgrade() else {
329 return;
330 };
331 state.peers.write().unwrap().remove(&self.uid);
332 }
333}
334
335#[derive(Debug, thiserror::Error)]
338#[error("gateway already has a via peer with uid {uid}")]
339pub struct PeerAttachError {
340 pub uid: Uid,
342}
343
344struct RoutingState {
347 fallback_location: Location,
349
350 base_forwarder: BoxedMailboxSender,
352
353 active_serves: Vec<ActiveServe>,
355}
356
357struct Routing {
361 state: RoutingState,
362
363 default_location: Location,
366
367 local_delivery_locations: Arc<[Location]>,
369
370 forwarder: BoxedMailboxSender,
375}
376
377impl Routing {
378 fn new(fallback_location: Location, base_forwarder: BoxedMailboxSender) -> Self {
379 Self {
380 state: RoutingState {
381 fallback_location: fallback_location.clone(),
382 base_forwarder: base_forwarder.clone(),
383 active_serves: Vec::new(),
384 },
385 default_location: fallback_location.clone(),
386 local_delivery_locations: vec![fallback_location].into(),
387 forwarder: base_forwarder,
388 }
389 }
390
391 fn mutate(&mut self, update: impl FnOnce(&mut RoutingState)) {
392 update(&mut self.state);
393 self.recompute();
394 }
395
396 fn recompute(&mut self) {
397 self.default_location = self
398 .state
399 .active_serves
400 .last()
401 .map(|serve| serve.location.clone())
402 .unwrap_or_else(|| self.state.fallback_location.clone());
403 self.local_delivery_locations = if self.state.active_serves.is_empty() {
404 vec![self.default_location.clone()].into()
405 } else {
406 self.state
407 .active_serves
408 .iter()
409 .map(|serve| serve.location.clone())
410 .collect::<Vec<_>>()
411 .into()
412 };
413 self.forwarder = self
414 .state
415 .active_serves
416 .iter()
417 .rev()
418 .find_map(|serve| match &serve.kind {
419 ActiveServeKind::Server => None,
420 ActiveServeKind::Via { forwarder } => Some(forwarder.clone()),
421 })
422 .unwrap_or_else(|| self.state.base_forwarder.clone());
423 }
424}
425
426#[derive(Clone, Copy, Debug, Eq, PartialEq)]
427struct ServeId(u64);
428
429struct ActiveServe {
430 id: ServeId,
431 location: Location,
432 kind: ActiveServeKind,
433}
434
435enum ActiveServeKind {
436 Server,
437 Via { forwarder: BoxedMailboxSender },
438}
439
440struct GatewayState {
441 uid: Uid,
447
448 routing: RwLock<Routing>,
453
454 procs: RwLock<HashMap<ProcId, WeakProc>>,
457
458 next_serve_id: AtomicU64,
460
461 peers: RwLock<HashMap<Uid, BoxedMailboxSender>>,
467}
468
469impl Gateway {
470 pub fn new() -> Self {
472 Self::configured(
473 channel::reserve_local_addr().into(),
474 DialMailboxRouter::new().into_boxed(),
475 )
476 }
477
478 pub fn isolated() -> Self {
480 Self::configured(
481 channel::reserve_local_addr().into(),
482 BoxedMailboxSender::new(UnroutableMailboxSender),
483 )
484 }
485
486 pub fn global() -> &'static Self {
488 static GLOBAL_GATEWAY: OnceLock<Gateway> = OnceLock::new();
489 GLOBAL_GATEWAY.get_or_init(Self::new)
490 }
491
492 pub fn current() -> Self {
496 Proc::current().gateway()
497 }
498
499 pub(crate) fn configured(default_location: Location, forwarder: BoxedMailboxSender) -> Self {
504 Self {
505 inner: Arc::new(GatewayState {
506 uid: Uid::anonymous(),
507 routing: RwLock::new(Routing::new(default_location, forwarder.clone())),
508 procs: RwLock::new(HashMap::new()),
509 next_serve_id: AtomicU64::new(1),
510 peers: RwLock::new(HashMap::new()),
511 }),
512 }
513 }
514
515 pub fn uid(&self) -> &Uid {
519 &self.inner.uid
520 }
521
522 pub fn default_location(&self) -> Location {
524 self.inner.routing.read().unwrap().default_location.clone()
525 }
526
527 pub fn forwarder(&self) -> BoxedMailboxSender {
531 self.inner.routing.read().unwrap().forwarder.clone()
532 }
533
534 pub fn set_default_location(&self, location: Location) {
540 let mut routing = self.inner.routing.write().unwrap();
541 routing.mutate(|routing| routing.fallback_location = location);
542 }
543
544 pub(crate) fn attach_proc(&self, proc: &Proc) -> AttachedProcGuard {
568 let proc_id = proc.proc_id().clone();
569 let weak = proc.downgrade();
570 let mut procs = self.inner.procs.write().unwrap();
571 let duplicate_live_proc = procs.get(&proc_id).and_then(WeakProc::upgrade).is_some();
572 if duplicate_live_proc {
573 drop(procs);
574 panic!("gateway already has a proc attached with id {}", proc_id)
575 }
576 procs.insert(proc_id.clone(), weak.clone());
577 AttachedProcGuard {
578 gateway: Arc::downgrade(&self.inner),
579 proc_id,
580 weak,
581 }
582 }
583
584 pub fn attach_peer(
597 &self,
598 uid: Uid,
599 sender: BoxedMailboxSender,
600 ) -> Result<PeerAttachGuard, PeerAttachError> {
601 let mut via = self.inner.peers.write().unwrap();
602 if via.contains_key(&uid) {
603 return Err(PeerAttachError { uid });
604 }
605 via.insert(uid.clone(), sender);
606 Ok(PeerAttachGuard {
607 gateway: Arc::downgrade(&self.inner),
608 uid,
609 })
610 }
611
612 pub(crate) fn serve_rx(
613 &self,
614 rx: impl channel::Rx<MessageEnvelope> + Send + 'static,
615 ) -> MailboxServerHandle {
616 Arc::downgrade(&self.inner).serve(rx)
617 }
618
619 pub fn serve(&self, addr: ChannelAddr) -> Result<GatewayServeHandle, ChannelError> {
633 let (serve_id, handle) = self.serve_inner(addr)?;
634 Ok(GatewayServeHandle {
635 gateway: self.clone(),
636 handle,
637 stopped: false,
638 kind: HandleKind::Serve {
639 serve_id: Some(serve_id),
640 },
641 })
642 }
643
644 pub fn serve_duplex(&self, addr: ChannelAddr) -> Result<GatewayServeHandle, ChannelError> {
667 self.serve_duplex_with_listener(addr, None)
668 }
669
670 fn serve_duplex_with_listener(
671 &self,
672 addr: ChannelAddr,
673 listener: Option<std::net::TcpListener>,
674 ) -> Result<GatewayServeHandle, ChannelError> {
675 if !addr.transport().supports_duplex() {
676 return Err(ChannelError::Other(anyhow::anyhow!(
677 "serve_duplex requires a duplex-capable transport, but {addr} does not support duplex"
678 )));
679 }
680 if addr.transport().is_net() {
692 return self.serve_mux_with_listener(addr, listener);
693 }
694 let server = PreboundAcceptServer::duplex(addr, listener)
695 .map_err(|e| ChannelError::Other(anyhow::anyhow!("{e}")))?;
696 Ok(self.serve_duplex_with_server(server))
697 }
698
699 fn serve_duplex_with_server(&self, server: PreboundAcceptServer) -> GatewayServeHandle {
700 let bound_addr = server.addr().clone();
701 let location = Location::from(bound_addr.clone());
702 let cancel_token = CancellationToken::new();
708 let loop_token = cancel_token.clone();
709 let gateway = self.clone();
710 let inner = server.inner;
711 let serve_id = self.add_server(location);
712 let join_handle = tokio::spawn(async move {
713 duplex_accept_loop(inner, bound_addr, gateway, loop_token).await;
714 Ok::<(), MailboxServerError>(())
715 });
716 let (idle_stop_tx, _idle_stop_rx) = watch::channel(false);
720 let handle = MailboxServerHandle::from_parts(join_handle, idle_stop_tx);
721 GatewayServeHandle {
722 gateway: self.clone(),
723 handle,
724 stopped: false,
725 kind: HandleKind::ServeDuplex {
726 serve_id: Some(serve_id),
727 cancel_token,
728 },
729 }
730 }
731
732 pub fn serve_mux_with_listener(
750 &self,
751 addr: ChannelAddr,
752 listener: Option<std::net::TcpListener>,
753 ) -> Result<GatewayServeHandle, ChannelError> {
754 let mux =
755 channel::serve_mux::<MessageEnvelope, MessageEnvelope, AttachWire>(addr, listener)?;
756 let bound_addr = mux.addr().clone();
757 let simplex_gateway = self.clone();
758 let duplex_gateway = self.clone();
759 let duplex_addr = bound_addr.clone();
760 let raw = mux.serve(
761 move |rx| simplex_gateway.serve(rx),
762 move |duplex_server, mut stop_rx| async move {
763 let cancel_token = CancellationToken::new();
770 let loop_token = cancel_token.clone();
771 tokio::spawn(async move {
772 if stop_rx.wait_for(|stopped| *stopped).await.is_ok() {
773 cancel_token.cancel();
774 }
775 });
776 duplex_accept_loop(duplex_server, duplex_addr, duplex_gateway, loop_token).await;
777 },
778 );
779 Ok(GatewayServeHandle::from_mailbox_handle(
780 self.clone(),
781 bound_addr,
782 raw,
783 ))
784 }
785
786 pub fn serve_with_listener(
795 &self,
796 addr: ChannelAddr,
797 listener: Option<std::net::TcpListener>,
798 ) -> Result<GatewayServeHandle, ChannelError> {
799 if addr.transport().supports_duplex() {
800 self.serve_duplex_with_listener(addr, listener)
801 } else {
802 let addr = self.resolve_serve_addr(addr);
803 let (addr, rx) = channel::serve_with_listener(addr, listener)?;
804 let serve_id = self.add_server(Location::from(addr));
805 let raw = self.clone().serve(rx);
806 Ok(GatewayServeHandle::from_simplex(
807 self.clone(),
808 raw,
809 Some(serve_id),
810 ))
811 }
812 }
813
814 pub async fn serve_via(&self, addr: ChannelAddr) -> anyhow::Result<GatewayServeHandle> {
832 let my_uid = self.inner.uid.clone();
833 let mut duplex_client = channel::duplex::dial::<MessageEnvelope, AttachWire>(addr)?;
834 let duplex_tx = duplex_client.tx();
835 let mut duplex_rx = duplex_client
836 .take_rx()
837 .expect("dial returns a fresh DuplexClient with rx present");
838
839 duplex_tx.post(build_control_envelope(&AttachRequest { uid: my_uid })?);
840
841 let location = match tokio::time::timeout(ATTACH_HANDSHAKE_TIMEOUT, duplex_rx.recv())
842 .await
843 .map_err(|_| {
844 anyhow::anyhow!(
845 "attach handshake timed out after {:?} waiting for AttachAck",
846 ATTACH_HANDSHAKE_TIMEOUT
847 )
848 })?? {
849 AttachWire::Ack(AttachAck::Accepted { location }) => location,
850 AttachWire::Ack(AttachAck::Rejected { reason }) => {
851 anyhow::bail!("attach rejected by peer: {reason}")
852 }
853 AttachWire::Envelope(_) => anyhow::bail!("expected attach ack as first message"),
854 };
855
856 let serve_id = self.add_via_server(location, MailboxClient::new(duplex_tx).into_boxed());
857 let serve_handle = self.serve_rx(AttachRx(duplex_rx));
858 Ok(GatewayServeHandle {
859 gateway: self.clone(),
860 handle: serve_handle,
861 stopped: false,
862 kind: HandleKind::ServeVia {
863 serve_id: Some(serve_id),
864 duplex_client: Some(duplex_client),
865 },
866 })
867 }
868
869 fn serve_inner(
870 &self,
871 addr: ChannelAddr,
872 ) -> Result<(ServeId, MailboxServerHandle), ChannelError> {
873 let addr = self.resolve_serve_addr(addr);
874 let (addr, rx) = channel::serve(addr)?;
875 let serve_id = self.add_server(Location::from(addr));
876 Ok((serve_id, self.serve_rx(rx)))
877 }
878
879 fn resolve_serve_addr(&self, addr: ChannelAddr) -> ChannelAddr {
880 if addr != ChannelAddr::any(ChannelTransport::Local) {
881 return addr;
882 }
883
884 let routing = self.inner.routing.read().unwrap();
888 let fallback_addr = routing.state.fallback_location.addr();
889 let fallback_is_active = routing.state.active_serves.iter().any(|serve| {
890 matches!(&serve.kind, ActiveServeKind::Server) && serve.location.addr() == fallback_addr
891 });
892 if matches!(fallback_addr, ChannelAddr::Local(_)) && !fallback_is_active {
893 return fallback_addr.clone();
894 }
895 addr
896 }
897
898 fn add_server(&self, location: Location) -> ServeId {
899 self.add_active_serve(location, ActiveServeKind::Server)
900 }
901
902 fn add_via_server(&self, location: Location, forwarder: BoxedMailboxSender) -> ServeId {
903 self.add_active_serve(location, ActiveServeKind::Via { forwarder })
904 }
905
906 fn add_active_serve(&self, location: Location, kind: ActiveServeKind) -> ServeId {
907 let id = ServeId(
908 self.inner
909 .next_serve_id
910 .fetch_add(1, AtomicOrdering::Relaxed),
911 );
912 let mut routing = self.inner.routing.write().unwrap();
913 routing.mutate(|routing| {
914 routing
915 .active_serves
916 .push(ActiveServe { id, location, kind });
917 });
918 id
919 }
920
921 fn remove_active_serve(&self, serve_id: ServeId) {
922 let mut routing = self.inner.routing.write().unwrap();
923 routing.mutate(|routing| {
924 if let Some(index) = routing
925 .active_serves
926 .iter()
927 .rposition(|active| active.id == serve_id)
928 {
929 routing.active_serves.remove(index);
930 }
931 });
932 }
933
934 fn local_delivery_locations(&self) -> Arc<[Location]> {
935 Arc::clone(&self.inner.routing.read().unwrap().local_delivery_locations)
936 }
937
938 fn envelope_with_next_hop_location(
939 envelope: MessageEnvelope,
940 location: Location,
941 ) -> MessageEnvelope {
942 let dest_id = envelope.next_hop().id().clone();
943 envelope.with_next_hop(PortAddr::new(dest_id, location))
944 }
945
946 fn return_no_route(
947 envelope: MessageEnvelope,
948 return_handle: PortHandle<Undeliverable<MessageEnvelope>>,
949 ) {
950 let target = envelope.dest().clone();
951 let failure = DeliveryFailure::new(UndeliverableReason::Transport(TransportFailure::new(
952 target,
953 TransportFailureReason::NoRoute,
954 )));
955 envelope.undeliverable(failure, return_handle);
956 }
957
958 pub(crate) async fn flush(&self) -> Result<(), anyhow::Error> {
975 let local_procs: Vec<_> = self
981 .inner
982 .procs
983 .read()
984 .unwrap()
985 .values()
986 .filter_map(WeakProc::upgrade)
987 .collect();
988 let concurrency = local_procs.len().max(1);
992 let proc_results: Vec<_> = futures::stream::iter(
993 local_procs
994 .into_iter()
995 .map(|proc| async move { proc.muxer().flush().await }),
996 )
997 .buffer_unordered(concurrency)
998 .collect()
999 .await;
1000 let forwarder = self.inner.routing.read().unwrap().forwarder.clone();
1001 let forwarder_result = forwarder.flush().await;
1002
1003 proc_results
1005 .into_iter()
1006 .chain(std::iter::once(forwarder_result))
1007 .collect()
1008 }
1009}
1010
1011impl fmt::Debug for Gateway {
1012 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1013 f.debug_struct("Gateway")
1014 .field("default_location", &self.default_location())
1015 .finish()
1016 }
1017}
1018
1019pub struct GatewayServeHandle {
1030 gateway: Gateway,
1031 handle: MailboxServerHandle,
1032 stopped: bool,
1033 kind: HandleKind,
1034}
1035
1036enum HandleKind {
1038 Serve { serve_id: Option<ServeId> },
1042 ServeDuplex {
1046 serve_id: Option<ServeId>,
1047 cancel_token: CancellationToken,
1048 },
1049 ServeVia {
1052 serve_id: Option<ServeId>,
1053 duplex_client: Option<channel::duplex::DuplexClient<MessageEnvelope, AttachWire>>,
1054 },
1055}
1056
1057impl fmt::Debug for GatewayServeHandle {
1058 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1059 let variant = match &self.kind {
1060 HandleKind::Serve { .. } => "Serve",
1061 HandleKind::ServeDuplex { .. } => "ServeDuplex",
1062 HandleKind::ServeVia { .. } => "ServeVia",
1063 };
1064 f.debug_struct("GatewayServeHandle")
1065 .field("kind", &variant)
1066 .finish()
1067 }
1068}
1069
1070impl GatewayServeHandle {
1071 fn from_simplex(
1073 gateway: Gateway,
1074 handle: MailboxServerHandle,
1075 serve_id: Option<ServeId>,
1076 ) -> Self {
1077 Self {
1078 gateway,
1079 handle,
1080 stopped: false,
1081 kind: HandleKind::Serve { serve_id },
1082 }
1083 }
1084
1085 pub fn from_mailbox_handle(
1094 gateway: Gateway,
1095 addr: ChannelAddr,
1096 handle: MailboxServerHandle,
1097 ) -> Self {
1098 let serve_id = gateway.add_server(Location::from(addr));
1099 Self::from_simplex(gateway, handle, Some(serve_id))
1100 }
1101
1102 pub fn stop(&mut self, reason: &str) {
1107 if self.stopped {
1108 return;
1109 }
1110 self.stopped = true;
1111 match &self.kind {
1115 HandleKind::ServeDuplex { cancel_token, .. } => {
1116 tracing::info!("stopping gateway duplex accept loop; reason: {reason}");
1117 cancel_token.cancel();
1118 }
1119 HandleKind::Serve { .. } | HandleKind::ServeVia { .. } => {
1120 self.handle.stop(reason);
1121 }
1122 }
1123 self.run_cleanup();
1124 }
1125
1126 pub async fn join(mut self) -> Result<(), MailboxServerError> {
1131 let inner_result = (&mut self.handle).await;
1132
1133 if let HandleKind::ServeVia { duplex_client, .. } = &mut self.kind
1135 && let Some(client) = duplex_client.take()
1136 {
1137 client.join().await;
1138 }
1139 self.run_cleanup();
1140 match inner_result {
1141 Ok(Ok(())) => Ok(()),
1142 Ok(Err(err)) => Err(err),
1143 Err(join_err) => Err(MailboxServerError::Channel(ChannelError::Other(
1144 anyhow::anyhow!("gateway serve task join error: {join_err}"),
1145 ))),
1146 }
1147 }
1148
1149 fn run_cleanup(&mut self) {
1150 match &mut self.kind {
1151 HandleKind::Serve { serve_id }
1152 | HandleKind::ServeDuplex { serve_id, .. }
1153 | HandleKind::ServeVia { serve_id, .. } => {
1154 if let Some(serve_id) = serve_id.take() {
1155 self.gateway.remove_active_serve(serve_id);
1156 }
1157 }
1158 }
1159 }
1160}
1161
1162impl Drop for GatewayServeHandle {
1163 fn drop(&mut self) {
1164 if self.stopped {
1172 return;
1173 }
1174 if matches!(&self.kind, HandleKind::ServeVia { .. }) {
1175 self.run_cleanup();
1176 }
1177 }
1178}
1179
1180async fn duplex_accept_loop(
1191 mut duplex_server: channel::duplex::DuplexServer<MessageEnvelope, AttachWire>,
1192 bound_addr: ChannelAddr,
1193 gateway: Gateway,
1194 cancel_token: CancellationToken,
1195) {
1196 let mut tasks: JoinSet<()> = JoinSet::new();
1197 loop {
1198 let accept = tokio::select! {
1199 result = duplex_server.accept() => result,
1200 () = cancel_token.cancelled() => break,
1201 };
1202 let (duplex_rx, duplex_tx) = match accept {
1203 Ok(pair) => pair,
1204 Err(e) => {
1205 tracing::info!(
1206 bound_addr = bound_addr.to_string(),
1207 error = %e,
1208 "duplex accept loop ended"
1209 );
1210 break;
1211 }
1212 };
1213
1214 tasks.spawn(serve_duplex_connection(
1215 gateway.clone(),
1216 duplex_rx,
1217 duplex_tx,
1218 cancel_token.clone(),
1219 ));
1220 }
1221
1222 while tasks.join_next().await.is_some() {}
1223 duplex_server.stop("duplex accept loop draining");
1232 duplex_server.join().await;
1233}
1234
1235async fn serve_duplex_connection(
1236 gateway: Gateway,
1237 mut duplex_rx: channel::duplex::DuplexRx<MessageEnvelope>,
1238 duplex_tx: channel::duplex::DuplexTx<AttachWire>,
1239 cancel_token: CancellationToken,
1240) {
1241 let first_msg = tokio::select! {
1242 result = duplex_rx.recv() => match result {
1243 Ok(msg) => msg,
1244 Err(e) => {
1245 tracing::info!(error = %e, "duplex connection closed before first message");
1246 return;
1247 }
1248 },
1249 () = cancel_token.cancelled() => return,
1250 };
1251
1252 if let Ok(attach_request) = first_msg.deserialized::<AttachRequest>() {
1253 let peer_uid = attach_request.uid;
1254 tracing::info!(
1255 uid = %peer_uid,
1256 "duplex accepted gateway-attach connection",
1257 );
1258 let sender = MailboxClient::new(AttachTx(duplex_tx.clone())).into_boxed();
1263 let attach_guard = match gateway.attach_peer(peer_uid.clone(), sender) {
1264 Ok(guard) => guard,
1265 Err(err) => {
1266 let reason = err.to_string();
1267 tracing::warn!(
1268 uid = %err.uid,
1269 error = %reason,
1270 "rejecting gateway-attach connection"
1271 );
1272 if let Err(send_error) = duplex_tx
1273 .send(AttachWire::Ack(AttachAck::Rejected { reason }))
1274 .await
1275 {
1276 tracing::warn!(
1277 uid = %err.uid,
1278 error = %send_error,
1279 "failed to send gateway-attach rejection"
1280 );
1281 }
1282 return;
1283 }
1284 };
1285
1286 let via_location = gateway.default_location().with_via(peer_uid);
1288 duplex_tx.post(AttachWire::Ack(AttachAck::Accepted {
1289 location: via_location,
1290 }));
1291
1292 let mut handle = gateway.serve_rx(duplex_rx);
1293 tokio::select! {
1294 _ = &mut handle => {}
1295 () = cancel_token.cancelled() => {
1296 handle.stop("gateway accept loop stopping");
1297 let _ = handle.await;
1298 }
1299 }
1300 drop(attach_guard);
1301 tracing::info!("gateway-attach connection closed");
1302 } else {
1303 let _keep_alive = duplex_tx;
1309 let rx = PrependRx {
1310 first: Some(first_msg),
1311 inner: duplex_rx,
1312 };
1313 let mut handle = gateway.serve_rx(rx);
1314 tokio::select! {
1315 _ = &mut handle => {}
1316 () = cancel_token.cancelled() => {
1317 handle.stop("gateway accept loop stopping");
1318 let _ = handle.await;
1319 }
1320 }
1321 }
1322}
1323
1324struct PrependRx<R> {
1329 first: Option<MessageEnvelope>,
1330 inner: R,
1331}
1332
1333#[async_trait]
1334impl<R: channel::Rx<MessageEnvelope> + Send> channel::Rx<MessageEnvelope> for PrependRx<R> {
1335 async fn recv(&mut self) -> Result<MessageEnvelope, ChannelError> {
1336 if let Some(msg) = self.first.take() {
1337 return Ok(msg);
1338 }
1339 self.inner.recv().await
1340 }
1341
1342 fn addr(&self) -> ChannelAddr {
1343 self.inner.addr()
1344 }
1345
1346 async fn join(self) {
1347 self.inner.join().await
1348 }
1349}
1350
1351#[async_trait]
1352impl crate::mailbox::MailboxSender for Weak<GatewayState> {
1353 fn post_unchecked(
1354 &self,
1355 envelope: MessageEnvelope,
1356 return_handle: PortHandle<Undeliverable<MessageEnvelope>>,
1357 ) {
1358 match Weak::upgrade(self).map(|inner| Gateway { inner }) {
1359 Some(gateway) => {
1360 gateway.route_envelope(envelope, return_handle);
1361 }
1362 None => {
1363 let target = envelope.dest().clone();
1364 let failure =
1365 DeliveryFailure::new(UndeliverableReason::Transport(TransportFailure::new(
1366 target,
1367 TransportFailureReason::LinkUnavailable("gateway is gone".to_string()),
1368 )));
1369 envelope.undeliverable(failure, return_handle)
1370 }
1371 }
1372 }
1373
1374 async fn flush(&self) -> Result<(), anyhow::Error> {
1375 match Weak::upgrade(self).map(|inner| Gateway { inner }) {
1376 Some(gateway) => Gateway::flush(&gateway).await,
1377 None => Ok(()),
1378 }
1379 }
1380}
1381
1382#[async_trait]
1383impl crate::mailbox::MailboxSender for Gateway {
1384 fn post_unchecked(
1385 &self,
1386 envelope: MessageEnvelope,
1387 return_handle: PortHandle<Undeliverable<MessageEnvelope>>,
1388 ) {
1389 self.route_envelope(envelope, return_handle);
1390 }
1391
1392 async fn flush(&self) -> Result<(), anyhow::Error> {
1393 Gateway::flush(self).await
1394 }
1395}
1396
1397impl Gateway {
1398 fn route_envelope(
1399 &self,
1400 envelope: MessageEnvelope,
1401 return_handle: PortHandle<Undeliverable<MessageEnvelope>>,
1402 ) {
1403 let mut envelope = envelope;
1419 loop {
1420 let dest_location = envelope.next_hop().location().clone();
1421 let Ok((via_uid, inner_location)) = dest_location.pop_via() else {
1422 break;
1423 };
1424
1425 if let Some(sender) = self.inner.peers.read().unwrap().get(&via_uid).cloned() {
1426 let envelope = Gateway::envelope_with_next_hop_location(envelope, inner_location);
1427 sender.post(envelope, return_handle);
1428 return;
1429 }
1430 if via_uid != self.inner.uid {
1431 let forwarder = self.inner.routing.read().unwrap().forwarder.clone();
1436 forwarder.post(envelope, return_handle);
1437 return;
1438 }
1439 envelope = Gateway::envelope_with_next_hop_location(envelope, inner_location);
1440 }
1441
1442 let dest_proc = envelope.dest().actor_addr().proc_addr();
1449 let local = self
1450 .inner
1451 .procs
1452 .read()
1453 .unwrap()
1454 .get(dest_proc.id())
1455 .and_then(WeakProc::upgrade);
1456 if let Some(proc) = local {
1457 let local_locations = self.local_delivery_locations();
1458 if proc.is_local_delivery_target_at(&dest_proc, &local_locations) {
1459 proc.muxer().post(envelope, return_handle);
1460 return;
1461 }
1462 }
1463
1464 if envelope.has_next_hop() {
1465 Gateway::return_no_route(envelope, return_handle);
1466 } else {
1467 let forwarder = self.inner.routing.read().unwrap().forwarder.clone();
1468 forwarder.post(envelope, return_handle)
1469 }
1470 }
1471}
1472
1473#[cfg(test)]
1474mod tests {
1475 use std::sync::Arc;
1476 use std::sync::atomic::AtomicUsize;
1477 use std::sync::atomic::Ordering;
1478 use std::time::Duration;
1479
1480 use async_trait::async_trait;
1481 use hyperactor_config::Flattrs;
1482 use timed_test::async_timed_test;
1483 use tokio::sync::mpsc;
1484 use tokio::time;
1485
1486 use super::*;
1487 use crate::Endpoint as _;
1488 use crate::Label;
1489 use crate::ProcAddr;
1490 use crate::mailbox::DeliveryFailureKind;
1491 use crate::mailbox::MailboxClient;
1492 use crate::mailbox::MailboxSender;
1493 use crate::mailbox::PortLocation;
1494 use crate::mailbox::monitored_return_handle;
1495 use crate::port::Port;
1496 use crate::proc::Proc;
1497 use crate::testing::ids::test_actor_id;
1498 use crate::testing::pingpong::PingPongActor;
1499 use crate::testing::pingpong::PingPongMessage;
1500
1501 #[derive(Clone)]
1502 struct RecordingSender(mpsc::UnboundedSender<MessageEnvelope>);
1503
1504 #[async_trait]
1505 impl MailboxSender for RecordingSender {
1506 fn post_unchecked(
1507 &self,
1508 envelope: MessageEnvelope,
1509 _return_handle: PortHandle<Undeliverable<MessageEnvelope>>,
1510 ) {
1511 self.0
1512 .send(envelope)
1513 .expect("recording sender should be open");
1514 }
1515 }
1516
1517 trait GatewayAttachExt {
1529 fn attach(&self, peer: &Gateway) -> AttachGuard;
1530 }
1531
1532 impl GatewayAttachExt for Gateway {
1533 fn attach(&self, peer: &Gateway) -> AttachGuard {
1534 let pre_self_default = self.default_location();
1538 let pre_peer_default = peer.default_location();
1539
1540 let self_serve = self
1543 .serve(ChannelAddr::any(ChannelTransport::Local))
1544 .expect("serve self on local channel");
1545 let peer_serve = peer
1546 .serve(ChannelAddr::any(ChannelTransport::Local))
1547 .expect("serve peer on local channel");
1548 let self_addr = self.default_location().addr().clone();
1549 let peer_addr = peer.default_location().addr().clone();
1550
1551 let self_via_guard = peer
1554 .attach_peer(
1555 self.inner.uid.clone(),
1556 MailboxClient::dial(self_addr)
1557 .expect("dial self")
1558 .into_boxed(),
1559 )
1560 .expect("peer has no via entry for this gateway's uid");
1561 let peer_via_guard = self
1562 .attach_peer(
1563 peer.inner.uid.clone(),
1564 MailboxClient::dial(peer_addr)
1565 .expect("dial peer")
1566 .into_boxed(),
1567 )
1568 .expect("self has no via entry for the peer gateway's uid");
1569
1570 self.inner.routing.write().unwrap().default_location =
1572 Location::Via(self.inner.uid.clone(), Box::new(pre_peer_default.clone()));
1573 peer.inner.routing.write().unwrap().default_location =
1574 Location::Via(peer.inner.uid.clone(), Box::new(pre_self_default.clone()));
1575
1576 AttachGuard {
1577 self_gateway: Arc::downgrade(&self.inner),
1578 peer_gateway: Arc::downgrade(&peer.inner),
1579 prev_self_default: Some(pre_self_default),
1580 prev_peer_default: Some(pre_peer_default),
1581 _self_via_guard: self_via_guard,
1582 _peer_via_guard: peer_via_guard,
1583 _self_serve: self_serve,
1584 _peer_serve: peer_serve,
1585 }
1586 }
1587 }
1588
1589 struct AttachGuard {
1594 self_gateway: Weak<GatewayState>,
1595 peer_gateway: Weak<GatewayState>,
1596 prev_self_default: Option<Location>,
1597 prev_peer_default: Option<Location>,
1598 _self_via_guard: PeerAttachGuard,
1599 _peer_via_guard: PeerAttachGuard,
1600 _self_serve: GatewayServeHandle,
1601 _peer_serve: GatewayServeHandle,
1602 }
1603
1604 impl Drop for AttachGuard {
1605 fn drop(&mut self) {
1606 if let Some(state) = self.self_gateway.upgrade()
1607 && let Some(loc) = self.prev_self_default.take()
1608 {
1609 state.routing.write().unwrap().default_location = loc;
1610 }
1611 if let Some(state) = self.peer_gateway.upgrade()
1612 && let Some(loc) = self.prev_peer_default.take()
1613 {
1614 state.routing.write().unwrap().default_location = loc;
1615 }
1616 }
1618 }
1619
1620 #[tokio::test]
1627 async fn test_gateway_post_demuxes_by_proc_id() {
1628 let (tx, mut forwarded_rx) = mpsc::unbounded_channel();
1629 let gateway = Gateway::configured(
1630 channel::reserve_local_addr().into(),
1631 BoxedMailboxSender::new(RecordingSender(tx)),
1632 );
1633
1634 let alpha = Proc::builder()
1635 .proc_id(ProcId::instance(Label::strip("alpha")))
1636 .shared_gateway(gateway.clone())
1637 .build()
1638 .unwrap();
1639 let beta = Proc::builder()
1640 .proc_id(ProcId::instance(Label::strip("beta")))
1641 .shared_gateway(gateway.clone())
1642 .build()
1643 .unwrap();
1644
1645 let alpha_client = alpha.client("client");
1646 let (alpha_port, mut alpha_rx) = alpha_client.bind_handler_port::<u64>();
1647 let PortLocation::Bound(alpha_dest) = alpha_port.location() else {
1648 panic!("alpha handler port must be bound");
1649 };
1650
1651 let beta_client = beta.client("client");
1652 let (beta_port, mut beta_rx) = beta_client.bind_handler_port::<u64>();
1653 let PortLocation::Bound(beta_dest) = beta_port.location() else {
1654 panic!("beta handler port must be bound");
1655 };
1656
1657 let sender = test_actor_id("test", "sender");
1658
1659 gateway.post(
1660 MessageEnvelope::serialize(sender.clone(), alpha_dest.clone(), &111u64, Flattrs::new())
1661 .unwrap(),
1662 monitored_return_handle(),
1663 );
1664 let received = time::timeout(Duration::from_secs(5), alpha_rx.recv())
1665 .await
1666 .expect("alpha_rx timed out")
1667 .expect("alpha_rx closed");
1668 assert_eq!(received, 111);
1669 assert!(matches!(
1670 forwarded_rx.try_recv(),
1671 Err(mpsc::error::TryRecvError::Empty)
1672 ));
1673
1674 gateway.post(
1675 MessageEnvelope::serialize(sender.clone(), beta_dest.clone(), &222u64, Flattrs::new())
1676 .unwrap(),
1677 monitored_return_handle(),
1678 );
1679 let received = time::timeout(Duration::from_secs(5), beta_rx.recv())
1680 .await
1681 .expect("beta_rx timed out")
1682 .expect("beta_rx closed");
1683 assert_eq!(received, 222);
1684 assert!(matches!(
1685 forwarded_rx.try_recv(),
1686 Err(mpsc::error::TryRecvError::Empty)
1687 ));
1688
1689 let stranger_proc = ProcAddr::instance(ChannelAddr::Local(9999), "stranger");
1690 let stranger_dest = stranger_proc
1691 .actor_addr("ghost")
1692 .port_addr(Port::from(0u64));
1693 gateway.post(
1694 MessageEnvelope::serialize(sender, stranger_dest.clone(), &333u64, Flattrs::new())
1695 .unwrap()
1696 .set_ttl(3),
1697 monitored_return_handle(),
1698 );
1699 let forwarded = time::timeout(Duration::from_secs(5), forwarded_rx.recv())
1700 .await
1701 .expect("forwarded_rx timed out")
1702 .expect("forwarded_rx closed");
1703 assert_eq!(forwarded.dest(), &stranger_dest);
1704 assert_eq!(forwarded.ttl(), 1);
1707 assert!(
1708 time::timeout(Duration::from_millis(50), alpha_rx.recv())
1709 .await
1710 .is_err(),
1711 "alpha_rx received a message after stranger post",
1712 );
1713 assert!(
1714 time::timeout(Duration::from_millis(50), beta_rx.recv())
1715 .await
1716 .is_err(),
1717 "beta_rx received a message after stranger post",
1718 );
1719 }
1720
1721 #[tokio::test]
1726 async fn test_gateway_foreign_via_forwards_not_local() {
1727 #[derive(Clone)]
1728 struct CountingSender(Arc<AtomicUsize>);
1729
1730 #[async_trait]
1731 impl MailboxSender for CountingSender {
1732 fn post_unchecked(
1733 &self,
1734 _envelope: MessageEnvelope,
1735 _return_handle: PortHandle<Undeliverable<MessageEnvelope>>,
1736 ) {
1737 self.0.fetch_add(1, Ordering::SeqCst);
1738 }
1739 }
1740
1741 let forwarded = Arc::new(AtomicUsize::new(0));
1742 let gateway = Gateway::configured(
1743 channel::reserve_local_addr().into(),
1744 BoxedMailboxSender::new(CountingSender(forwarded.clone())),
1745 );
1746
1747 let alpha = Proc::builder()
1749 .proc_id(ProcId::instance(Label::strip("alpha")))
1750 .shared_gateway(gateway.clone())
1751 .build()
1752 .unwrap();
1753
1754 let foreign_uid = Uid::Instance(0xfeed, Some(Label::strip("foreign")));
1757 assert_ne!(&foreign_uid, gateway.uid());
1758 let dest = ProcAddr::new(
1759 alpha.proc_id().clone(),
1760 Location::from(ChannelAddr::Local(7777)).with_via(foreign_uid),
1761 )
1762 .actor_addr("ghost")
1763 .port_addr(Port::from(0u64));
1764
1765 gateway.post(
1766 MessageEnvelope::serialize(
1767 test_actor_id("test", "sender"),
1768 dest,
1769 &7u64,
1770 Flattrs::new(),
1771 )
1772 .unwrap(),
1773 monitored_return_handle(),
1774 );
1775
1776 assert_eq!(
1778 forwarded.load(Ordering::SeqCst),
1779 1,
1780 "a foreign via must be forwarded, not delivered locally",
1781 );
1782 }
1783
1784 #[tokio::test]
1787 async fn test_gateway_self_via_routes_remaining_via_before_local_delivery() {
1788 #[derive(Clone)]
1789 struct CountingSender(Arc<AtomicUsize>);
1790
1791 #[async_trait]
1792 impl MailboxSender for CountingSender {
1793 fn post_unchecked(
1794 &self,
1795 _envelope: MessageEnvelope,
1796 _return_handle: PortHandle<Undeliverable<MessageEnvelope>>,
1797 ) {
1798 self.0.fetch_add(1, Ordering::SeqCst);
1799 }
1800 }
1801
1802 let forwarded = Arc::new(AtomicUsize::new(0));
1803 let gateway = Gateway::configured(
1804 channel::reserve_local_addr().into(),
1805 BoxedMailboxSender::new(CountingSender(forwarded.clone())),
1806 );
1807 let proc = Proc::builder()
1808 .proc_id(ProcId::instance(Label::strip("alpha")))
1809 .shared_gateway(gateway.clone())
1810 .build()
1811 .unwrap();
1812
1813 let foreign_uid = Uid::Instance(0xfeed, Some(Label::strip("foreign")));
1814 let dest = ProcAddr::new(
1815 proc.proc_id().clone(),
1816 proc.default_location()
1817 .with_via(foreign_uid)
1818 .with_via(gateway.uid().clone()),
1819 )
1820 .actor_addr("ghost")
1821 .port_addr(Port::from(0u64));
1822
1823 gateway.post(
1824 MessageEnvelope::serialize(
1825 test_actor_id("test", "sender"),
1826 dest,
1827 &7u64,
1828 Flattrs::new(),
1829 )
1830 .unwrap(),
1831 monitored_return_handle(),
1832 );
1833
1834 assert_eq!(
1835 forwarded.load(Ordering::SeqCst),
1836 1,
1837 "remaining via routes must not deliver locally after peeling this gateway's hop",
1838 );
1839 }
1840
1841 #[tokio::test]
1844 async fn test_gateway_self_via_peels_before_local_delivery() {
1845 #[derive(Clone)]
1846 struct CapturingSender(Arc<std::sync::Mutex<Option<(PortAddr, PortAddr)>>>);
1847
1848 #[async_trait]
1849 impl MailboxSender for CapturingSender {
1850 fn post_unchecked(
1851 &self,
1852 envelope: MessageEnvelope,
1853 _return_handle: PortHandle<Undeliverable<MessageEnvelope>>,
1854 ) {
1855 *self.0.lock().unwrap() =
1856 Some((envelope.dest().clone(), envelope.next_hop().clone()));
1857 }
1858 }
1859
1860 let gateway = Gateway::isolated();
1861 let proc = Proc::builder()
1862 .proc_id(ProcId::instance(Label::strip("alpha")))
1863 .shared_gateway(gateway.clone())
1864 .build()
1865 .unwrap();
1866
1867 let captured = Arc::new(std::sync::Mutex::new(None));
1868 let actor_addr = proc.proc_addr().actor_addr("capture");
1869 assert!(
1870 proc.muxer()
1871 .bind(actor_addr.id().clone(), CapturingSender(captured.clone()))
1872 );
1873
1874 let inner_dest = actor_addr.port_addr(Port::from(7u64));
1875 let via_dest = PortAddr::new(
1876 inner_dest.id().clone(),
1877 inner_dest
1878 .location()
1879 .clone()
1880 .with_via(gateway.uid().clone()),
1881 );
1882 gateway.post(
1883 MessageEnvelope::serialize(
1884 test_actor_id("test", "sender"),
1885 via_dest.clone(),
1886 &7u64,
1887 Flattrs::new(),
1888 )
1889 .unwrap(),
1890 monitored_return_handle(),
1891 );
1892
1893 assert_eq!(
1894 *captured.lock().unwrap(),
1895 Some((via_dest, inner_dest)),
1896 "delivery preserves the canonical destination and peels only the next hop",
1897 );
1898 }
1899
1900 #[tokio::test]
1905 async fn test_gateway_self_via_unknown_proc_is_undeliverable() {
1906 let gateway = Gateway::isolated();
1907
1908 let scratch = Proc::isolated();
1910 let scratch_client = scratch.client("return");
1911 let (return_handle, mut return_rx) =
1912 scratch_client.open_port::<Undeliverable<MessageEnvelope>>();
1913
1914 let peeled_dest = ProcAddr::new(
1917 ProcId::instance(Label::strip("stranger")),
1918 Location::from(ChannelAddr::Local(4321)),
1919 )
1920 .actor_addr("ghost")
1921 .port_addr(Port::from(0u64));
1922 let dest = PortAddr::new(
1923 peeled_dest.id().clone(),
1924 peeled_dest
1925 .location()
1926 .clone()
1927 .with_via(gateway.uid().clone()),
1928 );
1929 let envelope = MessageEnvelope::serialize(
1930 test_actor_id("test", "sender"),
1931 dest.clone(),
1932 &9u64,
1933 Flattrs::new(),
1934 )
1935 .unwrap()
1936 .set_ttl(3);
1937
1938 gateway.post(envelope, return_handle);
1939
1940 let Undeliverable::Returned(returned) =
1941 time::timeout(Duration::from_secs(5), return_rx.recv())
1942 .await
1943 .expect("return_rx timed out")
1944 .expect("return_rx closed")
1945 else {
1946 panic!("expected returned envelope");
1947 };
1948 assert_eq!(returned.dest(), &dest);
1949 assert_eq!(returned.next_hop(), &peeled_dest);
1950 assert!(
1951 returned
1952 .root_delivery_failure()
1953 .is_some_and(|failure| matches!(
1954 &failure.kind,
1955 DeliveryFailureKind::Undeliverable(UndeliverableReason::Transport(_))
1956 )),
1957 "expected NoRoute transport bounce, got {:?}",
1958 returned.delivery_failures(),
1959 );
1960 assert_eq!(returned.ttl(), 2);
1964 }
1965
1966 #[tokio::test]
1971 async fn test_ping_pong_across_shared_gateway() {
1972 let gateway = Gateway::isolated();
1973
1974 let alpha = Proc::builder()
1975 .proc_id(ProcId::instance(Label::strip("alpha")))
1976 .shared_gateway(gateway.clone())
1977 .build()
1978 .unwrap();
1979 let beta = Proc::builder()
1980 .proc_id(ProcId::instance(Label::strip("beta")))
1981 .shared_gateway(gateway.clone())
1982 .build()
1983 .unwrap();
1984
1985 let client = alpha.client("client");
1986 let (undeliverable_msg_tx, mut undeliverable_rx) =
1987 client.open_port::<Undeliverable<MessageEnvelope>>();
1988
1989 let ping_actor = PingPongActor::new(Some(undeliverable_msg_tx.bind()), None, None);
1990 let pong_actor = PingPongActor::new(Some(undeliverable_msg_tx.bind()), None, None);
1991 let ping_handle = alpha.spawn_with_label::<PingPongActor>("ping", ping_actor);
1992 let pong_handle = beta.spawn_with_label::<PingPongActor>("pong", pong_actor);
1993
1994 let (local_port, local_receiver) = client.open_once_port();
1995
1996 ping_handle.post(
1997 &client,
1998 PingPongMessage(10, pong_handle.bind(), local_port.bind()),
1999 );
2000
2001 let received = time::timeout(Duration::from_secs(5), local_receiver.recv())
2002 .await
2003 .expect("local_receiver timed out")
2004 .expect("local_receiver closed");
2005 assert!(received);
2006
2007 assert!(
2008 time::timeout(Duration::from_millis(50), undeliverable_rx.recv())
2009 .await
2010 .is_err(),
2011 "unexpected undeliverable during cross-proc ping-pong",
2012 );
2013 }
2014
2015 #[test]
2021 #[should_panic(expected = "gateway already has a proc attached with id")]
2022 fn test_gateway_attach_proc_panics_on_duplicate_live_proc() {
2023 let gateway = Gateway::isolated();
2024 let proc_id = ProcId::instance(Label::strip("alpha"));
2025
2026 let _first = Proc::builder()
2031 .proc_id(proc_id.clone())
2032 .shared_gateway(gateway.clone())
2033 .build()
2034 .unwrap();
2035
2036 let _second = Proc::builder()
2037 .proc_id(proc_id)
2038 .shared_gateway(gateway.clone())
2039 .build()
2040 .unwrap();
2041 }
2042
2043 #[tokio::test]
2050 async fn test_gateway_flush_propagates_to_attached_procs() {
2051 #[derive(Clone)]
2052 struct FlushCountingSender(Arc<AtomicUsize>);
2053
2054 #[async_trait]
2055 impl MailboxSender for FlushCountingSender {
2056 fn post_unchecked(
2057 &self,
2058 _envelope: MessageEnvelope,
2059 _return_handle: PortHandle<Undeliverable<MessageEnvelope>>,
2060 ) {
2061 }
2063
2064 async fn flush(&self) -> Result<(), anyhow::Error> {
2065 self.0.fetch_add(1, Ordering::SeqCst);
2066 Ok(())
2067 }
2068 }
2069
2070 let alpha_flushed = Arc::new(AtomicUsize::new(0));
2071 let beta_flushed = Arc::new(AtomicUsize::new(0));
2072 let forwarder_flushed = Arc::new(AtomicUsize::new(0));
2073
2074 let gateway = Gateway::configured(
2075 channel::reserve_local_addr().into(),
2076 BoxedMailboxSender::new(FlushCountingSender(forwarder_flushed.clone())),
2077 );
2078
2079 let alpha = Proc::builder()
2080 .proc_id(ProcId::instance(Label::strip("alpha")))
2081 .shared_gateway(gateway.clone())
2082 .build()
2083 .unwrap();
2084 let beta = Proc::builder()
2085 .proc_id(ProcId::instance(Label::strip("beta")))
2086 .shared_gateway(gateway.clone())
2087 .build()
2088 .unwrap();
2089
2090 let alpha_probe = alpha.proc_addr().actor_addr("alpha_probe").id().clone();
2094 let beta_probe = beta.proc_addr().actor_addr("beta_probe").id().clone();
2095 assert!(
2096 alpha
2097 .muxer()
2098 .bind(alpha_probe, FlushCountingSender(alpha_flushed.clone()))
2099 );
2100 assert!(
2101 beta.muxer()
2102 .bind(beta_probe, FlushCountingSender(beta_flushed.clone()))
2103 );
2104
2105 assert_eq!(gateway.inner.procs.read().unwrap().len(), 2);
2107
2108 gateway.flush().await.unwrap();
2109
2110 assert_eq!(alpha_flushed.load(Ordering::SeqCst), 1);
2111 assert_eq!(beta_flushed.load(Ordering::SeqCst), 1);
2112 assert_eq!(forwarder_flushed.load(Ordering::SeqCst), 1);
2113 }
2114
2115 #[async_timed_test(timeout_secs = 10)]
2121 async fn test_gateway_flush_concurrent_with_attach_and_drop() {
2122 #[derive(Clone)]
2123 struct NoopSender;
2124
2125 #[async_trait]
2126 impl MailboxSender for NoopSender {
2127 fn post_unchecked(
2128 &self,
2129 _envelope: MessageEnvelope,
2130 _return_handle: PortHandle<Undeliverable<MessageEnvelope>>,
2131 ) {
2132 }
2134
2135 async fn flush(&self) -> Result<(), anyhow::Error> {
2136 Ok(())
2137 }
2138 }
2139
2140 let gateway = Gateway::configured(
2141 channel::reserve_local_addr().into(),
2142 BoxedMailboxSender::new(NoopSender),
2143 );
2144
2145 let barrier = Arc::new(tokio::sync::Barrier::new(2));
2146
2147 let flushes = {
2148 let gateway = gateway.clone();
2149 let barrier = barrier.clone();
2150 tokio::spawn(async move {
2151 barrier.wait().await;
2152 for _ in 0..100 {
2153 gateway.flush().await.unwrap();
2154 tokio::task::yield_now().await;
2155 }
2156 })
2157 };
2158
2159 let attach_drop = {
2160 let gateway = gateway.clone();
2161 let barrier = barrier.clone();
2162 tokio::spawn(async move {
2163 barrier.wait().await;
2164 for i in 0..100 {
2165 let proc = Proc::builder()
2166 .proc_id(ProcId::instance(Label::strip(&format!("p{i}"))))
2167 .shared_gateway(gateway.clone())
2168 .build()
2169 .unwrap();
2170 tokio::task::yield_now().await;
2173 drop(proc);
2174 }
2175 })
2176 };
2177
2178 flushes.await.unwrap();
2179 attach_drop.await.unwrap();
2180
2181 gateway.flush().await.unwrap();
2183
2184 assert_eq!(
2189 gateway
2190 .inner
2191 .procs
2192 .read()
2193 .unwrap()
2194 .values()
2195 .filter_map(WeakProc::upgrade)
2196 .count(),
2197 0,
2198 "no procs should still be live after attach_drop task completes",
2199 );
2200 }
2201
2202 #[tokio::test]
2211 async fn test_weak_gateway_bounces_broken_link_after_drop() {
2212 let gateway = Gateway::isolated();
2213 let weak = Arc::downgrade(&gateway.inner);
2214 drop(gateway);
2215
2216 let scratch = Proc::isolated();
2218 let scratch_client = scratch.client("return");
2219 let (return_handle, mut return_rx) =
2220 scratch_client.open_port::<Undeliverable<MessageEnvelope>>();
2221
2222 let dest_proc = ProcAddr::instance(ChannelAddr::Local(1234), "stranger");
2226 let dest = dest_proc.actor_addr("ghost").port_addr(Port::from(0u64));
2227 let envelope = MessageEnvelope::serialize(
2228 test_actor_id("test", "sender"),
2229 dest.clone(),
2230 &42u64,
2231 Flattrs::new(),
2232 )
2233 .unwrap();
2234
2235 weak.post(envelope, return_handle);
2238
2239 let Undeliverable::Returned(envelope) =
2240 time::timeout(Duration::from_secs(5), return_rx.recv())
2241 .await
2242 .expect("return_rx timed out")
2243 .expect("return_rx closed")
2244 else {
2245 panic!("expected returned envelope");
2246 };
2247 assert_eq!(envelope.dest(), &dest);
2248 assert!(
2249 envelope
2250 .root_delivery_failure()
2251 .is_some_and(|failure| matches!(
2252 &failure.kind,
2253 DeliveryFailureKind::Undeliverable(UndeliverableReason::Transport(_))
2254 )),
2255 "expected structured transport bounce, got {:?}",
2256 envelope.delivery_failures(),
2257 );
2258 }
2259
2260 #[tokio::test]
2265 async fn test_gateway_attach_proc_after_proc_drop() {
2266 let gateway = Gateway::isolated();
2267 let proc_id = ProcId::instance(Label::strip("alpha"));
2268
2269 let first = Proc::builder()
2270 .proc_id(proc_id.clone())
2271 .shared_gateway(gateway.clone())
2272 .build()
2273 .unwrap();
2274 drop(first);
2275
2276 assert_eq!(gateway.inner.procs.read().unwrap().len(), 0);
2278
2279 let second = Proc::builder()
2282 .proc_id(proc_id.clone())
2283 .shared_gateway(gateway.clone())
2284 .build()
2285 .unwrap();
2286 assert_eq!(gateway.inner.procs.read().unwrap().len(), 1);
2287
2288 let client = second.client("client");
2290 let (port, mut rx) = client.bind_handler_port::<u64>();
2291 let dest = port.bind().port_addr().clone();
2292
2293 gateway.post(
2294 MessageEnvelope::serialize(
2295 test_actor_id("test", "sender"),
2296 dest,
2297 &42u64,
2298 Flattrs::new(),
2299 )
2300 .unwrap(),
2301 monitored_return_handle(),
2302 );
2303
2304 let received = time::timeout(Duration::from_secs(5), rx.recv())
2305 .await
2306 .expect("rx timed out")
2307 .expect("rx closed");
2308 assert_eq!(received, 42);
2309 }
2310
2311 #[tokio::test]
2317 async fn test_gateway_serve_stop_unwinds_in_any_order() {
2318 let gateway = Gateway::isolated();
2319 let fallback = gateway.default_location();
2320
2321 let mut s1 = Gateway::serve(&gateway, ChannelAddr::any(ChannelTransport::Local)).unwrap();
2322 let loc1 = gateway.default_location();
2323 let mut s2 = Gateway::serve(&gateway, ChannelAddr::any(ChannelTransport::Local)).unwrap();
2324 let loc2 = gateway.default_location();
2325 let mut s3 = Gateway::serve(&gateway, ChannelAddr::any(ChannelTransport::Local)).unwrap();
2326 let loc3 = gateway.default_location();
2327
2328 assert_eq!(loc1, fallback);
2332 assert_ne!(loc1, loc2);
2333 assert_ne!(loc2, loc3);
2334 assert_ne!(loc1, loc3);
2335
2336 s2.stop("test");
2340 s2.join().await.unwrap();
2341 assert_eq!(gateway.default_location(), loc3);
2342
2343 s3.stop("test");
2345 s3.join().await.unwrap();
2346 assert_eq!(gateway.default_location(), loc1);
2347
2348 s1.stop("test");
2351 s1.join().await.unwrap();
2352 assert_eq!(gateway.default_location(), fallback);
2353 }
2354
2355 #[tokio::test]
2356 async fn test_gateway_first_local_serve_uses_fallback_after_nonlocal_serve() {
2357 let gateway = Gateway::isolated();
2358 let fallback = gateway.default_location();
2359
2360 let mut unix = Gateway::serve(&gateway, ChannelAddr::any(ChannelTransport::Unix)).unwrap();
2361 let unix_location = gateway.default_location();
2362 assert_ne!(unix_location, fallback);
2363
2364 let mut local =
2365 Gateway::serve(&gateway, ChannelAddr::any(ChannelTransport::Local)).unwrap();
2366 assert_eq!(gateway.default_location(), fallback);
2367
2368 local.stop("test");
2369 local.join().await.unwrap();
2370 assert_eq!(gateway.default_location(), unix_location);
2371
2372 unix.stop("test");
2373 unix.join().await.unwrap();
2374 assert_eq!(gateway.default_location(), fallback);
2375 }
2376
2377 #[tokio::test]
2385 async fn test_gateway_serve_via_peer() {
2386 let server_gw = Gateway::new();
2390 let server_addr = ChannelAddr::any(ChannelTransport::Unix);
2391 let mut accept_handle = server_gw.serve_duplex(server_addr).unwrap();
2392 let server_addr = server_gw.default_location().addr().clone();
2393
2394 let server_proc = Proc::builder()
2395 .proc_id(ProcId::instance(Label::strip("echo")))
2396 .shared_gateway(server_gw.clone())
2397 .build()
2398 .unwrap();
2399 let server_inst = server_proc.client("recv");
2400 let (server_port, mut server_rx) = server_inst.bind_handler_port::<u64>();
2401 let PortLocation::Bound(server_dest) = server_port.location() else {
2402 panic!("server port must be bound");
2403 };
2404
2405 let client_gw = Gateway::new();
2409 let pre_default = client_gw.default_location();
2410 let serve_via = client_gw.serve_via(server_addr.clone()).await.unwrap();
2411 let post_default = client_gw.default_location();
2412 assert_ne!(
2413 pre_default, post_default,
2414 "serve_via must update default_location"
2415 );
2416 let (via_uid, inner) = post_default.as_via().expect("default must be via");
2417 assert_eq!(via_uid, client_gw.uid());
2418 assert_eq!(inner.addr(), &server_addr);
2419
2420 let sender = test_actor_id("client", "sender");
2425 client_gw.post(
2426 MessageEnvelope::serialize(sender, server_dest.clone(), &7u64, Flattrs::new()).unwrap(),
2427 monitored_return_handle(),
2428 );
2429 let received = time::timeout(Duration::from_secs(5), server_rx.recv())
2430 .await
2431 .expect("server_rx timed out")
2432 .expect("server_rx closed");
2433 assert_eq!(received, 7);
2434
2435 drop(serve_via);
2437 assert_eq!(client_gw.default_location(), pre_default);
2438
2439 accept_handle.stop("test cleanup");
2441 }
2442
2443 #[tokio::test]
2444 async fn test_gateway_serve_via_sessions_are_additive() {
2445 let server1_gw = Gateway::new();
2446 let mut accept1_handle = server1_gw
2447 .serve_duplex(ChannelAddr::any(ChannelTransport::Unix))
2448 .unwrap();
2449 let server1_addr = server1_gw.default_location().addr().clone();
2450 let server2_gw = Gateway::new();
2451 let mut accept2_handle = server2_gw
2452 .serve_duplex(ChannelAddr::any(ChannelTransport::Unix))
2453 .unwrap();
2454 let server2_addr = server2_gw.default_location().addr().clone();
2455
2456 let client_gw = Gateway::new();
2457 let pre_default = client_gw.default_location();
2458 let mut serve_via1 = client_gw.serve_via(server1_addr.clone()).await.unwrap();
2459 let via1_default = client_gw.default_location();
2460 assert_eq!(via1_default.addr(), &server1_addr);
2461
2462 let client_proc = Proc::legacy_service_pseudo_singleton_on_gateway(client_gw.clone());
2463 let client = client_proc.client("recv");
2464 let (client_port, mut client_rx) = client.bind_handler_port::<u64>();
2465 let PortLocation::Bound(old_client_dest) = client_port.location() else {
2466 panic!("client port must be bound");
2467 };
2468
2469 let mut serve_via2 = client_gw.serve_via(server2_addr.clone()).await.unwrap();
2470 let via2_default = client_gw.default_location();
2471 assert_eq!(via2_default.addr(), &server2_addr);
2472 assert_ne!(via1_default, via2_default);
2473
2474 server1_gw.post(
2475 MessageEnvelope::serialize(
2476 test_actor_id("server1", "sender"),
2477 old_client_dest,
2478 &11u64,
2479 Flattrs::new(),
2480 )
2481 .unwrap(),
2482 monitored_return_handle(),
2483 );
2484 let received = time::timeout(Duration::from_secs(5), client_rx.recv())
2485 .await
2486 .expect("client_rx timed out")
2487 .expect("client_rx closed");
2488 assert_eq!(received, 11);
2489
2490 serve_via2.stop("test cleanup");
2491 serve_via2.join().await.unwrap();
2492 assert_eq!(client_gw.default_location(), via1_default);
2493
2494 serve_via1.stop("test cleanup");
2495 serve_via1.join().await.unwrap();
2496 assert_eq!(client_gw.default_location(), pre_default);
2497
2498 accept1_handle.stop("test cleanup");
2499 accept1_handle.join().await.unwrap();
2500 accept2_handle.stop("test cleanup");
2501 accept2_handle.join().await.unwrap();
2502 }
2503
2504 #[tokio::test]
2505 async fn test_gateway_serve_via_duplicate_peer_reports_rejection() {
2506 let server_gw = Gateway::new();
2507 let mut accept_handle = server_gw
2508 .serve_duplex(ChannelAddr::any(ChannelTransport::Unix))
2509 .unwrap();
2510 let server_addr = server_gw.default_location().addr().clone();
2511
2512 let client_gw = Gateway::new();
2513 let mut serve_via = client_gw.serve_via(server_addr.clone()).await.unwrap();
2514 let via_default = client_gw.default_location();
2515
2516 let err = client_gw.serve_via(server_addr).await.unwrap_err();
2517 assert!(
2518 err.to_string()
2519 .contains("gateway already has a via peer with uid"),
2520 "unexpected error: {err:#}"
2521 );
2522 assert_eq!(client_gw.default_location(), via_default);
2523
2524 serve_via.stop("test cleanup");
2525 serve_via.join().await.unwrap();
2526 accept_handle.stop("test cleanup");
2527 accept_handle.join().await.unwrap();
2528 }
2529
2530 #[tokio::test]
2531 async fn test_gateway_serve_with_listener_takes_precedence_after_via() {
2532 let server_gw = Gateway::new();
2533 let mut accept_handle = server_gw
2534 .serve_duplex(ChannelAddr::any(ChannelTransport::Unix))
2535 .unwrap();
2536 let server_addr = server_gw.default_location().addr().clone();
2537 let _server_proc = Proc::legacy_service_pseudo_singleton_on_gateway(server_gw.clone());
2538
2539 let client_gw = Gateway::new();
2540 let pre_default = client_gw.default_location();
2541 let mut serve_via = client_gw.serve_via(server_addr).await.unwrap();
2542 let via_default = client_gw.default_location();
2543 assert!(via_default.as_via().is_some());
2544
2545 let mut frontend = client_gw
2546 .serve_with_listener(ChannelAddr::any(ChannelTransport::Unix), None)
2547 .unwrap();
2548 let frontend_location = client_gw.default_location();
2549 assert!(matches!(frontend_location.addr(), ChannelAddr::Unix(_)));
2550 assert_ne!(frontend_location, via_default);
2551
2552 frontend.stop("test cleanup");
2553 frontend.join().await.unwrap();
2554 assert_eq!(client_gw.default_location(), via_default);
2555
2556 serve_via.stop("test cleanup");
2557 serve_via.join().await.unwrap();
2558 assert_eq!(client_gw.default_location(), pre_default);
2559
2560 accept_handle.stop("test cleanup");
2561 accept_handle.join().await.unwrap();
2562 }
2563
2564 #[tokio::test]
2565 async fn test_set_default_location_updates_fallback_while_serving() {
2566 let gateway = Gateway::new();
2567 let mut serve =
2568 Gateway::serve(&gateway, ChannelAddr::any(ChannelTransport::Local)).unwrap();
2569 let served_location = gateway.default_location();
2570 let fallback_location = Location::from(ChannelAddr::Local(9876));
2571
2572 gateway.set_default_location(fallback_location.clone());
2573 assert_eq!(gateway.default_location(), served_location);
2574
2575 serve.stop("test cleanup");
2576 serve.join().await.unwrap();
2577 assert_eq!(gateway.default_location(), fallback_location);
2578 }
2579
2580 #[tokio::test]
2581 async fn test_gateway_serve_via_delivers_peeled_instance_proc_destination() {
2582 let server_gw = Gateway::new();
2583 let mut accept_handle = server_gw
2584 .serve_duplex(ChannelAddr::any(ChannelTransport::Unix))
2585 .unwrap();
2586 let server_addr = server_gw.default_location().addr().clone();
2587
2588 let client_gw = Gateway::new();
2589 let mut serve_via = client_gw.serve_via(server_addr).await.unwrap();
2590 let client_proc = Proc::builder()
2591 .proc_id(ProcId::anonymous())
2592 .shared_gateway(client_gw.clone())
2593 .build()
2594 .unwrap();
2595 let client = client_proc.client("recv");
2596 let (port, mut rx) = client.bind_handler_port::<u64>();
2597 let PortLocation::Bound(via_dest) = port.location() else {
2598 panic!("client port must be bound");
2599 };
2600
2601 server_gw.post(
2602 MessageEnvelope::serialize(
2603 test_actor_id("server", "sender"),
2604 via_dest,
2605 &7u64,
2606 Flattrs::new(),
2607 )
2608 .unwrap(),
2609 monitored_return_handle(),
2610 );
2611
2612 let received = time::timeout(Duration::from_secs(5), rx.recv())
2613 .await
2614 .expect("rx timed out")
2615 .expect("rx closed");
2616 assert_eq!(received, 7);
2617
2618 serve_via.stop("test cleanup");
2619 serve_via.join().await.unwrap();
2620 accept_handle.stop("test cleanup");
2621 accept_handle.join().await.unwrap();
2622 }
2623
2624 #[tokio::test]
2625 async fn test_gateway_serve_via_does_not_shadow_peer_legacy_proc() {
2626 let server_gw = Gateway::new();
2627 let mut accept_handle = server_gw
2628 .serve_duplex(ChannelAddr::any(ChannelTransport::Unix))
2629 .unwrap();
2630 let server_addr = server_gw.default_location().addr().clone();
2631 let server_proc = Proc::legacy_service_pseudo_singleton_on_gateway(server_gw.clone());
2632 let server = server_proc.client("recv");
2633 let (server_port, mut server_rx) = server.bind_handler_port::<u64>();
2634 let PortLocation::Bound(server_dest) = server_port.location() else {
2635 panic!("server port must be bound");
2636 };
2637
2638 let client_gw = Gateway::new();
2639 let mut serve_via = client_gw.serve_via(server_addr).await.unwrap();
2640 let client_proc = Proc::legacy_service_pseudo_singleton_on_gateway(client_gw.clone());
2641 let (client_shadow_tx, mut client_shadow_rx) = mpsc::unbounded_channel();
2642 assert!(
2643 client_proc.muxer().bind(
2644 server_dest.actor_addr().id().clone(),
2645 RecordingSender(client_shadow_tx),
2646 ),
2647 "client shadow handler should bind"
2648 );
2649
2650 client_gw.post(
2651 MessageEnvelope::serialize(
2652 test_actor_id("client", "sender"),
2653 server_dest.clone(),
2654 &7u64,
2655 Flattrs::new(),
2656 )
2657 .unwrap(),
2658 monitored_return_handle(),
2659 );
2660
2661 let received = time::timeout(Duration::from_secs(5), server_rx.recv())
2662 .await
2663 .expect("server_rx timed out")
2664 .expect("server_rx closed");
2665 assert_eq!(received, 7);
2666 time::timeout(Duration::from_millis(200), client_shadow_rx.recv())
2667 .await
2668 .expect_err("peer legacy proc location must not deliver to the attached client proc");
2669
2670 serve_via.stop("test cleanup");
2671 serve_via.join().await.unwrap();
2672 accept_handle.stop("test cleanup");
2673 accept_handle.join().await.unwrap();
2674 }
2675
2676 #[tokio::test]
2677 async fn test_gateway_serve_via_relay_delivers_to_client_instance_proc() {
2678 let relay_gw = Gateway::new();
2679 let mut accept_handle = relay_gw
2680 .serve_duplex(ChannelAddr::any(ChannelTransport::Unix))
2681 .unwrap();
2682 let relay_addr = relay_gw.default_location().addr().clone();
2683
2684 let client_gw = Gateway::new();
2685 let mut serve_via = client_gw.serve_via(relay_addr.clone()).await.unwrap();
2686 let client_proc = Proc::builder()
2687 .proc_id(ProcId::anonymous())
2688 .shared_gateway(client_gw.clone())
2689 .build()
2690 .unwrap();
2691 let client = client_proc.client("recv");
2692 let (port, mut rx) = client.bind_handler_port::<u64>();
2693 let PortLocation::Bound(via_dest) = port.location() else {
2694 panic!("client port must be bound");
2695 };
2696
2697 let relay_dialer = DialMailboxRouter::new();
2704 relay_dialer.post(
2705 MessageEnvelope::serialize(
2706 test_actor_id("third_party", "sender"),
2707 via_dest,
2708 &7u64,
2709 Flattrs::new(),
2710 )
2711 .unwrap(),
2712 monitored_return_handle(),
2713 );
2714
2715 let received = time::timeout(Duration::from_secs(5), rx.recv())
2716 .await
2717 .expect("rx timed out")
2718 .expect("rx closed");
2719 assert_eq!(received, 7);
2720
2721 serve_via.stop("test cleanup");
2722 serve_via.join().await.unwrap();
2723 accept_handle.stop("test cleanup");
2724 accept_handle.join().await.unwrap();
2725 }
2726
2727 #[tokio::test]
2743 async fn test_gateway_attach_bidirectional() {
2744 use crate::testing::ids::test_actor_id;
2745
2746 let gw_a = Gateway::isolated();
2747 let pre_a_default = gw_a.default_location();
2748 let gw_b = Gateway::isolated();
2749 let pre_b_default = gw_b.default_location();
2750
2751 let attach = gw_a.attach(&gw_b);
2755
2756 let post_a_default = gw_a.default_location();
2758 let post_b_default = gw_b.default_location();
2759 let (uid_a, inner_a) = post_a_default.as_via().expect("gw_a default is via");
2760 assert_eq!(uid_a, gw_a.uid());
2761 assert_eq!(inner_a.as_ref(), &pre_b_default);
2762 let (uid_b, inner_b) = post_b_default.as_via().expect("gw_b default is via");
2763 assert_eq!(uid_b, gw_b.uid());
2764 assert_eq!(inner_b.as_ref(), &pre_a_default);
2765
2766 let proc_a = Proc::builder()
2769 .proc_id(ProcId::instance(Label::strip("alpha")))
2770 .shared_gateway(gw_a.clone())
2771 .build()
2772 .unwrap();
2773 let alpha_client = proc_a.client("client");
2774 let (alpha_port, mut alpha_rx) = alpha_client.bind_handler_port::<u64>();
2775 let PortLocation::Bound(alpha_dest) = alpha_port.location() else {
2776 panic!("alpha port must be bound");
2777 };
2778 assert!(
2779 alpha_dest.location().as_via().is_some(),
2780 "alpha carries via prefix"
2781 );
2782
2783 let proc_b = Proc::builder()
2784 .proc_id(ProcId::instance(Label::strip("beta")))
2785 .shared_gateway(gw_b.clone())
2786 .build()
2787 .unwrap();
2788 let beta_client = proc_b.client("client");
2789 let (beta_port, mut beta_rx) = beta_client.bind_handler_port::<u64>();
2790 let PortLocation::Bound(beta_dest) = beta_port.location() else {
2791 panic!("beta port must be bound");
2792 };
2793 assert!(
2794 beta_dest.location().as_via().is_some(),
2795 "beta carries via prefix"
2796 );
2797
2798 let sender = test_actor_id("client", "sender");
2801 gw_a.post(
2802 MessageEnvelope::serialize(sender.clone(), alpha_dest.clone(), &11u64, Flattrs::new())
2803 .unwrap(),
2804 monitored_return_handle(),
2805 );
2806 assert_eq!(
2807 time::timeout(Duration::from_secs(2), alpha_rx.recv())
2808 .await
2809 .expect("alpha_rx timed out")
2810 .expect("alpha_rx closed"),
2811 11
2812 );
2813
2814 gw_b.post(
2817 MessageEnvelope::serialize(sender.clone(), alpha_dest.clone(), &22u64, Flattrs::new())
2818 .unwrap(),
2819 monitored_return_handle(),
2820 );
2821 assert_eq!(
2822 time::timeout(Duration::from_secs(2), alpha_rx.recv())
2823 .await
2824 .expect("alpha_rx timed out")
2825 .expect("alpha_rx closed"),
2826 22
2827 );
2828
2829 gw_a.post(
2831 MessageEnvelope::serialize(sender.clone(), beta_dest.clone(), &33u64, Flattrs::new())
2832 .unwrap(),
2833 monitored_return_handle(),
2834 );
2835 assert_eq!(
2836 time::timeout(Duration::from_secs(2), beta_rx.recv())
2837 .await
2838 .expect("beta_rx timed out")
2839 .expect("beta_rx closed"),
2840 33
2841 );
2842
2843 drop(attach);
2846 assert_eq!(gw_a.default_location(), pre_a_default);
2847 assert_eq!(gw_b.default_location(), pre_b_default);
2848 assert!(gw_a.inner.peers.read().unwrap().is_empty());
2849 assert!(gw_b.inner.peers.read().unwrap().is_empty());
2850 }
2851}