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#[must_use = "dropping the AttachedProcGuard immediately detaches the proc"]
264pub struct AttachedProcGuard {
265 gateway: Weak<GatewayState>,
266 proc_id: ProcId,
267 weak: WeakProc,
268}
269
270impl fmt::Debug for AttachedProcGuard {
271 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
272 f.debug_struct("AttachedProcGuard")
273 .field("proc_id", &self.proc_id)
274 .finish()
275 }
276}
277
278impl Drop for AttachedProcGuard {
279 fn drop(&mut self) {
280 let Some(state) = self.gateway.upgrade() else {
281 return;
282 };
283 let mut procs = state.procs.write().unwrap();
284 if procs
285 .get(&self.proc_id)
286 .is_some_and(|weak| weak.ptr_eq(&self.weak))
287 {
288 procs.remove(&self.proc_id);
289 }
290 }
291}
292
293#[must_use = "dropping the PeerAttachGuard immediately removes the peer entry"]
296pub struct PeerAttachGuard {
297 gateway: Weak<GatewayState>,
298 uid: Uid,
299}
300
301impl fmt::Debug for PeerAttachGuard {
302 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
303 f.debug_struct("PeerAttachGuard")
304 .field("uid", &self.uid)
305 .finish()
306 }
307}
308
309impl Drop for PeerAttachGuard {
310 fn drop(&mut self) {
311 let Some(state) = self.gateway.upgrade() else {
312 return;
313 };
314 state.peers.write().unwrap().remove(&self.uid);
315 }
316}
317
318#[derive(Debug, thiserror::Error)]
321#[error("gateway already has a via peer with uid {uid}")]
322pub struct PeerAttachError {
323 pub uid: Uid,
325}
326
327struct RoutingState {
330 fallback_location: Location,
332
333 base_forwarder: BoxedMailboxSender,
335
336 active_serves: Vec<ActiveServe>,
338}
339
340struct Routing {
344 state: RoutingState,
345
346 default_location: Location,
349
350 local_delivery_locations: Arc<[Location]>,
352
353 forwarder: BoxedMailboxSender,
358}
359
360impl Routing {
361 fn new(fallback_location: Location, base_forwarder: BoxedMailboxSender) -> Self {
362 Self {
363 state: RoutingState {
364 fallback_location: fallback_location.clone(),
365 base_forwarder: base_forwarder.clone(),
366 active_serves: Vec::new(),
367 },
368 default_location: fallback_location.clone(),
369 local_delivery_locations: vec![fallback_location].into(),
370 forwarder: base_forwarder,
371 }
372 }
373
374 fn mutate(&mut self, update: impl FnOnce(&mut RoutingState)) {
375 update(&mut self.state);
376 self.recompute();
377 }
378
379 fn recompute(&mut self) {
380 self.default_location = self
381 .state
382 .active_serves
383 .last()
384 .map(|serve| serve.location.clone())
385 .unwrap_or_else(|| self.state.fallback_location.clone());
386 self.local_delivery_locations = if self.state.active_serves.is_empty() {
387 vec![self.default_location.clone()].into()
388 } else {
389 self.state
390 .active_serves
391 .iter()
392 .map(|serve| serve.location.clone())
393 .collect::<Vec<_>>()
394 .into()
395 };
396 self.forwarder = self
397 .state
398 .active_serves
399 .iter()
400 .rev()
401 .find_map(|serve| match &serve.kind {
402 ActiveServeKind::Server => None,
403 ActiveServeKind::Via { forwarder } => Some(forwarder.clone()),
404 })
405 .unwrap_or_else(|| self.state.base_forwarder.clone());
406 }
407}
408
409#[derive(Clone, Copy, Debug, Eq, PartialEq)]
410struct ServeId(u64);
411
412struct ActiveServe {
413 id: ServeId,
414 location: Location,
415 kind: ActiveServeKind,
416}
417
418enum ActiveServeKind {
419 Server,
420 Via { forwarder: BoxedMailboxSender },
421}
422
423struct GatewayState {
424 uid: Uid,
430
431 routing: RwLock<Routing>,
436
437 procs: RwLock<HashMap<ProcId, WeakProc>>,
440
441 next_serve_id: AtomicU64,
443
444 peers: RwLock<HashMap<Uid, BoxedMailboxSender>>,
450}
451
452impl Gateway {
453 pub fn new() -> Self {
455 Self::configured(
456 channel::reserve_local_addr().into(),
457 DialMailboxRouter::new().into_boxed(),
458 )
459 }
460
461 pub fn isolated() -> Self {
463 Self::configured(
464 channel::reserve_local_addr().into(),
465 BoxedMailboxSender::new(UnroutableMailboxSender),
466 )
467 }
468
469 pub fn global() -> &'static Self {
471 static GLOBAL_GATEWAY: OnceLock<Gateway> = OnceLock::new();
472 GLOBAL_GATEWAY.get_or_init(Self::new)
473 }
474
475 pub fn current() -> Self {
479 Proc::current().gateway()
480 }
481
482 pub(crate) fn configured(default_location: Location, forwarder: BoxedMailboxSender) -> Self {
487 Self {
488 inner: Arc::new(GatewayState {
489 uid: Uid::anonymous(),
490 routing: RwLock::new(Routing::new(default_location, forwarder.clone())),
491 procs: RwLock::new(HashMap::new()),
492 next_serve_id: AtomicU64::new(1),
493 peers: RwLock::new(HashMap::new()),
494 }),
495 }
496 }
497
498 pub fn uid(&self) -> &Uid {
502 &self.inner.uid
503 }
504
505 pub fn default_location(&self) -> Location {
507 self.inner.routing.read().unwrap().default_location.clone()
508 }
509
510 pub fn forwarder(&self) -> BoxedMailboxSender {
514 self.inner.routing.read().unwrap().forwarder.clone()
515 }
516
517 pub fn set_default_location(&self, location: Location) {
523 let mut routing = self.inner.routing.write().unwrap();
524 routing.mutate(|routing| routing.fallback_location = location);
525 }
526
527 pub(crate) fn attach_proc(&self, proc: &Proc) -> AttachedProcGuard {
551 let proc_id = proc.proc_id().clone();
552 let weak = proc.downgrade();
553 let mut procs = self.inner.procs.write().unwrap();
554 let duplicate_live_proc = procs.get(&proc_id).and_then(WeakProc::upgrade).is_some();
555 if duplicate_live_proc {
556 drop(procs);
557 panic!("gateway already has a proc attached with id {}", proc_id)
558 }
559 procs.insert(proc_id.clone(), weak.clone());
560 AttachedProcGuard {
561 gateway: Arc::downgrade(&self.inner),
562 proc_id,
563 weak,
564 }
565 }
566
567 pub fn attach_peer(
580 &self,
581 uid: Uid,
582 sender: BoxedMailboxSender,
583 ) -> Result<PeerAttachGuard, PeerAttachError> {
584 let mut via = self.inner.peers.write().unwrap();
585 if via.contains_key(&uid) {
586 return Err(PeerAttachError { uid });
587 }
588 via.insert(uid.clone(), sender);
589 Ok(PeerAttachGuard {
590 gateway: Arc::downgrade(&self.inner),
591 uid,
592 })
593 }
594
595 pub(crate) fn serve_rx(
596 &self,
597 rx: impl channel::Rx<MessageEnvelope> + Send + 'static,
598 ) -> MailboxServerHandle {
599 Arc::downgrade(&self.inner).serve(rx)
600 }
601
602 pub fn serve(&self, addr: ChannelAddr) -> Result<GatewayServeHandle, ChannelError> {
616 let (serve_id, handle) = self.serve_inner(addr)?;
617 Ok(GatewayServeHandle {
618 gateway: self.clone(),
619 handle,
620 stopped: false,
621 kind: HandleKind::Serve {
622 serve_id: Some(serve_id),
623 },
624 })
625 }
626
627 pub fn serve_duplex(&self, addr: ChannelAddr) -> Result<GatewayServeHandle, ChannelError> {
650 self.serve_duplex_with_listener(addr, None)
651 }
652
653 fn serve_duplex_with_listener(
654 &self,
655 addr: ChannelAddr,
656 listener: Option<std::net::TcpListener>,
657 ) -> Result<GatewayServeHandle, ChannelError> {
658 if !addr.transport().supports_duplex() {
659 return Err(ChannelError::Other(anyhow::anyhow!(
660 "serve_duplex requires a duplex-capable transport, but {addr} does not support duplex"
661 )));
662 }
663 if addr.transport().is_net() {
675 return self.serve_mux_with_listener(addr, listener);
676 }
677 let server = PreboundAcceptServer::duplex(addr, listener)
678 .map_err(|e| ChannelError::Other(anyhow::anyhow!("{e}")))?;
679 Ok(self.serve_duplex_with_server(server))
680 }
681
682 fn serve_duplex_with_server(&self, server: PreboundAcceptServer) -> GatewayServeHandle {
683 let bound_addr = server.addr().clone();
684 let location = Location::from(bound_addr.clone());
685 let cancel_token = CancellationToken::new();
691 let loop_token = cancel_token.clone();
692 let gateway = self.clone();
693 let inner = server.inner;
694 let serve_id = self.add_server(location);
695 let join_handle = tokio::spawn(async move {
696 duplex_accept_loop(inner, bound_addr, gateway, loop_token).await;
697 Ok::<(), MailboxServerError>(())
698 });
699 let (idle_stop_tx, _idle_stop_rx) = watch::channel(false);
703 let handle = MailboxServerHandle::from_parts(join_handle, idle_stop_tx);
704 GatewayServeHandle {
705 gateway: self.clone(),
706 handle,
707 stopped: false,
708 kind: HandleKind::ServeDuplex {
709 serve_id: Some(serve_id),
710 cancel_token,
711 },
712 }
713 }
714
715 pub fn serve_mux_with_listener(
733 &self,
734 addr: ChannelAddr,
735 listener: Option<std::net::TcpListener>,
736 ) -> Result<GatewayServeHandle, ChannelError> {
737 let mux =
738 channel::serve_mux::<MessageEnvelope, MessageEnvelope, AttachWire>(addr, listener)?;
739 let bound_addr = mux.addr().clone();
740 let simplex_gateway = self.clone();
741 let duplex_gateway = self.clone();
742 let duplex_addr = bound_addr.clone();
743 let raw = mux.serve(
744 move |rx| simplex_gateway.serve(rx),
745 move |duplex_server, mut stop_rx| async move {
746 let cancel_token = CancellationToken::new();
753 let loop_token = cancel_token.clone();
754 tokio::spawn(async move {
755 if stop_rx.wait_for(|stopped| *stopped).await.is_ok() {
756 cancel_token.cancel();
757 }
758 });
759 duplex_accept_loop(duplex_server, duplex_addr, duplex_gateway, loop_token).await;
760 },
761 );
762 Ok(GatewayServeHandle::from_mailbox_handle(
763 self.clone(),
764 bound_addr,
765 raw,
766 ))
767 }
768
769 pub fn serve_with_listener(
778 &self,
779 addr: ChannelAddr,
780 listener: Option<std::net::TcpListener>,
781 ) -> Result<GatewayServeHandle, ChannelError> {
782 if addr.transport().supports_duplex() {
783 self.serve_duplex_with_listener(addr, listener)
784 } else {
785 let addr = self.resolve_serve_addr(addr);
786 let (addr, rx) = channel::serve_with_listener(addr, listener)?;
787 let serve_id = self.add_server(Location::from(addr));
788 let raw = self.clone().serve(rx);
789 Ok(GatewayServeHandle::from_simplex(
790 self.clone(),
791 raw,
792 Some(serve_id),
793 ))
794 }
795 }
796
797 pub async fn serve_via(&self, addr: ChannelAddr) -> anyhow::Result<GatewayServeHandle> {
815 let my_uid = self.inner.uid.clone();
816 let mut duplex_client = channel::duplex::dial::<MessageEnvelope, AttachWire>(addr)?;
817 let duplex_tx = duplex_client.tx();
818 let mut duplex_rx = duplex_client
819 .take_rx()
820 .expect("dial returns a fresh DuplexClient with rx present");
821
822 duplex_tx.post(build_control_envelope(&AttachRequest { uid: my_uid })?);
823
824 let location = match tokio::time::timeout(ATTACH_HANDSHAKE_TIMEOUT, duplex_rx.recv())
825 .await
826 .map_err(|_| {
827 anyhow::anyhow!(
828 "attach handshake timed out after {:?} waiting for AttachAck",
829 ATTACH_HANDSHAKE_TIMEOUT
830 )
831 })?? {
832 AttachWire::Ack(AttachAck::Accepted { location }) => location,
833 AttachWire::Ack(AttachAck::Rejected { reason }) => {
834 anyhow::bail!("attach rejected by peer: {reason}")
835 }
836 AttachWire::Envelope(_) => anyhow::bail!("expected attach ack as first message"),
837 };
838
839 let serve_id = self.add_via_server(location, MailboxClient::new(duplex_tx).into_boxed());
840 let serve_handle = self.serve_rx(AttachRx(duplex_rx));
841 Ok(GatewayServeHandle {
842 gateway: self.clone(),
843 handle: serve_handle,
844 stopped: false,
845 kind: HandleKind::ServeVia {
846 serve_id: Some(serve_id),
847 duplex_client: Some(duplex_client),
848 },
849 })
850 }
851
852 fn serve_inner(
853 &self,
854 addr: ChannelAddr,
855 ) -> Result<(ServeId, MailboxServerHandle), ChannelError> {
856 let addr = self.resolve_serve_addr(addr);
857 let (addr, rx) = channel::serve(addr)?;
858 let serve_id = self.add_server(Location::from(addr));
859 Ok((serve_id, self.serve_rx(rx)))
860 }
861
862 fn resolve_serve_addr(&self, addr: ChannelAddr) -> ChannelAddr {
863 if addr != ChannelAddr::any(ChannelTransport::Local) {
864 return addr;
865 }
866
867 let routing = self.inner.routing.read().unwrap();
871 let fallback_addr = routing.state.fallback_location.addr();
872 let fallback_is_active = routing.state.active_serves.iter().any(|serve| {
873 matches!(&serve.kind, ActiveServeKind::Server) && serve.location.addr() == fallback_addr
874 });
875 if matches!(fallback_addr, ChannelAddr::Local(_)) && !fallback_is_active {
876 return fallback_addr.clone();
877 }
878 addr
879 }
880
881 fn add_server(&self, location: Location) -> ServeId {
882 self.add_active_serve(location, ActiveServeKind::Server)
883 }
884
885 fn add_via_server(&self, location: Location, forwarder: BoxedMailboxSender) -> ServeId {
886 self.add_active_serve(location, ActiveServeKind::Via { forwarder })
887 }
888
889 fn add_active_serve(&self, location: Location, kind: ActiveServeKind) -> ServeId {
890 let id = ServeId(
891 self.inner
892 .next_serve_id
893 .fetch_add(1, AtomicOrdering::Relaxed),
894 );
895 let mut routing = self.inner.routing.write().unwrap();
896 routing.mutate(|routing| {
897 routing
898 .active_serves
899 .push(ActiveServe { id, location, kind });
900 });
901 id
902 }
903
904 fn remove_active_serve(&self, serve_id: ServeId) {
905 let mut routing = self.inner.routing.write().unwrap();
906 routing.mutate(|routing| {
907 if let Some(index) = routing
908 .active_serves
909 .iter()
910 .rposition(|active| active.id == serve_id)
911 {
912 routing.active_serves.remove(index);
913 }
914 });
915 }
916
917 fn local_delivery_locations(&self) -> Arc<[Location]> {
918 Arc::clone(&self.inner.routing.read().unwrap().local_delivery_locations)
919 }
920
921 fn envelope_with_next_hop_location(
922 envelope: MessageEnvelope,
923 location: Location,
924 ) -> MessageEnvelope {
925 let dest_id = envelope.next_hop().id().clone();
926 envelope.with_next_hop(PortAddr::new(dest_id, location))
927 }
928
929 fn return_no_route(
930 envelope: MessageEnvelope,
931 return_handle: PortHandle<Undeliverable<MessageEnvelope>>,
932 ) {
933 let target = envelope.dest().clone();
934 let failure = DeliveryFailure::new(UndeliverableReason::Transport(TransportFailure::new(
935 target,
936 TransportFailureReason::NoRoute,
937 )));
938 envelope.undeliverable(failure, return_handle);
939 }
940
941 pub(crate) async fn flush(&self) -> Result<(), anyhow::Error> {
958 let local_procs: Vec<_> = self
964 .inner
965 .procs
966 .read()
967 .unwrap()
968 .values()
969 .filter_map(WeakProc::upgrade)
970 .collect();
971 let concurrency = local_procs.len().max(1);
975 let proc_results: Vec<_> = futures::stream::iter(
976 local_procs
977 .into_iter()
978 .map(|proc| async move { proc.muxer().flush().await }),
979 )
980 .buffer_unordered(concurrency)
981 .collect()
982 .await;
983 let forwarder = self.inner.routing.read().unwrap().forwarder.clone();
984 let forwarder_result = forwarder.flush().await;
985
986 proc_results
988 .into_iter()
989 .chain(std::iter::once(forwarder_result))
990 .collect()
991 }
992}
993
994impl fmt::Debug for Gateway {
995 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
996 f.debug_struct("Gateway")
997 .field("default_location", &self.default_location())
998 .finish()
999 }
1000}
1001
1002pub struct GatewayServeHandle {
1013 gateway: Gateway,
1014 handle: MailboxServerHandle,
1015 stopped: bool,
1016 kind: HandleKind,
1017}
1018
1019enum HandleKind {
1021 Serve { serve_id: Option<ServeId> },
1025 ServeDuplex {
1029 serve_id: Option<ServeId>,
1030 cancel_token: CancellationToken,
1031 },
1032 ServeVia {
1035 serve_id: Option<ServeId>,
1036 duplex_client: Option<channel::duplex::DuplexClient<MessageEnvelope, AttachWire>>,
1037 },
1038}
1039
1040impl fmt::Debug for GatewayServeHandle {
1041 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1042 let variant = match &self.kind {
1043 HandleKind::Serve { .. } => "Serve",
1044 HandleKind::ServeDuplex { .. } => "ServeDuplex",
1045 HandleKind::ServeVia { .. } => "ServeVia",
1046 };
1047 f.debug_struct("GatewayServeHandle")
1048 .field("kind", &variant)
1049 .finish()
1050 }
1051}
1052
1053impl GatewayServeHandle {
1054 fn from_simplex(
1056 gateway: Gateway,
1057 handle: MailboxServerHandle,
1058 serve_id: Option<ServeId>,
1059 ) -> Self {
1060 Self {
1061 gateway,
1062 handle,
1063 stopped: false,
1064 kind: HandleKind::Serve { serve_id },
1065 }
1066 }
1067
1068 pub fn from_mailbox_handle(
1077 gateway: Gateway,
1078 addr: ChannelAddr,
1079 handle: MailboxServerHandle,
1080 ) -> Self {
1081 let serve_id = gateway.add_server(Location::from(addr));
1082 Self::from_simplex(gateway, handle, Some(serve_id))
1083 }
1084
1085 pub fn stop(&mut self, reason: &str) {
1090 if self.stopped {
1091 return;
1092 }
1093 self.stopped = true;
1094 match &self.kind {
1098 HandleKind::ServeDuplex { cancel_token, .. } => {
1099 tracing::info!("stopping gateway duplex accept loop; reason: {reason}");
1100 cancel_token.cancel();
1101 }
1102 HandleKind::Serve { .. } | HandleKind::ServeVia { .. } => {
1103 self.handle.stop(reason);
1104 }
1105 }
1106 self.run_cleanup();
1107 }
1108
1109 pub async fn join(mut self) -> Result<(), MailboxServerError> {
1114 let inner_result = (&mut self.handle).await;
1115
1116 if let HandleKind::ServeVia { duplex_client, .. } = &mut self.kind
1118 && let Some(client) = duplex_client.take()
1119 {
1120 client.join().await;
1121 }
1122 self.run_cleanup();
1123 match inner_result {
1124 Ok(Ok(())) => Ok(()),
1125 Ok(Err(err)) => Err(err),
1126 Err(join_err) => Err(MailboxServerError::Channel(ChannelError::Other(
1127 anyhow::anyhow!("gateway serve task join error: {join_err}"),
1128 ))),
1129 }
1130 }
1131
1132 fn run_cleanup(&mut self) {
1133 match &mut self.kind {
1134 HandleKind::Serve { serve_id }
1135 | HandleKind::ServeDuplex { serve_id, .. }
1136 | HandleKind::ServeVia { serve_id, .. } => {
1137 if let Some(serve_id) = serve_id.take() {
1138 self.gateway.remove_active_serve(serve_id);
1139 }
1140 }
1141 }
1142 }
1143}
1144
1145impl Drop for GatewayServeHandle {
1146 fn drop(&mut self) {
1147 if self.stopped {
1155 return;
1156 }
1157 if matches!(&self.kind, HandleKind::ServeVia { .. }) {
1158 self.run_cleanup();
1159 }
1160 }
1161}
1162
1163async fn duplex_accept_loop(
1174 mut duplex_server: channel::duplex::DuplexServer<MessageEnvelope, AttachWire>,
1175 bound_addr: ChannelAddr,
1176 gateway: Gateway,
1177 cancel_token: CancellationToken,
1178) {
1179 let mut tasks: JoinSet<()> = JoinSet::new();
1180 loop {
1181 let accept = tokio::select! {
1182 result = duplex_server.accept() => result,
1183 () = cancel_token.cancelled() => break,
1184 };
1185 let (duplex_rx, duplex_tx) = match accept {
1186 Ok(pair) => pair,
1187 Err(e) => {
1188 tracing::info!(
1189 bound_addr = bound_addr.to_string(),
1190 error = %e,
1191 "duplex accept loop ended"
1192 );
1193 break;
1194 }
1195 };
1196
1197 tasks.spawn(serve_duplex_connection(
1198 gateway.clone(),
1199 duplex_rx,
1200 duplex_tx,
1201 cancel_token.clone(),
1202 ));
1203 }
1204
1205 while tasks.join_next().await.is_some() {}
1206 duplex_server.stop("duplex accept loop draining");
1215 duplex_server.join().await;
1216}
1217
1218async fn serve_duplex_connection(
1219 gateway: Gateway,
1220 mut duplex_rx: channel::duplex::DuplexRx<MessageEnvelope>,
1221 duplex_tx: channel::duplex::DuplexTx<AttachWire>,
1222 cancel_token: CancellationToken,
1223) {
1224 let first_msg = tokio::select! {
1225 result = duplex_rx.recv() => match result {
1226 Ok(msg) => msg,
1227 Err(e) => {
1228 tracing::info!(error = %e, "duplex connection closed before first message");
1229 return;
1230 }
1231 },
1232 () = cancel_token.cancelled() => return,
1233 };
1234
1235 if let Ok(attach_request) = first_msg.deserialized::<AttachRequest>() {
1236 let peer_uid = attach_request.uid;
1237 tracing::info!(
1238 uid = %peer_uid,
1239 "duplex accepted gateway-attach connection",
1240 );
1241 let sender = MailboxClient::new(AttachTx(duplex_tx.clone())).into_boxed();
1246 let attach_guard = match gateway.attach_peer(peer_uid.clone(), sender) {
1247 Ok(guard) => guard,
1248 Err(err) => {
1249 let reason = err.to_string();
1250 tracing::warn!(
1251 uid = %err.uid,
1252 error = %reason,
1253 "rejecting gateway-attach connection"
1254 );
1255 if let Err(send_error) = duplex_tx
1256 .send(AttachWire::Ack(AttachAck::Rejected { reason }))
1257 .await
1258 {
1259 tracing::warn!(
1260 uid = %err.uid,
1261 error = %send_error,
1262 "failed to send gateway-attach rejection"
1263 );
1264 }
1265 return;
1266 }
1267 };
1268
1269 let via_location = gateway.default_location().with_via(peer_uid);
1271 duplex_tx.post(AttachWire::Ack(AttachAck::Accepted {
1272 location: via_location,
1273 }));
1274
1275 let mut handle = gateway.serve_rx(duplex_rx);
1276 tokio::select! {
1277 _ = &mut handle => {}
1278 () = cancel_token.cancelled() => {
1279 handle.stop("gateway accept loop stopping");
1280 let _ = handle.await;
1281 }
1282 }
1283 drop(attach_guard);
1284 tracing::info!("gateway-attach connection closed");
1285 } else {
1286 let _keep_alive = duplex_tx;
1292 let rx = PrependRx {
1293 first: Some(first_msg),
1294 inner: duplex_rx,
1295 };
1296 let mut handle = gateway.serve_rx(rx);
1297 tokio::select! {
1298 _ = &mut handle => {}
1299 () = cancel_token.cancelled() => {
1300 handle.stop("gateway accept loop stopping");
1301 let _ = handle.await;
1302 }
1303 }
1304 }
1305}
1306
1307struct PrependRx<R> {
1312 first: Option<MessageEnvelope>,
1313 inner: R,
1314}
1315
1316#[async_trait]
1317impl<R: channel::Rx<MessageEnvelope> + Send> channel::Rx<MessageEnvelope> for PrependRx<R> {
1318 async fn recv(&mut self) -> Result<MessageEnvelope, ChannelError> {
1319 if let Some(msg) = self.first.take() {
1320 return Ok(msg);
1321 }
1322 self.inner.recv().await
1323 }
1324
1325 fn addr(&self) -> ChannelAddr {
1326 self.inner.addr()
1327 }
1328
1329 async fn join(self) {
1330 self.inner.join().await
1331 }
1332}
1333
1334#[async_trait]
1335impl crate::mailbox::MailboxSender for Weak<GatewayState> {
1336 fn post_unchecked(
1337 &self,
1338 envelope: MessageEnvelope,
1339 return_handle: PortHandle<Undeliverable<MessageEnvelope>>,
1340 ) {
1341 match Weak::upgrade(self).map(|inner| Gateway { inner }) {
1342 Some(gateway) => {
1343 gateway.route_envelope(envelope, return_handle);
1344 }
1345 None => {
1346 let target = envelope.dest().clone();
1347 let failure =
1348 DeliveryFailure::new(UndeliverableReason::Transport(TransportFailure::new(
1349 target,
1350 TransportFailureReason::LinkUnavailable("gateway is gone".to_string()),
1351 )));
1352 envelope.undeliverable(failure, return_handle)
1353 }
1354 }
1355 }
1356
1357 async fn flush(&self) -> Result<(), anyhow::Error> {
1358 match Weak::upgrade(self).map(|inner| Gateway { inner }) {
1359 Some(gateway) => Gateway::flush(&gateway).await,
1360 None => Ok(()),
1361 }
1362 }
1363}
1364
1365#[async_trait]
1366impl crate::mailbox::MailboxSender for Gateway {
1367 fn post_unchecked(
1368 &self,
1369 envelope: MessageEnvelope,
1370 return_handle: PortHandle<Undeliverable<MessageEnvelope>>,
1371 ) {
1372 self.route_envelope(envelope, return_handle);
1373 }
1374
1375 async fn flush(&self) -> Result<(), anyhow::Error> {
1376 Gateway::flush(self).await
1377 }
1378}
1379
1380impl Gateway {
1381 fn route_envelope(
1382 &self,
1383 envelope: MessageEnvelope,
1384 return_handle: PortHandle<Undeliverable<MessageEnvelope>>,
1385 ) {
1386 let mut envelope = envelope;
1402 loop {
1403 let dest_location = envelope.next_hop().location().clone();
1404 let Ok((via_uid, inner_location)) = dest_location.pop_via() else {
1405 break;
1406 };
1407
1408 if let Some(sender) = self.inner.peers.read().unwrap().get(&via_uid).cloned() {
1409 let envelope = Gateway::envelope_with_next_hop_location(envelope, inner_location);
1410 sender.post(envelope, return_handle);
1411 return;
1412 }
1413 if via_uid != self.inner.uid {
1414 let forwarder = self.inner.routing.read().unwrap().forwarder.clone();
1419 forwarder.post(envelope, return_handle);
1420 return;
1421 }
1422 envelope = Gateway::envelope_with_next_hop_location(envelope, inner_location);
1423 }
1424
1425 let dest_proc = envelope.dest().actor_addr().proc_addr();
1432 let local = self
1433 .inner
1434 .procs
1435 .read()
1436 .unwrap()
1437 .get(dest_proc.id())
1438 .and_then(WeakProc::upgrade);
1439 if let Some(proc) = local {
1440 let local_locations = self.local_delivery_locations();
1441 if proc.is_local_delivery_target_at(&dest_proc, &local_locations) {
1442 proc.muxer().post(envelope, return_handle);
1443 return;
1444 }
1445 }
1446
1447 if envelope.has_next_hop() {
1448 Gateway::return_no_route(envelope, return_handle);
1449 } else {
1450 let forwarder = self.inner.routing.read().unwrap().forwarder.clone();
1451 forwarder.post(envelope, return_handle)
1452 }
1453 }
1454}
1455
1456#[cfg(test)]
1457mod tests {
1458 use std::sync::Arc;
1459 use std::sync::atomic::AtomicUsize;
1460 use std::sync::atomic::Ordering;
1461 use std::time::Duration;
1462
1463 use async_trait::async_trait;
1464 use hyperactor_config::Flattrs;
1465 use timed_test::async_timed_test;
1466 use tokio::sync::mpsc;
1467 use tokio::time;
1468
1469 use super::*;
1470 use crate::Endpoint as _;
1471 use crate::Label;
1472 use crate::ProcAddr;
1473 use crate::mailbox::DeliveryFailureKind;
1474 use crate::mailbox::MailboxClient;
1475 use crate::mailbox::MailboxSender;
1476 use crate::mailbox::PortLocation;
1477 use crate::mailbox::monitored_return_handle;
1478 use crate::port::Port;
1479 use crate::proc::Proc;
1480 use crate::testing::ids::test_actor_id;
1481 use crate::testing::pingpong::PingPongActor;
1482 use crate::testing::pingpong::PingPongMessage;
1483
1484 #[derive(Clone)]
1485 struct RecordingSender(mpsc::UnboundedSender<MessageEnvelope>);
1486
1487 #[async_trait]
1488 impl MailboxSender for RecordingSender {
1489 fn post_unchecked(
1490 &self,
1491 envelope: MessageEnvelope,
1492 _return_handle: PortHandle<Undeliverable<MessageEnvelope>>,
1493 ) {
1494 self.0
1495 .send(envelope)
1496 .expect("recording sender should be open");
1497 }
1498 }
1499
1500 trait GatewayAttachExt {
1512 fn attach(&self, peer: &Gateway) -> AttachGuard;
1513 }
1514
1515 impl GatewayAttachExt for Gateway {
1516 fn attach(&self, peer: &Gateway) -> AttachGuard {
1517 let pre_self_default = self.default_location();
1521 let pre_peer_default = peer.default_location();
1522
1523 let self_serve = self
1526 .serve(ChannelAddr::any(ChannelTransport::Local))
1527 .expect("serve self on local channel");
1528 let peer_serve = peer
1529 .serve(ChannelAddr::any(ChannelTransport::Local))
1530 .expect("serve peer on local channel");
1531 let self_addr = self.default_location().addr().clone();
1532 let peer_addr = peer.default_location().addr().clone();
1533
1534 let self_via_guard = peer
1537 .attach_peer(
1538 self.inner.uid.clone(),
1539 MailboxClient::dial(self_addr)
1540 .expect("dial self")
1541 .into_boxed(),
1542 )
1543 .expect("peer has no via entry for this gateway's uid");
1544 let peer_via_guard = self
1545 .attach_peer(
1546 peer.inner.uid.clone(),
1547 MailboxClient::dial(peer_addr)
1548 .expect("dial peer")
1549 .into_boxed(),
1550 )
1551 .expect("self has no via entry for the peer gateway's uid");
1552
1553 self.inner.routing.write().unwrap().default_location =
1555 Location::Via(self.inner.uid.clone(), Box::new(pre_peer_default.clone()));
1556 peer.inner.routing.write().unwrap().default_location =
1557 Location::Via(peer.inner.uid.clone(), Box::new(pre_self_default.clone()));
1558
1559 AttachGuard {
1560 self_gateway: Arc::downgrade(&self.inner),
1561 peer_gateway: Arc::downgrade(&peer.inner),
1562 prev_self_default: Some(pre_self_default),
1563 prev_peer_default: Some(pre_peer_default),
1564 _self_via_guard: self_via_guard,
1565 _peer_via_guard: peer_via_guard,
1566 _self_serve: self_serve,
1567 _peer_serve: peer_serve,
1568 }
1569 }
1570 }
1571
1572 struct AttachGuard {
1577 self_gateway: Weak<GatewayState>,
1578 peer_gateway: Weak<GatewayState>,
1579 prev_self_default: Option<Location>,
1580 prev_peer_default: Option<Location>,
1581 _self_via_guard: PeerAttachGuard,
1582 _peer_via_guard: PeerAttachGuard,
1583 _self_serve: GatewayServeHandle,
1584 _peer_serve: GatewayServeHandle,
1585 }
1586
1587 impl Drop for AttachGuard {
1588 fn drop(&mut self) {
1589 if let Some(state) = self.self_gateway.upgrade()
1590 && let Some(loc) = self.prev_self_default.take()
1591 {
1592 state.routing.write().unwrap().default_location = loc;
1593 }
1594 if let Some(state) = self.peer_gateway.upgrade()
1595 && let Some(loc) = self.prev_peer_default.take()
1596 {
1597 state.routing.write().unwrap().default_location = loc;
1598 }
1599 }
1601 }
1602
1603 #[tokio::test]
1610 async fn test_gateway_post_demuxes_by_proc_id() {
1611 let (tx, mut forwarded_rx) = mpsc::unbounded_channel();
1612 let gateway = Gateway::configured(
1613 channel::reserve_local_addr().into(),
1614 BoxedMailboxSender::new(RecordingSender(tx)),
1615 );
1616
1617 let alpha = Proc::builder()
1618 .proc_id(ProcId::instance(Label::strip("alpha")))
1619 .shared_gateway(gateway.clone())
1620 .build()
1621 .unwrap();
1622 let beta = Proc::builder()
1623 .proc_id(ProcId::instance(Label::strip("beta")))
1624 .shared_gateway(gateway.clone())
1625 .build()
1626 .unwrap();
1627
1628 let alpha_client = alpha.client("client");
1629 let (alpha_port, mut alpha_rx) = alpha_client.bind_handler_port::<u64>();
1630 let PortLocation::Bound(alpha_dest) = alpha_port.location() else {
1631 panic!("alpha handler port must be bound");
1632 };
1633
1634 let beta_client = beta.client("client");
1635 let (beta_port, mut beta_rx) = beta_client.bind_handler_port::<u64>();
1636 let PortLocation::Bound(beta_dest) = beta_port.location() else {
1637 panic!("beta handler port must be bound");
1638 };
1639
1640 let sender = test_actor_id("test", "sender");
1641
1642 gateway.post(
1643 MessageEnvelope::serialize(sender.clone(), alpha_dest.clone(), &111u64, Flattrs::new())
1644 .unwrap(),
1645 monitored_return_handle(),
1646 );
1647 let received = time::timeout(Duration::from_secs(5), alpha_rx.recv())
1648 .await
1649 .expect("alpha_rx timed out")
1650 .expect("alpha_rx closed");
1651 assert_eq!(received, 111);
1652 assert!(matches!(
1653 forwarded_rx.try_recv(),
1654 Err(mpsc::error::TryRecvError::Empty)
1655 ));
1656
1657 gateway.post(
1658 MessageEnvelope::serialize(sender.clone(), beta_dest.clone(), &222u64, Flattrs::new())
1659 .unwrap(),
1660 monitored_return_handle(),
1661 );
1662 let received = time::timeout(Duration::from_secs(5), beta_rx.recv())
1663 .await
1664 .expect("beta_rx timed out")
1665 .expect("beta_rx closed");
1666 assert_eq!(received, 222);
1667 assert!(matches!(
1668 forwarded_rx.try_recv(),
1669 Err(mpsc::error::TryRecvError::Empty)
1670 ));
1671
1672 let stranger_proc = ProcAddr::instance(ChannelAddr::Local(9999), "stranger");
1673 let stranger_dest = stranger_proc
1674 .actor_addr("ghost")
1675 .port_addr(Port::from(0u64));
1676 gateway.post(
1677 MessageEnvelope::serialize(sender, stranger_dest.clone(), &333u64, Flattrs::new())
1678 .unwrap()
1679 .set_ttl(3),
1680 monitored_return_handle(),
1681 );
1682 let forwarded = time::timeout(Duration::from_secs(5), forwarded_rx.recv())
1683 .await
1684 .expect("forwarded_rx timed out")
1685 .expect("forwarded_rx closed");
1686 assert_eq!(forwarded.dest(), &stranger_dest);
1687 assert_eq!(forwarded.ttl(), 1);
1690 assert!(
1691 time::timeout(Duration::from_millis(50), alpha_rx.recv())
1692 .await
1693 .is_err(),
1694 "alpha_rx received a message after stranger post",
1695 );
1696 assert!(
1697 time::timeout(Duration::from_millis(50), beta_rx.recv())
1698 .await
1699 .is_err(),
1700 "beta_rx received a message after stranger post",
1701 );
1702 }
1703
1704 #[tokio::test]
1709 async fn test_gateway_foreign_via_forwards_not_local() {
1710 #[derive(Clone)]
1711 struct CountingSender(Arc<AtomicUsize>);
1712
1713 #[async_trait]
1714 impl MailboxSender for CountingSender {
1715 fn post_unchecked(
1716 &self,
1717 _envelope: MessageEnvelope,
1718 _return_handle: PortHandle<Undeliverable<MessageEnvelope>>,
1719 ) {
1720 self.0.fetch_add(1, Ordering::SeqCst);
1721 }
1722 }
1723
1724 let forwarded = Arc::new(AtomicUsize::new(0));
1725 let gateway = Gateway::configured(
1726 channel::reserve_local_addr().into(),
1727 BoxedMailboxSender::new(CountingSender(forwarded.clone())),
1728 );
1729
1730 let alpha = Proc::builder()
1732 .proc_id(ProcId::instance(Label::strip("alpha")))
1733 .shared_gateway(gateway.clone())
1734 .build()
1735 .unwrap();
1736
1737 let foreign_uid = Uid::Instance(0xfeed, Some(Label::strip("foreign")));
1740 assert_ne!(&foreign_uid, gateway.uid());
1741 let dest = ProcAddr::new(
1742 alpha.proc_id().clone(),
1743 Location::from(ChannelAddr::Local(7777)).with_via(foreign_uid),
1744 )
1745 .actor_addr("ghost")
1746 .port_addr(Port::from(0u64));
1747
1748 gateway.post(
1749 MessageEnvelope::serialize(
1750 test_actor_id("test", "sender"),
1751 dest,
1752 &7u64,
1753 Flattrs::new(),
1754 )
1755 .unwrap(),
1756 monitored_return_handle(),
1757 );
1758
1759 assert_eq!(
1761 forwarded.load(Ordering::SeqCst),
1762 1,
1763 "a foreign via must be forwarded, not delivered locally",
1764 );
1765 }
1766
1767 #[tokio::test]
1770 async fn test_gateway_self_via_routes_remaining_via_before_local_delivery() {
1771 #[derive(Clone)]
1772 struct CountingSender(Arc<AtomicUsize>);
1773
1774 #[async_trait]
1775 impl MailboxSender for CountingSender {
1776 fn post_unchecked(
1777 &self,
1778 _envelope: MessageEnvelope,
1779 _return_handle: PortHandle<Undeliverable<MessageEnvelope>>,
1780 ) {
1781 self.0.fetch_add(1, Ordering::SeqCst);
1782 }
1783 }
1784
1785 let forwarded = Arc::new(AtomicUsize::new(0));
1786 let gateway = Gateway::configured(
1787 channel::reserve_local_addr().into(),
1788 BoxedMailboxSender::new(CountingSender(forwarded.clone())),
1789 );
1790 let proc = Proc::builder()
1791 .proc_id(ProcId::instance(Label::strip("alpha")))
1792 .shared_gateway(gateway.clone())
1793 .build()
1794 .unwrap();
1795
1796 let foreign_uid = Uid::Instance(0xfeed, Some(Label::strip("foreign")));
1797 let dest = ProcAddr::new(
1798 proc.proc_id().clone(),
1799 proc.default_location()
1800 .with_via(foreign_uid)
1801 .with_via(gateway.uid().clone()),
1802 )
1803 .actor_addr("ghost")
1804 .port_addr(Port::from(0u64));
1805
1806 gateway.post(
1807 MessageEnvelope::serialize(
1808 test_actor_id("test", "sender"),
1809 dest,
1810 &7u64,
1811 Flattrs::new(),
1812 )
1813 .unwrap(),
1814 monitored_return_handle(),
1815 );
1816
1817 assert_eq!(
1818 forwarded.load(Ordering::SeqCst),
1819 1,
1820 "remaining via routes must not deliver locally after peeling this gateway's hop",
1821 );
1822 }
1823
1824 #[tokio::test]
1827 async fn test_gateway_self_via_peels_before_local_delivery() {
1828 #[derive(Clone)]
1829 struct CapturingSender(Arc<std::sync::Mutex<Option<(PortAddr, PortAddr)>>>);
1830
1831 #[async_trait]
1832 impl MailboxSender for CapturingSender {
1833 fn post_unchecked(
1834 &self,
1835 envelope: MessageEnvelope,
1836 _return_handle: PortHandle<Undeliverable<MessageEnvelope>>,
1837 ) {
1838 *self.0.lock().unwrap() =
1839 Some((envelope.dest().clone(), envelope.next_hop().clone()));
1840 }
1841 }
1842
1843 let gateway = Gateway::isolated();
1844 let proc = Proc::builder()
1845 .proc_id(ProcId::instance(Label::strip("alpha")))
1846 .shared_gateway(gateway.clone())
1847 .build()
1848 .unwrap();
1849
1850 let captured = Arc::new(std::sync::Mutex::new(None));
1851 let actor_addr = proc.proc_addr().actor_addr("capture");
1852 assert!(
1853 proc.muxer()
1854 .bind(actor_addr.id().clone(), CapturingSender(captured.clone()))
1855 );
1856
1857 let inner_dest = actor_addr.port_addr(Port::from(7u64));
1858 let via_dest = PortAddr::new(
1859 inner_dest.id().clone(),
1860 inner_dest
1861 .location()
1862 .clone()
1863 .with_via(gateway.uid().clone()),
1864 );
1865 gateway.post(
1866 MessageEnvelope::serialize(
1867 test_actor_id("test", "sender"),
1868 via_dest.clone(),
1869 &7u64,
1870 Flattrs::new(),
1871 )
1872 .unwrap(),
1873 monitored_return_handle(),
1874 );
1875
1876 assert_eq!(
1877 *captured.lock().unwrap(),
1878 Some((via_dest, inner_dest)),
1879 "delivery preserves the canonical destination and peels only the next hop",
1880 );
1881 }
1882
1883 #[tokio::test]
1888 async fn test_gateway_self_via_unknown_proc_is_undeliverable() {
1889 let gateway = Gateway::isolated();
1890
1891 let scratch = Proc::isolated();
1893 let scratch_client = scratch.client("return");
1894 let (return_handle, mut return_rx) =
1895 scratch_client.open_port::<Undeliverable<MessageEnvelope>>();
1896
1897 let peeled_dest = ProcAddr::new(
1900 ProcId::instance(Label::strip("stranger")),
1901 Location::from(ChannelAddr::Local(4321)),
1902 )
1903 .actor_addr("ghost")
1904 .port_addr(Port::from(0u64));
1905 let dest = PortAddr::new(
1906 peeled_dest.id().clone(),
1907 peeled_dest
1908 .location()
1909 .clone()
1910 .with_via(gateway.uid().clone()),
1911 );
1912 let envelope = MessageEnvelope::serialize(
1913 test_actor_id("test", "sender"),
1914 dest.clone(),
1915 &9u64,
1916 Flattrs::new(),
1917 )
1918 .unwrap()
1919 .set_ttl(3);
1920
1921 gateway.post(envelope, return_handle);
1922
1923 let Undeliverable::Returned(returned) =
1924 time::timeout(Duration::from_secs(5), return_rx.recv())
1925 .await
1926 .expect("return_rx timed out")
1927 .expect("return_rx closed")
1928 else {
1929 panic!("expected returned envelope");
1930 };
1931 assert_eq!(returned.dest(), &dest);
1932 assert_eq!(returned.next_hop(), &peeled_dest);
1933 assert!(
1934 returned
1935 .root_delivery_failure()
1936 .is_some_and(|failure| matches!(
1937 &failure.kind,
1938 DeliveryFailureKind::Undeliverable(UndeliverableReason::Transport(_))
1939 )),
1940 "expected NoRoute transport bounce, got {:?}",
1941 returned.delivery_failures(),
1942 );
1943 assert_eq!(returned.ttl(), 2);
1947 }
1948
1949 #[tokio::test]
1954 async fn test_ping_pong_across_shared_gateway() {
1955 let gateway = Gateway::isolated();
1956
1957 let alpha = Proc::builder()
1958 .proc_id(ProcId::instance(Label::strip("alpha")))
1959 .shared_gateway(gateway.clone())
1960 .build()
1961 .unwrap();
1962 let beta = Proc::builder()
1963 .proc_id(ProcId::instance(Label::strip("beta")))
1964 .shared_gateway(gateway.clone())
1965 .build()
1966 .unwrap();
1967
1968 let client = alpha.client("client");
1969 let (undeliverable_msg_tx, mut undeliverable_rx) =
1970 client.open_port::<Undeliverable<MessageEnvelope>>();
1971
1972 let ping_actor = PingPongActor::new(Some(undeliverable_msg_tx.bind()), None, None);
1973 let pong_actor = PingPongActor::new(Some(undeliverable_msg_tx.bind()), None, None);
1974 let ping_handle = alpha.spawn_with_label::<PingPongActor>("ping", ping_actor);
1975 let pong_handle = beta.spawn_with_label::<PingPongActor>("pong", pong_actor);
1976
1977 let (local_port, local_receiver) = client.open_once_port();
1978
1979 ping_handle.post(
1980 &client,
1981 PingPongMessage(10, pong_handle.bind(), local_port.bind()),
1982 );
1983
1984 let received = time::timeout(Duration::from_secs(5), local_receiver.recv())
1985 .await
1986 .expect("local_receiver timed out")
1987 .expect("local_receiver closed");
1988 assert!(received);
1989
1990 assert!(
1991 time::timeout(Duration::from_millis(50), undeliverable_rx.recv())
1992 .await
1993 .is_err(),
1994 "unexpected undeliverable during cross-proc ping-pong",
1995 );
1996 }
1997
1998 #[test]
2004 #[should_panic(expected = "gateway already has a proc attached with id")]
2005 fn test_gateway_attach_proc_panics_on_duplicate_live_proc() {
2006 let gateway = Gateway::isolated();
2007 let proc_id = ProcId::instance(Label::strip("alpha"));
2008
2009 let _first = Proc::builder()
2014 .proc_id(proc_id.clone())
2015 .shared_gateway(gateway.clone())
2016 .build()
2017 .unwrap();
2018
2019 let _second = Proc::builder()
2020 .proc_id(proc_id)
2021 .shared_gateway(gateway.clone())
2022 .build()
2023 .unwrap();
2024 }
2025
2026 #[tokio::test]
2033 async fn test_gateway_flush_propagates_to_attached_procs() {
2034 #[derive(Clone)]
2035 struct FlushCountingSender(Arc<AtomicUsize>);
2036
2037 #[async_trait]
2038 impl MailboxSender for FlushCountingSender {
2039 fn post_unchecked(
2040 &self,
2041 _envelope: MessageEnvelope,
2042 _return_handle: PortHandle<Undeliverable<MessageEnvelope>>,
2043 ) {
2044 }
2046
2047 async fn flush(&self) -> Result<(), anyhow::Error> {
2048 self.0.fetch_add(1, Ordering::SeqCst);
2049 Ok(())
2050 }
2051 }
2052
2053 let alpha_flushed = Arc::new(AtomicUsize::new(0));
2054 let beta_flushed = Arc::new(AtomicUsize::new(0));
2055 let forwarder_flushed = Arc::new(AtomicUsize::new(0));
2056
2057 let gateway = Gateway::configured(
2058 channel::reserve_local_addr().into(),
2059 BoxedMailboxSender::new(FlushCountingSender(forwarder_flushed.clone())),
2060 );
2061
2062 let alpha = Proc::builder()
2063 .proc_id(ProcId::instance(Label::strip("alpha")))
2064 .shared_gateway(gateway.clone())
2065 .build()
2066 .unwrap();
2067 let beta = Proc::builder()
2068 .proc_id(ProcId::instance(Label::strip("beta")))
2069 .shared_gateway(gateway.clone())
2070 .build()
2071 .unwrap();
2072
2073 let alpha_probe = alpha.proc_addr().actor_addr("alpha_probe").id().clone();
2077 let beta_probe = beta.proc_addr().actor_addr("beta_probe").id().clone();
2078 assert!(
2079 alpha
2080 .muxer()
2081 .bind(alpha_probe, FlushCountingSender(alpha_flushed.clone()))
2082 );
2083 assert!(
2084 beta.muxer()
2085 .bind(beta_probe, FlushCountingSender(beta_flushed.clone()))
2086 );
2087
2088 assert_eq!(gateway.inner.procs.read().unwrap().len(), 2);
2090
2091 gateway.flush().await.unwrap();
2092
2093 assert_eq!(alpha_flushed.load(Ordering::SeqCst), 1);
2094 assert_eq!(beta_flushed.load(Ordering::SeqCst), 1);
2095 assert_eq!(forwarder_flushed.load(Ordering::SeqCst), 1);
2096 }
2097
2098 #[async_timed_test(timeout_secs = 10)]
2104 async fn test_gateway_flush_concurrent_with_attach_and_drop() {
2105 #[derive(Clone)]
2106 struct NoopSender;
2107
2108 #[async_trait]
2109 impl MailboxSender for NoopSender {
2110 fn post_unchecked(
2111 &self,
2112 _envelope: MessageEnvelope,
2113 _return_handle: PortHandle<Undeliverable<MessageEnvelope>>,
2114 ) {
2115 }
2117
2118 async fn flush(&self) -> Result<(), anyhow::Error> {
2119 Ok(())
2120 }
2121 }
2122
2123 let gateway = Gateway::configured(
2124 channel::reserve_local_addr().into(),
2125 BoxedMailboxSender::new(NoopSender),
2126 );
2127
2128 let barrier = Arc::new(tokio::sync::Barrier::new(2));
2129
2130 let flushes = {
2131 let gateway = gateway.clone();
2132 let barrier = barrier.clone();
2133 tokio::spawn(async move {
2134 barrier.wait().await;
2135 for _ in 0..100 {
2136 gateway.flush().await.unwrap();
2137 tokio::task::yield_now().await;
2138 }
2139 })
2140 };
2141
2142 let attach_drop = {
2143 let gateway = gateway.clone();
2144 let barrier = barrier.clone();
2145 tokio::spawn(async move {
2146 barrier.wait().await;
2147 for i in 0..100 {
2148 let proc = Proc::builder()
2149 .proc_id(ProcId::instance(Label::strip(&format!("p{i}"))))
2150 .shared_gateway(gateway.clone())
2151 .build()
2152 .unwrap();
2153 tokio::task::yield_now().await;
2156 drop(proc);
2157 }
2158 })
2159 };
2160
2161 flushes.await.unwrap();
2162 attach_drop.await.unwrap();
2163
2164 gateway.flush().await.unwrap();
2166
2167 assert_eq!(
2172 gateway
2173 .inner
2174 .procs
2175 .read()
2176 .unwrap()
2177 .values()
2178 .filter_map(WeakProc::upgrade)
2179 .count(),
2180 0,
2181 "no procs should still be live after attach_drop task completes",
2182 );
2183 }
2184
2185 #[tokio::test]
2194 async fn test_weak_gateway_bounces_broken_link_after_drop() {
2195 let gateway = Gateway::isolated();
2196 let weak = Arc::downgrade(&gateway.inner);
2197 drop(gateway);
2198
2199 let scratch = Proc::isolated();
2201 let scratch_client = scratch.client("return");
2202 let (return_handle, mut return_rx) =
2203 scratch_client.open_port::<Undeliverable<MessageEnvelope>>();
2204
2205 let dest_proc = ProcAddr::instance(ChannelAddr::Local(1234), "stranger");
2209 let dest = dest_proc.actor_addr("ghost").port_addr(Port::from(0u64));
2210 let envelope = MessageEnvelope::serialize(
2211 test_actor_id("test", "sender"),
2212 dest.clone(),
2213 &42u64,
2214 Flattrs::new(),
2215 )
2216 .unwrap();
2217
2218 weak.post(envelope, return_handle);
2221
2222 let Undeliverable::Returned(envelope) =
2223 time::timeout(Duration::from_secs(5), return_rx.recv())
2224 .await
2225 .expect("return_rx timed out")
2226 .expect("return_rx closed")
2227 else {
2228 panic!("expected returned envelope");
2229 };
2230 assert_eq!(envelope.dest(), &dest);
2231 assert!(
2232 envelope
2233 .root_delivery_failure()
2234 .is_some_and(|failure| matches!(
2235 &failure.kind,
2236 DeliveryFailureKind::Undeliverable(UndeliverableReason::Transport(_))
2237 )),
2238 "expected structured transport bounce, got {:?}",
2239 envelope.delivery_failures(),
2240 );
2241 }
2242
2243 #[tokio::test]
2248 async fn test_gateway_attach_proc_after_proc_drop() {
2249 let gateway = Gateway::isolated();
2250 let proc_id = ProcId::instance(Label::strip("alpha"));
2251
2252 let first = Proc::builder()
2253 .proc_id(proc_id.clone())
2254 .shared_gateway(gateway.clone())
2255 .build()
2256 .unwrap();
2257 drop(first);
2258
2259 assert_eq!(gateway.inner.procs.read().unwrap().len(), 0);
2261
2262 let second = Proc::builder()
2265 .proc_id(proc_id.clone())
2266 .shared_gateway(gateway.clone())
2267 .build()
2268 .unwrap();
2269 assert_eq!(gateway.inner.procs.read().unwrap().len(), 1);
2270
2271 let client = second.client("client");
2273 let (port, mut rx) = client.bind_handler_port::<u64>();
2274 let dest = port.bind().port_addr().clone();
2275
2276 gateway.post(
2277 MessageEnvelope::serialize(
2278 test_actor_id("test", "sender"),
2279 dest,
2280 &42u64,
2281 Flattrs::new(),
2282 )
2283 .unwrap(),
2284 monitored_return_handle(),
2285 );
2286
2287 let received = time::timeout(Duration::from_secs(5), rx.recv())
2288 .await
2289 .expect("rx timed out")
2290 .expect("rx closed");
2291 assert_eq!(received, 42);
2292 }
2293
2294 #[tokio::test]
2300 async fn test_gateway_serve_stop_unwinds_in_any_order() {
2301 let gateway = Gateway::isolated();
2302 let fallback = gateway.default_location();
2303
2304 let mut s1 = Gateway::serve(&gateway, ChannelAddr::any(ChannelTransport::Local)).unwrap();
2305 let loc1 = gateway.default_location();
2306 let mut s2 = Gateway::serve(&gateway, ChannelAddr::any(ChannelTransport::Local)).unwrap();
2307 let loc2 = gateway.default_location();
2308 let mut s3 = Gateway::serve(&gateway, ChannelAddr::any(ChannelTransport::Local)).unwrap();
2309 let loc3 = gateway.default_location();
2310
2311 assert_eq!(loc1, fallback);
2315 assert_ne!(loc1, loc2);
2316 assert_ne!(loc2, loc3);
2317 assert_ne!(loc1, loc3);
2318
2319 s2.stop("test");
2323 s2.join().await.unwrap();
2324 assert_eq!(gateway.default_location(), loc3);
2325
2326 s3.stop("test");
2328 s3.join().await.unwrap();
2329 assert_eq!(gateway.default_location(), loc1);
2330
2331 s1.stop("test");
2334 s1.join().await.unwrap();
2335 assert_eq!(gateway.default_location(), fallback);
2336 }
2337
2338 #[tokio::test]
2339 async fn test_gateway_first_local_serve_uses_fallback_after_nonlocal_serve() {
2340 let gateway = Gateway::isolated();
2341 let fallback = gateway.default_location();
2342
2343 let mut unix = Gateway::serve(&gateway, ChannelAddr::any(ChannelTransport::Unix)).unwrap();
2344 let unix_location = gateway.default_location();
2345 assert_ne!(unix_location, fallback);
2346
2347 let mut local =
2348 Gateway::serve(&gateway, ChannelAddr::any(ChannelTransport::Local)).unwrap();
2349 assert_eq!(gateway.default_location(), fallback);
2350
2351 local.stop("test");
2352 local.join().await.unwrap();
2353 assert_eq!(gateway.default_location(), unix_location);
2354
2355 unix.stop("test");
2356 unix.join().await.unwrap();
2357 assert_eq!(gateway.default_location(), fallback);
2358 }
2359
2360 #[tokio::test]
2368 async fn test_gateway_serve_via_peer() {
2369 let server_gw = Gateway::new();
2373 let server_addr = ChannelAddr::any(ChannelTransport::Unix);
2374 let mut accept_handle = server_gw.serve_duplex(server_addr).unwrap();
2375 let server_addr = server_gw.default_location().addr().clone();
2376
2377 let server_proc = Proc::builder()
2378 .proc_id(ProcId::instance(Label::strip("echo")))
2379 .shared_gateway(server_gw.clone())
2380 .build()
2381 .unwrap();
2382 let server_inst = server_proc.client("recv");
2383 let (server_port, mut server_rx) = server_inst.bind_handler_port::<u64>();
2384 let PortLocation::Bound(server_dest) = server_port.location() else {
2385 panic!("server port must be bound");
2386 };
2387
2388 let client_gw = Gateway::new();
2392 let pre_default = client_gw.default_location();
2393 let serve_via = client_gw.serve_via(server_addr.clone()).await.unwrap();
2394 let post_default = client_gw.default_location();
2395 assert_ne!(
2396 pre_default, post_default,
2397 "serve_via must update default_location"
2398 );
2399 let (via_uid, inner) = post_default.as_via().expect("default must be via");
2400 assert_eq!(via_uid, client_gw.uid());
2401 assert_eq!(inner.addr(), &server_addr);
2402
2403 let sender = test_actor_id("client", "sender");
2408 client_gw.post(
2409 MessageEnvelope::serialize(sender, server_dest.clone(), &7u64, Flattrs::new()).unwrap(),
2410 monitored_return_handle(),
2411 );
2412 let received = time::timeout(Duration::from_secs(5), server_rx.recv())
2413 .await
2414 .expect("server_rx timed out")
2415 .expect("server_rx closed");
2416 assert_eq!(received, 7);
2417
2418 drop(serve_via);
2420 assert_eq!(client_gw.default_location(), pre_default);
2421
2422 accept_handle.stop("test cleanup");
2424 }
2425
2426 #[tokio::test]
2427 async fn test_gateway_serve_via_sessions_are_additive() {
2428 let server1_gw = Gateway::new();
2429 let mut accept1_handle = server1_gw
2430 .serve_duplex(ChannelAddr::any(ChannelTransport::Unix))
2431 .unwrap();
2432 let server1_addr = server1_gw.default_location().addr().clone();
2433 let server2_gw = Gateway::new();
2434 let mut accept2_handle = server2_gw
2435 .serve_duplex(ChannelAddr::any(ChannelTransport::Unix))
2436 .unwrap();
2437 let server2_addr = server2_gw.default_location().addr().clone();
2438
2439 let client_gw = Gateway::new();
2440 let pre_default = client_gw.default_location();
2441 let mut serve_via1 = client_gw.serve_via(server1_addr.clone()).await.unwrap();
2442 let via1_default = client_gw.default_location();
2443 assert_eq!(via1_default.addr(), &server1_addr);
2444
2445 let client_proc = Proc::legacy_service_pseudo_singleton_on_gateway(client_gw.clone());
2446 let client = client_proc.client("recv");
2447 let (client_port, mut client_rx) = client.bind_handler_port::<u64>();
2448 let PortLocation::Bound(old_client_dest) = client_port.location() else {
2449 panic!("client port must be bound");
2450 };
2451
2452 let mut serve_via2 = client_gw.serve_via(server2_addr.clone()).await.unwrap();
2453 let via2_default = client_gw.default_location();
2454 assert_eq!(via2_default.addr(), &server2_addr);
2455 assert_ne!(via1_default, via2_default);
2456
2457 server1_gw.post(
2458 MessageEnvelope::serialize(
2459 test_actor_id("server1", "sender"),
2460 old_client_dest,
2461 &11u64,
2462 Flattrs::new(),
2463 )
2464 .unwrap(),
2465 monitored_return_handle(),
2466 );
2467 let received = time::timeout(Duration::from_secs(5), client_rx.recv())
2468 .await
2469 .expect("client_rx timed out")
2470 .expect("client_rx closed");
2471 assert_eq!(received, 11);
2472
2473 serve_via2.stop("test cleanup");
2474 serve_via2.join().await.unwrap();
2475 assert_eq!(client_gw.default_location(), via1_default);
2476
2477 serve_via1.stop("test cleanup");
2478 serve_via1.join().await.unwrap();
2479 assert_eq!(client_gw.default_location(), pre_default);
2480
2481 accept1_handle.stop("test cleanup");
2482 accept1_handle.join().await.unwrap();
2483 accept2_handle.stop("test cleanup");
2484 accept2_handle.join().await.unwrap();
2485 }
2486
2487 #[tokio::test]
2488 async fn test_gateway_serve_via_duplicate_peer_reports_rejection() {
2489 let server_gw = Gateway::new();
2490 let mut accept_handle = server_gw
2491 .serve_duplex(ChannelAddr::any(ChannelTransport::Unix))
2492 .unwrap();
2493 let server_addr = server_gw.default_location().addr().clone();
2494
2495 let client_gw = Gateway::new();
2496 let mut serve_via = client_gw.serve_via(server_addr.clone()).await.unwrap();
2497 let via_default = client_gw.default_location();
2498
2499 let err = client_gw.serve_via(server_addr).await.unwrap_err();
2500 assert!(
2501 err.to_string()
2502 .contains("gateway already has a via peer with uid"),
2503 "unexpected error: {err:#}"
2504 );
2505 assert_eq!(client_gw.default_location(), via_default);
2506
2507 serve_via.stop("test cleanup");
2508 serve_via.join().await.unwrap();
2509 accept_handle.stop("test cleanup");
2510 accept_handle.join().await.unwrap();
2511 }
2512
2513 #[tokio::test]
2514 async fn test_gateway_serve_with_listener_takes_precedence_after_via() {
2515 let server_gw = Gateway::new();
2516 let mut accept_handle = server_gw
2517 .serve_duplex(ChannelAddr::any(ChannelTransport::Unix))
2518 .unwrap();
2519 let server_addr = server_gw.default_location().addr().clone();
2520 let _server_proc = Proc::legacy_service_pseudo_singleton_on_gateway(server_gw.clone());
2521
2522 let client_gw = Gateway::new();
2523 let pre_default = client_gw.default_location();
2524 let mut serve_via = client_gw.serve_via(server_addr).await.unwrap();
2525 let via_default = client_gw.default_location();
2526 assert!(via_default.as_via().is_some());
2527
2528 let mut frontend = client_gw
2529 .serve_with_listener(ChannelAddr::any(ChannelTransport::Unix), None)
2530 .unwrap();
2531 let frontend_location = client_gw.default_location();
2532 assert!(matches!(frontend_location.addr(), ChannelAddr::Unix(_)));
2533 assert_ne!(frontend_location, via_default);
2534
2535 frontend.stop("test cleanup");
2536 frontend.join().await.unwrap();
2537 assert_eq!(client_gw.default_location(), via_default);
2538
2539 serve_via.stop("test cleanup");
2540 serve_via.join().await.unwrap();
2541 assert_eq!(client_gw.default_location(), pre_default);
2542
2543 accept_handle.stop("test cleanup");
2544 accept_handle.join().await.unwrap();
2545 }
2546
2547 #[tokio::test]
2548 async fn test_set_default_location_updates_fallback_while_serving() {
2549 let gateway = Gateway::new();
2550 let mut serve =
2551 Gateway::serve(&gateway, ChannelAddr::any(ChannelTransport::Local)).unwrap();
2552 let served_location = gateway.default_location();
2553 let fallback_location = Location::from(ChannelAddr::Local(9876));
2554
2555 gateway.set_default_location(fallback_location.clone());
2556 assert_eq!(gateway.default_location(), served_location);
2557
2558 serve.stop("test cleanup");
2559 serve.join().await.unwrap();
2560 assert_eq!(gateway.default_location(), fallback_location);
2561 }
2562
2563 #[tokio::test]
2564 async fn test_gateway_serve_via_delivers_peeled_legacy_local_proc_destination() {
2565 let server_gw = Gateway::new();
2566 let mut accept_handle = server_gw
2567 .serve_duplex(ChannelAddr::any(ChannelTransport::Unix))
2568 .unwrap();
2569 let server_addr = server_gw.default_location().addr().clone();
2570
2571 let client_gw = Gateway::new();
2572 let mut serve_via = client_gw.serve_via(server_addr).await.unwrap();
2573 let client_proc = Proc::legacy_local_pseudo_singleton_on_gateway(client_gw.clone());
2574 let client = client_proc.client("recv");
2575 let (port, mut rx) = client.bind_handler_port::<u64>();
2576 let PortLocation::Bound(via_dest) = port.location() else {
2577 panic!("client port must be bound");
2578 };
2579
2580 server_gw.post(
2581 MessageEnvelope::serialize(
2582 test_actor_id("server", "sender"),
2583 via_dest,
2584 &7u64,
2585 Flattrs::new(),
2586 )
2587 .unwrap(),
2588 monitored_return_handle(),
2589 );
2590
2591 let received = time::timeout(Duration::from_secs(5), rx.recv())
2592 .await
2593 .expect("rx timed out")
2594 .expect("rx closed");
2595 assert_eq!(received, 7);
2596
2597 serve_via.stop("test cleanup");
2598 serve_via.join().await.unwrap();
2599 accept_handle.stop("test cleanup");
2600 accept_handle.join().await.unwrap();
2601 }
2602
2603 #[tokio::test]
2604 async fn test_gateway_serve_via_does_not_shadow_peer_legacy_proc() {
2605 let server_gw = Gateway::new();
2606 let mut accept_handle = server_gw
2607 .serve_duplex(ChannelAddr::any(ChannelTransport::Unix))
2608 .unwrap();
2609 let server_addr = server_gw.default_location().addr().clone();
2610 let server_proc = Proc::legacy_service_pseudo_singleton_on_gateway(server_gw.clone());
2611 let server = server_proc.client("recv");
2612 let (server_port, mut server_rx) = server.bind_handler_port::<u64>();
2613 let PortLocation::Bound(server_dest) = server_port.location() else {
2614 panic!("server port must be bound");
2615 };
2616
2617 let client_gw = Gateway::new();
2618 let mut serve_via = client_gw.serve_via(server_addr).await.unwrap();
2619 let client_proc = Proc::legacy_service_pseudo_singleton_on_gateway(client_gw.clone());
2620 let (client_shadow_tx, mut client_shadow_rx) = mpsc::unbounded_channel();
2621 assert!(
2622 client_proc.muxer().bind(
2623 server_dest.actor_addr().id().clone(),
2624 RecordingSender(client_shadow_tx),
2625 ),
2626 "client shadow handler should bind"
2627 );
2628
2629 client_gw.post(
2630 MessageEnvelope::serialize(
2631 test_actor_id("client", "sender"),
2632 server_dest.clone(),
2633 &7u64,
2634 Flattrs::new(),
2635 )
2636 .unwrap(),
2637 monitored_return_handle(),
2638 );
2639
2640 let received = time::timeout(Duration::from_secs(5), server_rx.recv())
2641 .await
2642 .expect("server_rx timed out")
2643 .expect("server_rx closed");
2644 assert_eq!(received, 7);
2645 time::timeout(Duration::from_millis(200), client_shadow_rx.recv())
2646 .await
2647 .expect_err("peer legacy proc location must not deliver to the attached client proc");
2648
2649 serve_via.stop("test cleanup");
2650 serve_via.join().await.unwrap();
2651 accept_handle.stop("test cleanup");
2652 accept_handle.join().await.unwrap();
2653 }
2654
2655 #[tokio::test]
2656 async fn test_gateway_serve_via_relay_delivers_to_client_legacy_local_proc() {
2657 let relay_gw = Gateway::new();
2658 let mut accept_handle = relay_gw
2659 .serve_duplex(ChannelAddr::any(ChannelTransport::Unix))
2660 .unwrap();
2661 let relay_addr = relay_gw.default_location().addr().clone();
2662
2663 let client_gw = Gateway::new();
2664 let mut serve_via = client_gw.serve_via(relay_addr.clone()).await.unwrap();
2665 let client_proc = Proc::legacy_local_pseudo_singleton_on_gateway(client_gw.clone());
2666 let client = client_proc.client("recv");
2667 let (port, mut rx) = client.bind_handler_port::<u64>();
2668 let PortLocation::Bound(via_dest) = port.location() else {
2669 panic!("client port must be bound");
2670 };
2671
2672 let relay_dialer = DialMailboxRouter::new();
2679 relay_dialer.post(
2680 MessageEnvelope::serialize(
2681 test_actor_id("third_party", "sender"),
2682 via_dest,
2683 &7u64,
2684 Flattrs::new(),
2685 )
2686 .unwrap(),
2687 monitored_return_handle(),
2688 );
2689
2690 let received = time::timeout(Duration::from_secs(5), rx.recv())
2691 .await
2692 .expect("rx timed out")
2693 .expect("rx closed");
2694 assert_eq!(received, 7);
2695
2696 serve_via.stop("test cleanup");
2697 serve_via.join().await.unwrap();
2698 accept_handle.stop("test cleanup");
2699 accept_handle.join().await.unwrap();
2700 }
2701
2702 #[tokio::test]
2718 async fn test_gateway_attach_bidirectional() {
2719 use crate::testing::ids::test_actor_id;
2720
2721 let gw_a = Gateway::isolated();
2722 let pre_a_default = gw_a.default_location();
2723 let gw_b = Gateway::isolated();
2724 let pre_b_default = gw_b.default_location();
2725
2726 let attach = gw_a.attach(&gw_b);
2730
2731 let post_a_default = gw_a.default_location();
2733 let post_b_default = gw_b.default_location();
2734 let (uid_a, inner_a) = post_a_default.as_via().expect("gw_a default is via");
2735 assert_eq!(uid_a, gw_a.uid());
2736 assert_eq!(inner_a.as_ref(), &pre_b_default);
2737 let (uid_b, inner_b) = post_b_default.as_via().expect("gw_b default is via");
2738 assert_eq!(uid_b, gw_b.uid());
2739 assert_eq!(inner_b.as_ref(), &pre_a_default);
2740
2741 let proc_a = Proc::builder()
2744 .proc_id(ProcId::instance(Label::strip("alpha")))
2745 .shared_gateway(gw_a.clone())
2746 .build()
2747 .unwrap();
2748 let alpha_client = proc_a.client("client");
2749 let (alpha_port, mut alpha_rx) = alpha_client.bind_handler_port::<u64>();
2750 let PortLocation::Bound(alpha_dest) = alpha_port.location() else {
2751 panic!("alpha port must be bound");
2752 };
2753 assert!(
2754 alpha_dest.location().as_via().is_some(),
2755 "alpha carries via prefix"
2756 );
2757
2758 let proc_b = Proc::builder()
2759 .proc_id(ProcId::instance(Label::strip("beta")))
2760 .shared_gateway(gw_b.clone())
2761 .build()
2762 .unwrap();
2763 let beta_client = proc_b.client("client");
2764 let (beta_port, mut beta_rx) = beta_client.bind_handler_port::<u64>();
2765 let PortLocation::Bound(beta_dest) = beta_port.location() else {
2766 panic!("beta port must be bound");
2767 };
2768 assert!(
2769 beta_dest.location().as_via().is_some(),
2770 "beta carries via prefix"
2771 );
2772
2773 let sender = test_actor_id("client", "sender");
2776 gw_a.post(
2777 MessageEnvelope::serialize(sender.clone(), alpha_dest.clone(), &11u64, Flattrs::new())
2778 .unwrap(),
2779 monitored_return_handle(),
2780 );
2781 assert_eq!(
2782 time::timeout(Duration::from_secs(2), alpha_rx.recv())
2783 .await
2784 .expect("alpha_rx timed out")
2785 .expect("alpha_rx closed"),
2786 11
2787 );
2788
2789 gw_b.post(
2792 MessageEnvelope::serialize(sender.clone(), alpha_dest.clone(), &22u64, Flattrs::new())
2793 .unwrap(),
2794 monitored_return_handle(),
2795 );
2796 assert_eq!(
2797 time::timeout(Duration::from_secs(2), alpha_rx.recv())
2798 .await
2799 .expect("alpha_rx timed out")
2800 .expect("alpha_rx closed"),
2801 22
2802 );
2803
2804 gw_a.post(
2806 MessageEnvelope::serialize(sender.clone(), beta_dest.clone(), &33u64, Flattrs::new())
2807 .unwrap(),
2808 monitored_return_handle(),
2809 );
2810 assert_eq!(
2811 time::timeout(Duration::from_secs(2), beta_rx.recv())
2812 .await
2813 .expect("beta_rx timed out")
2814 .expect("beta_rx closed"),
2815 33
2816 );
2817
2818 drop(attach);
2821 assert_eq!(gw_a.default_location(), pre_a_default);
2822 assert_eq!(gw_b.default_location(), pre_b_default);
2823 assert!(gw_a.inner.peers.read().unwrap().is_empty());
2824 assert!(gw_b.inner.peers.read().unwrap().is_empty());
2825 }
2826}