1use std::collections::HashMap;
133
134use async_trait::async_trait;
135use hyperactor::Actor;
136use hyperactor::ActorEnvironment;
137use hyperactor::ActorId;
138use hyperactor::ActorRef;
139use hyperactor::Context;
140use hyperactor::Endpoint;
141use hyperactor::HandleClient;
142use hyperactor::Handler;
143use hyperactor::Instance;
144use hyperactor::OncePortRef;
145use hyperactor::PortRef;
146use hyperactor::ProcId;
147use hyperactor::RefClient;
148use hyperactor::RemoteSpawn;
149use hyperactor::context;
150use hyperactor::context::Actor as _;
151use hyperactor::mailbox::InvalidReference;
152use hyperactor::mailbox::MessageEnvelope;
153use hyperactor::mailbox::Undeliverable;
154use hyperactor::supervision::ActorSupervisionEvent;
155use hyperactor_mesh::ActorMesh;
156use hyperactor_mesh::ProcMeshRef;
157use hyperactor_mesh::mesh_controller::Subscribe;
158use hyperactor_mesh::mesh_controller::Unsubscribe;
159use hyperactor_mesh::supervision::MeshFailure;
160use ndslice::view::Ranked;
161use serde::Deserialize;
162use serde::Serialize;
163use typeuri::Named;
164
165use crate::errors::RdmaInitError;
166use crate::rdma_manager_actor::RdmaManagerActor;
167
168#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
176pub struct EntryId(u64);
177
178#[derive(
182 Handler,
183 HandleClient,
184 RefClient,
185 Debug,
186 Serialize,
187 Deserialize,
188 Named,
189 Clone
190)]
191pub struct RdmaManagerReady {
192 pub owner: ActorRef<RdmaManagerOwnerActor>,
193 pub entry: EntryId,
194}
195wirevalue::register_type!(RdmaManagerReady);
196
197#[derive(Handler, HandleClient, RefClient, Debug, Serialize, Deserialize, Named)]
199pub struct ReadyAck {
200 pub entry: EntryId,
201}
202wirevalue::register_type!(ReadyAck);
203
204#[derive(Handler, HandleClient, RefClient, Debug, Serialize, Deserialize, Named)]
209pub struct EnsureRdmaManager {
210 pub proc_mesh: ProcMeshRef,
211 pub reply: OncePortRef<Result<(), RdmaInitError>>,
212}
213wirevalue::register_type!(EnsureRdmaManager);
214
215#[derive(Debug)]
217#[expect(
218 clippy::large_enum_variant,
219 reason = "Pending is the common active state and entries live behind a HashMap; boxing it to shrink the rarer terminal variants would obscure the state machine for no material gain (the owner holds a handful of entries)."
220)]
221enum MeshState {
222 Pending {
223 manager_mesh: ActorMesh<RdmaManagerActor>,
229 controller_id: ActorId,
235 subscriber: PortRef<Option<MeshFailure>>,
241 remaining_acks: usize,
244 waiters: Vec<OncePortRef<Result<(), RdmaInitError>>>,
247 },
248 Ready {
249 manager_mesh: ActorMesh<RdmaManagerActor>,
252 controller_id: ActorId,
256 },
257 Failed {
258 err: RdmaInitError,
261 controller_id: Option<ActorId>,
264 },
265}
266
267impl MeshState {
268 fn controller_id(&self) -> Option<&ActorId> {
272 match self {
273 MeshState::Pending { controller_id, .. } | MeshState::Ready { controller_id, .. } => {
274 Some(controller_id)
275 }
276 MeshState::Failed { controller_id, .. } => controller_id.as_ref(),
277 }
278 }
279}
280
281fn proc_ids(mesh: &ProcMeshRef) -> std::collections::HashSet<ProcId> {
285 let n = Ranked::region(mesh).num_ranks();
286 (0..n)
287 .filter_map(|rank| Ranked::get(mesh, rank).map(|p| p.proc_addr().id().clone()))
288 .collect()
289}
290
291fn view_intersects(mesh: &ProcMeshRef, procs: &std::collections::HashSet<ProcId>) -> bool {
296 let n = Ranked::region(mesh).num_ranks();
297 (0..n).any(|rank| Ranked::get(mesh, rank).is_some_and(|p| procs.contains(p.proc_addr().id())))
298}
299
300pub const RDMA_MANAGER_OWNER_ACTOR_NAME: &str = "rdma_manager_owner";
302
303#[derive(Debug)]
304#[hyperactor::export(
305 handlers = [
306 EnsureRdmaManager,
307 ReadyAck,
308 MeshFailure,
309 Option<MeshFailure>,
310 ],
311)]
312#[hyperactor::spawnable]
313pub struct RdmaManagerOwnerActor {
314 by_mesh: HashMap<ProcMeshRef, EntryId>,
318 entries: HashMap<EntryId, MeshState>,
320 next_entry_id: u64,
324}
325
326impl RdmaManagerOwnerActor {
327 fn alloc_entry_id(&mut self) -> EntryId {
328 let id = EntryId(self.next_entry_id);
329 self.next_entry_id = self
330 .next_entry_id
331 .checked_add(1)
332 .expect("owner entry id space exhausted");
333 id
334 }
335
336 fn transition(
340 &mut self,
341 cx: &impl context::Actor<A = Self>,
342 entry: EntryId,
343 result: Result<(), RdmaInitError>,
344 ) {
345 if !matches!(self.entries.get(&entry), Some(MeshState::Pending { .. })) {
348 return;
349 }
350 let Some(MeshState::Pending {
351 manager_mesh,
352 controller_id,
353 subscriber,
354 waiters,
355 ..
356 }) = self.entries.remove(&entry)
357 else {
358 unreachable!("checked Pending immediately above");
359 };
360
361 tracing::debug!(
362 entry = entry.0,
363 controller = %controller_id,
364 ok = result.is_ok(),
365 waiters = waiters.len(),
366 "RdmaManagerOwnerActor draining Pending owner entry",
367 );
368
369 if let Some(controller) = manager_mesh
375 .as_managed()
376 .and_then(|mesh| mesh.controller().as_ref())
377 {
378 let mut unsub = controller.port();
379 unsub.return_undeliverable(false);
380 let _ = unsub.post(cx, Unsubscribe(subscriber));
381 }
382
383 for reply in waiters {
386 reply.post(cx, result.clone());
387 }
388
389 let next = match result {
393 Ok(()) => MeshState::Ready {
394 manager_mesh,
395 controller_id,
396 },
397 Err(e) => MeshState::Failed {
398 err: e,
399 controller_id: Some(controller_id),
400 },
401 };
402 self.entries.insert(entry, next);
403 debug_assert!(
404 !matches!(self.entries.get(&entry), Some(MeshState::Pending { .. })),
405 "RMO-3: entry must be terminal after a transition",
406 );
407 }
408
409 fn resolve_live_report(
419 &mut self,
420 cx: &impl context::Actor<A = Self>,
421 controller_id: &ActorId,
422 err: RdmaInitError,
423 ) -> bool {
424 debug_assert!(
425 self.entries
426 .values()
427 .filter(|s| matches!(
428 s,
429 MeshState::Pending { controller_id: c, .. } if c == controller_id
430 ))
431 .count()
432 <= 1,
433 "RMO-7: two pending entries must not share a controller id",
434 );
435 let matched = self.entries.iter().find_map(|(id, state)| match state {
441 MeshState::Pending {
442 controller_id: c, ..
443 } if c == controller_id => Some(*id),
444 _ => None,
445 });
446 match matched {
447 Some(entry) => {
448 self.transition(cx, entry, Err(err));
449 true
450 }
451 None => false,
452 }
453 }
454
455 fn resolve_controller_termination(
465 &mut self,
466 cx: &impl context::Actor<A = Self>,
467 controller_id: &ActorId,
468 err: RdmaInitError,
469 ) -> Option<usize> {
470 debug_assert!(
471 self.entries
472 .values()
473 .filter(|s| s.controller_id() == Some(controller_id))
474 .count()
475 <= 1,
476 "RMO-17: a controller id must be unique across all entries",
477 );
478 let source_id = self
480 .entries
481 .iter()
482 .find_map(|(id, s)| (s.controller_id() == Some(controller_id)).then_some(*id))?;
483 let source_procs = self
485 .by_mesh
486 .iter()
487 .find_map(|(mesh, id)| (*id == source_id).then(|| proc_ids(mesh)))?;
488 let targets: Vec<EntryId> = self
493 .by_mesh
494 .iter()
495 .filter(|(mesh, id)| {
496 matches!(self.entries.get(id), Some(MeshState::Pending { .. }))
497 && view_intersects(mesh, &source_procs)
498 })
499 .map(|(_, id)| *id)
500 .collect();
501 let n = targets.len();
502 for entry in targets {
503 self.transition(cx, entry, Err(err.clone()));
504 }
505 Some(n)
506 }
507
508 fn handle_live_report(&mut self, cx: &impl context::Actor<A = Self>, message: &MeshFailure) {
513 let Some(controller_id) = message.reporting_controller.as_ref() else {
514 tracing::warn!(
515 ?message,
516 "live report without a reporting controller; ignoring"
517 );
518 return;
519 };
520 let controller_id = controller_id.clone();
521 let err = RdmaInitError::InitFailed(message.to_string());
522 if self.resolve_live_report(cx, &controller_id, err) {
523 return;
524 }
525 if self
530 .entries
531 .values()
532 .any(|s| s.controller_id() == Some(&controller_id))
533 {
534 tracing::debug!(
535 %controller_id,
536 "late or duplicate live report for an already-resolved controller; absorbing",
537 );
538 } else {
539 tracing::warn!(%controller_id, "live report from an unknown controller; ignoring");
540 }
541 }
542}
543
544#[async_trait]
545impl RemoteSpawn for RdmaManagerOwnerActor {
546 type Params = ();
547
548 async fn new(_params: (), _environment: &ActorEnvironment) -> Result<Self, anyhow::Error> {
549 Ok(Self {
550 by_mesh: HashMap::new(),
551 entries: HashMap::new(),
552 next_entry_id: 0,
553 })
554 }
555}
556
557#[async_trait]
558impl Actor for RdmaManagerOwnerActor {
559 async fn init(&mut self, this: &Instance<Self>) -> Result<(), anyhow::Error> {
560 this.set_system();
561 Ok(())
562 }
563
564 async fn handle_supervision_event(
577 &mut self,
578 this: &Instance<Self>,
579 event: &ActorSupervisionEvent,
580 ) -> Result<bool, anyhow::Error> {
581 if event.actor_status.is_terminal() {
582 let controller_id = event.actor_id.id().clone();
583 let err = RdmaInitError::Supervision(event.to_string());
584 match self.resolve_controller_termination(this, &controller_id, err) {
585 Some(0) => {
586 tracing::debug!(
590 %controller_id,
591 "terminal controller with no overlapping pending view; nothing to resolve",
592 );
593 }
594 None => {
595 tracing::warn!(
596 %controller_id,
597 "terminal supervision event could not be attributed to a controller view; absorbing",
598 );
599 }
600 Some(_) => {}
601 }
602 }
603 Ok(true)
604 }
605
606 async fn handle_invalid_reference(
615 &mut self,
616 _cx: &Instance<Self>,
617 invalid: InvalidReference,
618 undeliverable: Undeliverable<MessageEnvelope>,
619 ) -> Result<(), anyhow::Error> {
620 tracing::warn!(
621 ?invalid,
622 ?undeliverable,
623 "RdmaManagerOwnerActor swallowing an invalid-reference return from initialization control traffic",
624 );
625 Ok(())
626 }
627}
628
629#[async_trait]
635impl Handler<EnsureRdmaManager> for RdmaManagerOwnerActor {
636 async fn handle(
637 &mut self,
638 cx: &Context<Self>,
639 msg: EnsureRdmaManager,
640 ) -> Result<(), anyhow::Error> {
641 let EnsureRdmaManager {
642 proc_mesh,
643 mut reply,
644 } = msg;
645 reply.return_undeliverable(false);
648
649 if let Some(&entry) = self.by_mesh.get(&proc_mesh) {
653 match self.entries.get_mut(&entry) {
654 Some(MeshState::Ready { manager_mesh, .. }) => {
655 debug_assert!(
656 manager_mesh
657 .as_managed()
658 .expect("ready RDMA manager mesh should be managed")
659 .controller()
660 .is_some(),
661 "RMO-13: a Ready manager mesh is controller-ful",
662 );
663 reply.post(cx, Ok(()));
664 }
665 Some(MeshState::Failed { err, .. }) => {
666 reply.post(cx, Err(err.clone()));
667 }
668 Some(MeshState::Pending { waiters, .. }) => {
669 waiters.push(reply);
670 }
671 None => {
672 debug_assert!(false, "by_mesh points to a missing entries slot");
676 reply.post(
677 cx,
678 Err(RdmaInitError::SpawnFailed("owner index desync".to_string())),
679 );
680 }
681 }
682 return Ok(());
683 }
684
685 let entry = self.alloc_entry_id();
691 let mesh = match proc_mesh
692 .spawn_service::<RdmaManagerActor, _>(cx, "rdma_manager", &None)
693 .await
694 {
695 Ok(mesh) => mesh,
696 Err(e) => {
697 let err = RdmaInitError::SpawnFailed(e.to_string());
700 self.entries.insert(
701 entry,
702 MeshState::Failed {
703 err: err.clone(),
704 controller_id: None,
705 },
706 );
707 self.by_mesh.insert(proc_mesh, entry);
708 reply.post(cx, Err(err));
709 return Ok(());
710 }
711 };
712 let controller = mesh
717 .as_managed()
718 .expect("spawned RDMA manager mesh should be managed")
719 .controller()
720 .as_ref()
721 .expect("controller-ful spawn_service always sets a controller")
722 .clone();
723 let controller_id = controller.actor_addr().id().clone();
724 let subscriber = cx.instance().port::<Option<MeshFailure>>().bind();
728 let remaining_acks = mesh.region().num_ranks();
729 let manager_for_cast = mesh.clone();
730
731 self.entries.insert(
734 entry,
735 MeshState::Pending {
736 manager_mesh: mesh,
737 controller_id,
738 subscriber: subscriber.clone(),
739 remaining_acks,
740 waiters: vec![reply],
741 },
742 );
743 self.by_mesh.insert(proc_mesh, entry);
744
745 controller.post(cx, Subscribe(subscriber));
749
750 if remaining_acks == 0 {
753 self.transition(cx, entry, Ok(()));
754 return Ok(());
755 }
756
757 let owner = cx.bind::<RdmaManagerOwnerActor>();
759 if let Err(e) = manager_for_cast.cast(cx, RdmaManagerReady { owner, entry }) {
760 self.transition(cx, entry, Err(RdmaInitError::SpawnFailed(e.to_string())));
761 }
762 Ok(())
763 }
764}
765
766#[async_trait]
770#[hyperactor::handle(ReadyAck)]
771impl ReadyAckHandler for RdmaManagerOwnerActor {
772 async fn ready_ack(&mut self, cx: &Context<Self>, entry: EntryId) -> Result<(), anyhow::Error> {
773 let resolved = if let Some(MeshState::Pending { remaining_acks, .. }) =
775 self.entries.get_mut(&entry)
776 {
777 debug_assert!(
778 *remaining_acks > 0,
779 "ReadyAck on a Pending entry with 0 remaining (0-rank resolves before any ack can arrive)",
780 );
781 *remaining_acks -= 1;
782 *remaining_acks == 0
783 } else {
784 false
787 };
788 if resolved {
789 self.transition(cx, entry, Ok(()));
792 }
793 Ok(())
794 }
795}
796
797#[async_trait]
806impl Handler<MeshFailure> for RdmaManagerOwnerActor {
807 async fn handle(
808 &mut self,
809 cx: &Context<Self>,
810 message: MeshFailure,
811 ) -> Result<(), anyhow::Error> {
812 self.handle_live_report(cx, &message);
813 Ok(())
814 }
815}
816
817#[async_trait]
818impl Handler<Option<MeshFailure>> for RdmaManagerOwnerActor {
819 async fn handle(
820 &mut self,
821 cx: &Context<Self>,
822 message: Option<MeshFailure>,
823 ) -> Result<(), anyhow::Error> {
824 if let Some(message) = message {
826 self.handle_live_report(cx, &message);
827 }
828 Ok(())
829 }
830}
831
832#[cfg(test)]
833mod tests {
834 use std::time::Duration;
835
836 use async_trait::async_trait;
837 use hyperactor::ActorAddr;
838 use hyperactor::ActorEnvironment;
839 use hyperactor::ActorHandle;
840 use hyperactor::ActorId;
841 use hyperactor::ActorRef;
842 use hyperactor::Context;
843 use hyperactor::Endpoint;
844 use hyperactor::Handler;
845 use hyperactor::PortRef;
846 use hyperactor::RemoteSpawn;
847 use hyperactor::actor::ActorStatus;
848 use hyperactor::context::Actor as _;
849 use hyperactor::mailbox::DeliveryFailure;
850 use hyperactor::mailbox::InvalidReference;
851 use hyperactor::mailbox::InvalidReferenceReason;
852 use hyperactor::mailbox::MessageEnvelope;
853 use hyperactor::mailbox::Undeliverable;
854 use hyperactor::supervision::ActorSupervisionEvent;
855 use hyperactor_config::Flattrs;
856 use hyperactor_mesh::ActorMesh;
857 use hyperactor_mesh::ProcMeshRef;
858 use hyperactor_mesh::context;
859 use hyperactor_mesh::host_mesh::HostMesh;
860 use hyperactor_mesh::mesh_controller::ActorMeshController;
861 use hyperactor_mesh::mesh_controller::GetSubscriberCount;
862 use hyperactor_mesh::mesh_controller::Subscribe;
863 use hyperactor_mesh::mesh_controller::Unsubscribe;
864 use hyperactor_mesh::mesh_id::ResourceId;
865 use hyperactor_mesh::resource;
866 use hyperactor_mesh::supervision::MeshFailure;
867 use ndslice::ViewExt;
868 use ndslice::view::Ranked;
869 use tokio::sync::oneshot;
870 use tokio::time::timeout;
871
872 use super::EnsureRdmaManagerClient;
873 use super::EntryId;
874 use super::MeshState;
875 use super::RDMA_MANAGER_OWNER_ACTOR_NAME;
876 use super::RdmaManagerOwnerActor;
877 use super::ReadyAck;
878 use crate::RdmaManagerActor;
879 use crate::errors::RdmaInitError;
880
881 #[timed_test::async_timed_test(timeout_secs = 30)]
885 async fn ensure_resolves_ok_after_all_ranks_ack() -> anyhow::Result<()> {
886 let config = hyperactor_config::global::lock();
887 let _guard = config.override_key(crate::config::RDMA_ALLOW_TCP_FALLBACK, true);
888
889 let cx = context().await;
890 let client = cx.actor_instance;
891
892 let mut host_mesh = HostMesh::local_in_process().await?;
893 let proc_mesh = host_mesh
894 .spawn(
895 client,
896 "rdma_procs",
897 hyperactor_mesh::extent!(procs = 2),
898 None,
899 None,
900 )
901 .await?;
902 let proc_mesh_ref: ProcMeshRef = (*proc_mesh).clone();
903
904 let owner = RdmaManagerOwnerActor::new((), &ActorEnvironment::default()).await?;
905 let owner_handle = client.spawn(owner);
906
907 let (reply, rx) = client.open_once_port::<Result<(), RdmaInitError>>();
908 owner_handle
909 .ensure_rdma_manager(client, proc_mesh_ref, reply.bind())
910 .await?;
911
912 let result = rx.recv().await?;
913 assert_eq!(result, Ok(()));
914
915 owner_handle.stop("test complete")?;
916 let _ = owner_handle.await;
917 host_mesh.shutdown(client).await?;
918 Ok(())
919 }
920
921 #[derive(Debug, PartialEq, Eq)]
924 enum EntryStateTag {
925 Absent,
926 Pending,
927 Ready,
928 Failed,
929 }
930
931 #[derive(Debug)]
932 struct ProbeResult {
933 state: EntryStateTag,
934 entry_count: usize,
935 waiter_count: usize,
936 remaining_acks: Option<usize>,
937 manager_ids: Vec<ActorId>,
938 controller_id: Option<ActorId>,
939 }
940
941 #[derive(Debug)]
944 struct Probe {
945 key: ProcMeshRef,
946 reply: oneshot::Sender<ProbeResult>,
947 }
948
949 #[async_trait]
950 impl Handler<Probe> for RdmaManagerOwnerActor {
951 async fn handle(&mut self, _cx: &Context<Self>, msg: Probe) -> Result<(), anyhow::Error> {
952 let entry_count = self.entries.len();
953 let (state, waiter_count, remaining_acks, manager_ids, controller_id) = match self
954 .by_mesh
955 .get(&msg.key)
956 .copied()
957 .and_then(|id| self.entries.get(&id))
958 {
959 None => (EntryStateTag::Absent, 0, None, Vec::new(), None),
960 Some(MeshState::Pending {
961 waiters,
962 remaining_acks,
963 manager_mesh,
964 controller_id,
965 ..
966 }) => (
967 EntryStateTag::Pending,
968 waiters.len(),
969 Some(*remaining_acks),
970 manager_mesh_ids(manager_mesh),
971 Some(controller_id.clone()),
972 ),
973 Some(MeshState::Ready {
974 manager_mesh,
975 controller_id,
976 }) => (
977 EntryStateTag::Ready,
978 0,
979 None,
980 manager_mesh_ids(manager_mesh),
981 Some(controller_id.clone()),
982 ),
983 Some(MeshState::Failed { controller_id, .. }) => (
984 EntryStateTag::Failed,
985 0,
986 None,
987 Vec::new(),
988 controller_id.clone(),
989 ),
990 };
991 let _ = msg.reply.send(ProbeResult {
992 state,
993 entry_count,
994 waiter_count,
995 remaining_acks,
996 manager_ids,
997 controller_id,
998 });
999 Ok(())
1000 }
1001 }
1002
1003 async fn probe(
1004 handle: &ActorHandle<RdmaManagerOwnerActor>,
1005 cx: &impl hyperactor::context::Actor,
1006 key: ProcMeshRef,
1007 ) -> ProbeResult {
1008 let (tx, rx) = oneshot::channel();
1009 handle.post(cx, Probe { key, reply: tx });
1010 rx.await.expect("owner replied to Probe")
1011 }
1012
1013 fn manager_mesh_ids(mesh: &ActorMesh<RdmaManagerActor>) -> Vec<ActorId> {
1015 let ranks = Ranked::region(&**mesh).num_ranks();
1016 (0..ranks)
1017 .filter_map(|rank| {
1018 Ranked::get(&**mesh, rank).map(|actor| actor.actor_addr().id().clone())
1019 })
1020 .collect()
1021 }
1022
1023 #[timed_test::async_timed_test(timeout_secs = 30)]
1026 async fn duplicate_ensure_replies_from_ready_cache() -> anyhow::Result<()> {
1027 let config = hyperactor_config::global::lock();
1028 let _guard = config.override_key(crate::config::RDMA_ALLOW_TCP_FALLBACK, true);
1029
1030 let cx = context().await;
1031 let client = cx.actor_instance;
1032
1033 let mut host_mesh = HostMesh::local_in_process().await?;
1034 let proc_mesh = host_mesh
1035 .spawn(
1036 client,
1037 "rdma_procs",
1038 hyperactor_mesh::extent!(procs = 2),
1039 None,
1040 None,
1041 )
1042 .await?;
1043 let key: ProcMeshRef = (*proc_mesh).clone();
1044
1045 let owner = RdmaManagerOwnerActor::new((), &ActorEnvironment::default()).await?;
1046 let owner_handle = client.spawn(owner);
1047
1048 let (r1, rx1) = client.open_once_port::<Result<(), RdmaInitError>>();
1049 owner_handle
1050 .ensure_rdma_manager(client, key.clone(), r1.bind())
1051 .await?;
1052 assert_eq!(rx1.recv().await?, Ok(()));
1053
1054 let (r2, rx2) = client.open_once_port::<Result<(), RdmaInitError>>();
1055 owner_handle
1056 .ensure_rdma_manager(client, key.clone(), r2.bind())
1057 .await?;
1058 assert_eq!(rx2.recv().await?, Ok(()));
1059
1060 let observed = probe(&owner_handle, client, key.clone()).await;
1061 assert_eq!(observed.state, EntryStateTag::Ready);
1062 assert_eq!(
1063 observed.entry_count, 1,
1064 "a duplicate ensure for the same view must not add an entry",
1065 );
1066
1067 owner_handle.stop("test complete")?;
1068 let _ = owner_handle.await;
1069 host_mesh.shutdown(client).await?;
1070 Ok(())
1071 }
1072
1073 #[derive(Debug)]
1076 struct Pause {
1077 entered: oneshot::Sender<()>,
1078 release: oneshot::Receiver<()>,
1079 }
1080
1081 #[async_trait]
1082 impl Handler<Pause> for RdmaManagerOwnerActor {
1083 async fn handle(&mut self, _cx: &Context<Self>, msg: Pause) -> Result<(), anyhow::Error> {
1084 let _ = msg.entered.send(());
1085 let _ = msg.release.await;
1086 Ok(())
1087 }
1088 }
1089
1090 #[timed_test::async_timed_test(timeout_secs = 30)]
1095 async fn duplicate_ensure_joins_pending_entry() -> anyhow::Result<()> {
1096 let config = hyperactor_config::global::lock();
1097 let _guard = config.override_key(crate::config::RDMA_ALLOW_TCP_FALLBACK, true);
1098
1099 let cx = context().await;
1100 let client = cx.actor_instance;
1101
1102 let mut host_mesh = HostMesh::local_in_process().await?;
1103 let proc_mesh = host_mesh
1104 .spawn(
1105 client,
1106 "rdma_procs",
1107 hyperactor_mesh::extent!(procs = 2),
1108 None,
1109 None,
1110 )
1111 .await?;
1112 let key: ProcMeshRef = (*proc_mesh).clone();
1113
1114 let owner = RdmaManagerOwnerActor::new((), &ActorEnvironment::default()).await?;
1115 let owner_handle = client.spawn(owner);
1116
1117 let (entered_tx, entered_rx) = oneshot::channel();
1120 let (release_tx, release_rx) = oneshot::channel();
1121 owner_handle.post(
1122 client,
1123 Pause {
1124 entered: entered_tx,
1125 release: release_rx,
1126 },
1127 );
1128 entered_rx.await.expect("owner entered Pause");
1129
1130 let (r1, rx1) = client.open_once_port::<Result<(), RdmaInitError>>();
1131 owner_handle
1132 .ensure_rdma_manager(client, key.clone(), r1.bind())
1133 .await?;
1134 let (r2, rx2) = client.open_once_port::<Result<(), RdmaInitError>>();
1135 owner_handle
1136 .ensure_rdma_manager(client, key.clone(), r2.bind())
1137 .await?;
1138
1139 let (ptx, prx) = oneshot::channel();
1140 owner_handle.post(
1141 client,
1142 Probe {
1143 key: key.clone(),
1144 reply: ptx,
1145 },
1146 );
1147
1148 let _ = release_tx.send(());
1151
1152 let observed = prx.await.expect("owner replied to Probe");
1153 assert_eq!(observed.state, EntryStateTag::Pending);
1154 assert_eq!(observed.entry_count, 1, "both ensures share one entry");
1155 assert_eq!(observed.waiter_count, 2, "second ensure joined as a waiter");
1156 assert_eq!(
1157 observed.remaining_acks,
1158 Some(2),
1159 "two-rank barrier initialized before any ack",
1160 );
1161
1162 assert_eq!(rx1.recv().await?, Ok(()));
1163 assert_eq!(rx2.recv().await?, Ok(()));
1164
1165 owner_handle.stop("test complete")?;
1166 let _ = owner_handle.await;
1167 host_mesh.shutdown(client).await?;
1168 Ok(())
1169 }
1170
1171 #[timed_test::async_timed_test(timeout_secs = 30)]
1176 async fn ensure_gather_is_non_blocking() -> anyhow::Result<()> {
1177 let config = hyperactor_config::global::lock();
1178 let _guard = config.override_key(crate::config::RDMA_ALLOW_TCP_FALLBACK, true);
1179
1180 let cx = context().await;
1181 let client = cx.actor_instance;
1182
1183 let mut host_mesh = HostMesh::local_in_process().await?;
1184 let proc_mesh = host_mesh
1185 .spawn(
1186 client,
1187 "rdma_procs",
1188 hyperactor_mesh::extent!(procs = 2),
1189 None,
1190 None,
1191 )
1192 .await?;
1193 let view_a: ProcMeshRef = proc_mesh.range("procs", 0..1)?;
1194 let view_b: ProcMeshRef = proc_mesh.range("procs", 1..2)?;
1195
1196 let owner = RdmaManagerOwnerActor::new((), &ActorEnvironment::default()).await?;
1197 let owner_handle = client.spawn(owner);
1198
1199 let (entered_tx, entered_rx) = oneshot::channel();
1202 let (release_tx, release_rx) = oneshot::channel();
1203 owner_handle.post(
1204 client,
1205 Pause {
1206 entered: entered_tx,
1207 release: release_rx,
1208 },
1209 );
1210 entered_rx.await.expect("owner entered Pause");
1211
1212 let (ra, rxa) = client.open_once_port::<Result<(), RdmaInitError>>();
1213 owner_handle
1214 .ensure_rdma_manager(client, view_a.clone(), ra.bind())
1215 .await?;
1216 let (rb, rxb) = client.open_once_port::<Result<(), RdmaInitError>>();
1217 owner_handle
1218 .ensure_rdma_manager(client, view_b.clone(), rb.bind())
1219 .await?;
1220
1221 let (pta, prxa) = oneshot::channel();
1222 owner_handle.post(
1223 client,
1224 Probe {
1225 key: view_a.clone(),
1226 reply: pta,
1227 },
1228 );
1229 let (ptb, prxb) = oneshot::channel();
1230 owner_handle.post(
1231 client,
1232 Probe {
1233 key: view_b.clone(),
1234 reply: ptb,
1235 },
1236 );
1237
1238 let _ = release_tx.send(());
1239
1240 let a = prxa.await.expect("probe view A");
1243 let b = prxb.await.expect("probe view B");
1244 assert_eq!(a.state, EntryStateTag::Pending);
1245 assert_eq!(b.state, EntryStateTag::Pending);
1246 assert_eq!(a.entry_count, 2, "owner tracks both views concurrently");
1247
1248 assert_eq!(rxa.recv().await?, Ok(()));
1249 assert_eq!(rxb.recv().await?, Ok(()));
1250
1251 owner_handle.stop("test complete")?;
1252 let _ = owner_handle.await;
1253 host_mesh.shutdown(client).await?;
1254 Ok(())
1255 }
1256
1257 #[timed_test::async_timed_test(timeout_secs = 30)]
1262 async fn owner_is_controllerless_singleton() -> anyhow::Result<()> {
1263 let config = hyperactor_config::global::lock();
1264 let _guard = config.override_key(crate::config::RDMA_ALLOW_TCP_FALLBACK, true);
1265
1266 let cx = context().await;
1267 let client = cx.actor_instance;
1268
1269 let mut host_mesh = HostMesh::local_in_process().await?;
1270 let proc_mesh = host_mesh
1271 .spawn(
1272 client,
1273 "rdma_procs",
1274 hyperactor_mesh::extent!(procs = 1),
1275 None,
1276 None,
1277 )
1278 .await?;
1279 let target: ProcMeshRef = (*proc_mesh).clone();
1280
1281 let owner_mesh = proc_mesh
1283 .spawn_controllerless_service::<RdmaManagerOwnerActor, _>(
1284 client,
1285 RDMA_MANAGER_OWNER_ACTOR_NAME,
1286 &(),
1287 )
1288 .await?;
1289 assert!(
1290 owner_mesh
1291 .as_managed()
1292 .expect("RDMA manager owner mesh should be managed")
1293 .controller()
1294 .is_none(),
1295 "owner is spawned controllerless",
1296 );
1297 let owner_ref: ActorRef<RdmaManagerOwnerActor> =
1298 owner_mesh.values().next().expect("owner rank 0").clone();
1299
1300 drop(owner_mesh);
1302
1303 let (r, rx) = client.open_once_port::<Result<(), RdmaInitError>>();
1305 owner_ref
1306 .ensure_rdma_manager(client, target, r.bind())
1307 .await?;
1308 assert_eq!(rx.recv().await?, Ok(()));
1309
1310 host_mesh.shutdown(client).await?;
1311 Ok(())
1312 }
1313
1314 #[timed_test::async_timed_test(timeout_secs = 30)]
1318 async fn owner_service_is_singleton() -> anyhow::Result<()> {
1319 let config = hyperactor_config::global::lock();
1320 let _guard = config.override_key(crate::config::RDMA_ALLOW_TCP_FALLBACK, true);
1321
1322 let cx = context().await;
1323 let client = cx.actor_instance;
1324
1325 let mut host_mesh = HostMesh::local_in_process().await?;
1326 let proc_mesh = host_mesh
1327 .spawn(
1328 client,
1329 "rdma_procs",
1330 hyperactor_mesh::extent!(procs = 1),
1331 None,
1332 None,
1333 )
1334 .await?;
1335
1336 let first = proc_mesh
1337 .spawn_controllerless_service::<RdmaManagerOwnerActor, _>(
1338 client,
1339 RDMA_MANAGER_OWNER_ACTOR_NAME,
1340 &(),
1341 )
1342 .await?;
1343 let second = proc_mesh
1344 .spawn_controllerless_service::<RdmaManagerOwnerActor, _>(
1345 client,
1346 RDMA_MANAGER_OWNER_ACTOR_NAME,
1347 &(),
1348 )
1349 .await?;
1350
1351 let addr1 = first
1352 .values()
1353 .next()
1354 .expect("owner rank 0")
1355 .actor_addr()
1356 .clone();
1357 let addr2 = second
1358 .values()
1359 .next()
1360 .expect("owner rank 0")
1361 .actor_addr()
1362 .clone();
1363 assert_eq!(
1364 addr1, addr2,
1365 "the owner service is a singleton, deduped by name"
1366 );
1367
1368 host_mesh.shutdown(client).await?;
1369 Ok(())
1370 }
1371
1372 #[timed_test::async_timed_test(timeout_secs = 30)]
1377 async fn overlapping_views_share_per_proc_manager_whole_first() -> anyhow::Result<()> {
1378 overlapping_views_share_per_proc_manager(true).await
1379 }
1380
1381 #[timed_test::async_timed_test(timeout_secs = 30)]
1382 async fn overlapping_views_share_per_proc_manager_slice_first() -> anyhow::Result<()> {
1383 overlapping_views_share_per_proc_manager(false).await
1384 }
1385
1386 async fn overlapping_views_share_per_proc_manager(whole_first: bool) -> anyhow::Result<()> {
1387 let config = hyperactor_config::global::lock();
1388 let _guard = config.override_key(crate::config::RDMA_ALLOW_TCP_FALLBACK, true);
1389
1390 let cx = context().await;
1391 let client = cx.actor_instance;
1392
1393 let mut host_mesh = HostMesh::local_in_process().await?;
1394 let proc_mesh = host_mesh
1395 .spawn(
1396 client,
1397 "rdma_procs",
1398 hyperactor_mesh::extent!(procs = 2),
1399 None,
1400 None,
1401 )
1402 .await?;
1403 let whole: ProcMeshRef = (*proc_mesh).clone();
1404 let slice: ProcMeshRef = proc_mesh.range("procs", 1..2)?;
1405
1406 let owner = RdmaManagerOwnerActor::new((), &ActorEnvironment::default()).await?;
1407 let owner_handle = client.spawn(owner);
1408
1409 let (first, second) = if whole_first {
1410 (whole.clone(), slice.clone())
1411 } else {
1412 (slice.clone(), whole.clone())
1413 };
1414
1415 let (r1, rx1) = client.open_once_port::<Result<(), RdmaInitError>>();
1422 owner_handle
1423 .ensure_rdma_manager(client, first, r1.bind())
1424 .await?;
1425 let first_reply = timeout(Duration::from_secs(15), rx1.recv())
1426 .await
1427 .expect("first ensure reply elapsed")?;
1428 assert_eq!(first_reply, Ok(()));
1429
1430 let (r2, rx2) = client.open_once_port::<Result<(), RdmaInitError>>();
1434 owner_handle
1435 .ensure_rdma_manager(client, second.clone(), r2.bind())
1436 .await?;
1437 let after_spawn = timeout(
1438 Duration::from_secs(15),
1439 probe(&owner_handle, client, second.clone()),
1440 )
1441 .await
1442 .expect("second spawn_service blocked: probe elapsed");
1443 assert!(
1444 matches!(
1445 after_spawn.state,
1446 EntryStateTag::Pending | EntryStateTag::Ready
1447 ),
1448 "second ensure recorded no entry: {after_spawn:?}",
1449 );
1450
1451 let second_reply = timeout(Duration::from_secs(15), rx2.recv())
1454 .await
1455 .expect("second ensure reply elapsed: shared actor did not re-ack")?;
1456 assert_eq!(second_reply, Ok(()));
1457
1458 let whole_entry = probe(&owner_handle, client, whole.clone()).await;
1459 let slice_entry = probe(&owner_handle, client, slice.clone()).await;
1460
1461 assert_eq!(whole_entry.entry_count, 2, "each view keeps its own entry");
1463 assert_eq!(whole_entry.state, EntryStateTag::Ready);
1464 assert_eq!(slice_entry.state, EntryStateTag::Ready);
1465
1466 let shared = whole_entry
1468 .manager_ids
1469 .iter()
1470 .filter(|id| slice_entry.manager_ids.contains(id))
1471 .count();
1472 assert_eq!(
1473 whole_entry.manager_ids.len(),
1474 2,
1475 "whole view spans two procs"
1476 );
1477 assert_eq!(
1478 slice_entry.manager_ids.len(),
1479 1,
1480 "slice view spans one proc"
1481 );
1482 assert_eq!(shared, 1, "views share the one manager on the common proc");
1483
1484 timeout(Duration::from_secs(15), async move {
1486 owner_handle.stop("test complete")?;
1487 let _ = owner_handle.await;
1488 host_mesh.shutdown(client).await?;
1489 anyhow::Ok(())
1490 })
1491 .await
1492 .expect("shutdown elapsed: overlapping controller cleanup")?;
1493 Ok(())
1494 }
1495
1496 fn make_mesh_failure(reporter: Option<ActorId>, subject: ActorAddr) -> MeshFailure {
1510 MeshFailure {
1511 actor_mesh_name: Some("rdma_manager".to_string()),
1512 event: ActorSupervisionEvent::new(
1513 subject,
1514 None,
1515 ActorStatus::generic_failure("injected manager failure"),
1516 None,
1517 ),
1518 crashed_ranks: vec![0],
1519 reporting_controller: reporter,
1520 }
1521 }
1522
1523 async fn wait_for_remaining(
1526 handle: &ActorHandle<RdmaManagerOwnerActor>,
1527 cx: &impl hyperactor::context::Actor,
1528 key: ProcMeshRef,
1529 want: usize,
1530 ) {
1531 for _ in 0..400 {
1532 if probe(handle, cx, key.clone()).await.remaining_acks == Some(want) {
1533 return;
1534 }
1535 tokio::time::sleep(Duration::from_millis(25)).await;
1536 }
1537 panic!("entry did not reach remaining_acks == {want}");
1538 }
1539
1540 #[derive(Debug)]
1545 struct HoldPending {
1546 key: ProcMeshRef,
1547 reply: oneshot::Sender<
1548 Option<(
1549 EntryId,
1550 ActorId,
1551 ActorRef<ActorMeshController<RdmaManagerActor>>,
1552 )>,
1553 >,
1554 }
1555
1556 #[async_trait]
1557 impl Handler<HoldPending> for RdmaManagerOwnerActor {
1558 async fn handle(
1559 &mut self,
1560 _cx: &Context<Self>,
1561 msg: HoldPending,
1562 ) -> Result<(), anyhow::Error> {
1563 let held = self.by_mesh.get(&msg.key).copied().and_then(|id| {
1564 match self.entries.get_mut(&id) {
1565 Some(MeshState::Pending {
1566 remaining_acks,
1567 controller_id,
1568 manager_mesh,
1569 ..
1570 }) => {
1571 *remaining_acks += 1;
1572 let controller = manager_mesh
1573 .as_managed()
1574 .expect("pending RDMA manager mesh should be managed")
1575 .controller()
1576 .as_ref()
1577 .expect("controller-ful manager mesh")
1578 .clone();
1579 Some((id, controller_id.clone(), controller))
1580 }
1581 _ => None,
1582 }
1583 });
1584 let _ = msg.reply.send(held);
1585 Ok(())
1586 }
1587 }
1588
1589 async fn wait_until_state(
1591 handle: &ActorHandle<RdmaManagerOwnerActor>,
1592 cx: &impl hyperactor::context::Actor,
1593 key: ProcMeshRef,
1594 want: EntryStateTag,
1595 ) {
1596 for _ in 0..400 {
1597 if probe(handle, cx, key.clone()).await.state == want {
1598 return;
1599 }
1600 tokio::time::sleep(Duration::from_millis(25)).await;
1601 }
1602 panic!("entry did not reach state {want:?}");
1603 }
1604
1605 #[derive(Debug)]
1610 struct UnsubscribeController {
1611 controller: ActorRef<ActorMeshController<RdmaManagerActor>>,
1612 ack: oneshot::Sender<()>,
1613 }
1614
1615 #[async_trait]
1616 impl Handler<UnsubscribeController> for RdmaManagerOwnerActor {
1617 async fn handle(
1618 &mut self,
1619 cx: &Context<Self>,
1620 msg: UnsubscribeController,
1621 ) -> Result<(), anyhow::Error> {
1622 let port = cx.instance().port::<Option<MeshFailure>>().bind();
1623 let mut unsub = msg.controller.port();
1624 unsub.return_undeliverable(false);
1625 let _ = unsub.post(cx, Unsubscribe(port));
1626 let _ = msg.ack.send(());
1627 Ok(())
1628 }
1629 }
1630
1631 #[derive(Debug)]
1635 struct CountSubscribers {
1636 controller: ActorRef<ActorMeshController<RdmaManagerActor>>,
1637 count_port: PortRef<usize>,
1638 }
1639
1640 #[async_trait]
1641 impl Handler<CountSubscribers> for RdmaManagerOwnerActor {
1642 async fn handle(
1643 &mut self,
1644 cx: &Context<Self>,
1645 msg: CountSubscribers,
1646 ) -> Result<(), anyhow::Error> {
1647 msg.controller.post(cx, GetSubscriberCount(msg.count_port));
1651 Ok(())
1652 }
1653 }
1654
1655 async fn owner_subscriber_count(
1659 owner: &ActorHandle<RdmaManagerOwnerActor>,
1660 cx: &impl hyperactor::context::Actor,
1661 controller: ActorRef<ActorMeshController<RdmaManagerActor>>,
1662 ) -> usize {
1663 let (tx, mut rx) = cx.instance().open_port::<usize>();
1664 owner.post(
1665 cx,
1666 CountSubscribers {
1667 controller,
1668 count_port: tx.bind(),
1669 },
1670 );
1671 rx.recv()
1672 .await
1673 .expect("controller replied to GetSubscriberCount")
1674 }
1675
1676 #[derive(Debug)]
1684 struct ReplaySequence {
1685 controller: ActorRef<ActorMeshController<RdmaManagerActor>>,
1686 midpoint_port: PortRef<usize>,
1687 }
1688
1689 #[async_trait]
1690 impl Handler<ReplaySequence> for RdmaManagerOwnerActor {
1691 async fn handle(
1692 &mut self,
1693 cx: &Context<Self>,
1694 msg: ReplaySequence,
1695 ) -> Result<(), anyhow::Error> {
1696 let port = cx.instance().port::<Option<MeshFailure>>().bind();
1697 msg.controller.post(cx, Unsubscribe(port.clone()));
1698 msg.controller
1699 .post(cx, GetSubscriberCount(msg.midpoint_port));
1700 msg.controller.post(
1701 cx,
1702 resource::Stop {
1703 id: ResourceId::from_name("rdma-owner-test-stop"),
1704 reason: "test: latch a whole-view stop for replay".to_string(),
1705 },
1706 );
1707 msg.controller.post(cx, Subscribe(port));
1708 Ok(())
1709 }
1710 }
1711
1712 #[timed_test::async_timed_test(timeout_secs = 60)]
1718 async fn initialization_failure_resolves_and_is_cached() -> anyhow::Result<()> {
1719 let config = hyperactor_config::global::lock();
1720 let _no_tcp = config.override_key(crate::config::RDMA_ALLOW_TCP_FALLBACK, false);
1721 let _no_ib = config.override_key(crate::config::RDMA_DISABLE_IBVERBS, true);
1722
1723 let cx = context().await;
1724 let client = cx.actor_instance;
1725
1726 let mut host_mesh = HostMesh::local_in_process().await?;
1727 let proc_mesh = host_mesh
1728 .spawn(
1729 client,
1730 "rdma_procs",
1731 hyperactor_mesh::extent!(procs = 1),
1732 None,
1733 None,
1734 )
1735 .await?;
1736 let key: ProcMeshRef = (*proc_mesh).clone();
1737
1738 let owner = RdmaManagerOwnerActor::new((), &ActorEnvironment::default()).await?;
1739 let owner_handle = client.spawn(owner);
1740
1741 let (r1, rx1) = client.open_once_port::<Result<(), RdmaInitError>>();
1742 owner_handle
1743 .ensure_rdma_manager(client, key.clone(), r1.bind())
1744 .await?;
1745 let err = rx1
1746 .recv()
1747 .await?
1748 .expect_err("init with no available backend must fail");
1749 match &err {
1750 RdmaInitError::InitFailed(msg) | RdmaInitError::SpawnFailed(msg) => assert!(
1758 msg.contains("no RDMA backend available"),
1759 "the forced no-backend cause must ride through on both the \
1760 InitFailed (controller report) and SpawnFailed (spawn-wait) \
1761 paths, got: {msg}",
1762 ),
1763 other => panic!("expected InitFailed or SpawnFailed, got {other:?}"),
1764 }
1765
1766 let (r2, rx2) = client.open_once_port::<Result<(), RdmaInitError>>();
1769 owner_handle
1770 .ensure_rdma_manager(client, key.clone(), r2.bind())
1771 .await?;
1772 let cached = rx2.recv().await?.expect_err("cached terminal error");
1773 assert_eq!(
1774 cached, err,
1775 "second ensure must reply the exact cached terminal error, not just \
1776 the same enum discriminant",
1777 );
1778
1779 let observed = probe(&owner_handle, client, key.clone()).await;
1780 assert_eq!(observed.state, EntryStateTag::Failed);
1781 assert_eq!(observed.entry_count, 1, "a failure must not add an entry");
1782
1783 owner_handle.stop("test complete")?;
1784 let _ = owner_handle.await;
1785 host_mesh.shutdown(client).await?;
1786 Ok(())
1787 }
1788
1789 #[timed_test::async_timed_test(timeout_secs = 60)]
1803 async fn mesh_failure_resolves_only_typed_source() -> anyhow::Result<()> {
1804 let config = hyperactor_config::global::lock();
1805 let _guard = config.override_key(crate::config::RDMA_ALLOW_TCP_FALLBACK, true);
1806
1807 let cx = context().await;
1808 let client = cx.actor_instance;
1809
1810 let mut host_mesh = HostMesh::local_in_process().await?;
1811 let proc_mesh = host_mesh
1812 .spawn(
1813 client,
1814 "rdma_procs",
1815 hyperactor_mesh::extent!(procs = 3),
1816 None,
1817 None,
1818 )
1819 .await?;
1820 let view_a: ProcMeshRef = proc_mesh.range("procs", 0..2)?; let view_b: ProcMeshRef = proc_mesh.range("procs", 0..1)?; let view_c: ProcMeshRef = proc_mesh.range("procs", 2..3)?; let owner = RdmaManagerOwnerActor::new((), &ActorEnvironment::default()).await?;
1825 let owner_handle = client.spawn(owner);
1826 let subject = owner_handle.actor_addr().clone();
1827
1828 let (entered_tx, entered_rx) = oneshot::channel();
1831 let (release_tx, release_rx) = oneshot::channel();
1832 owner_handle.post(
1833 client,
1834 Pause {
1835 entered: entered_tx,
1836 release: release_rx,
1837 },
1838 );
1839 entered_rx.await.expect("owner entered Pause");
1840
1841 let (ra, rxa) = client.open_once_port::<Result<(), RdmaInitError>>();
1842 owner_handle
1843 .ensure_rdma_manager(client, view_a.clone(), ra.bind())
1844 .await?;
1845 let (rb, rxb) = client.open_once_port::<Result<(), RdmaInitError>>();
1846 owner_handle
1847 .ensure_rdma_manager(client, view_b.clone(), rb.bind())
1848 .await?;
1849 let (rc, _rxc) = client.open_once_port::<Result<(), RdmaInitError>>();
1850 owner_handle
1851 .ensure_rdma_manager(client, view_c.clone(), rc.bind())
1852 .await?;
1853 let (hta, hrxa) = oneshot::channel();
1854 owner_handle.post(
1855 client,
1856 HoldPending {
1857 key: view_a.clone(),
1858 reply: hta,
1859 },
1860 );
1861 let (htb, hrxb) = oneshot::channel();
1862 owner_handle.post(
1863 client,
1864 HoldPending {
1865 key: view_b.clone(),
1866 reply: htb,
1867 },
1868 );
1869 let (htc, hrxc) = oneshot::channel();
1870 owner_handle.post(
1871 client,
1872 HoldPending {
1873 key: view_c.clone(),
1874 reply: htc,
1875 },
1876 );
1877 let _ = release_tx.send(());
1878
1879 let (_entry_a, controller_a, ctrl_a) = hrxa.await?.expect("view A held pending");
1880 let (_entry_b, controller_b, ctrl_b) = hrxb.await?.expect("view B held pending");
1881 let (_entry_c, controller_c, _ctrl_c) = hrxc.await?.expect("view C held pending");
1882 assert_ne!(
1883 controller_a, controller_b,
1884 "distinct views, distinct controllers"
1885 );
1886 assert_ne!(
1887 controller_a, controller_c,
1888 "distinct views, distinct controllers"
1889 );
1890
1891 let (ra2, rxa2) = client.open_once_port::<Result<(), RdmaInitError>>();
1894 owner_handle
1895 .ensure_rdma_manager(client, view_a.clone(), ra2.bind())
1896 .await?;
1897 wait_for_remaining(&owner_handle, client, view_a.clone(), 1).await;
1898 let a_probe = probe(&owner_handle, client, view_a.clone()).await;
1899 assert_eq!(
1900 a_probe.waiter_count, 2,
1901 "second ensure joined A as a waiter"
1902 );
1903 assert_eq!(
1904 a_probe.controller_id,
1905 Some(controller_a.clone()),
1906 "probe exposes A's recorded controller id (the RMO-7 match key)",
1907 );
1908 drop(rxa2);
1909
1910 owner_handle.post(client, None::<MeshFailure>);
1914 owner_handle.post(client, make_mesh_failure(None, subject.clone()));
1915 let unknown = owner_handle.actor_addr().id().clone();
1916 owner_handle.post(client, make_mesh_failure(Some(unknown), subject.clone()));
1917 let _ = owner_subscriber_count(&owner_handle, client, ctrl_a.clone()).await;
1918 for v in [&view_a, &view_b, &view_c] {
1919 assert_eq!(
1920 probe(&owner_handle, client, v.clone()).await.state,
1921 EntryStateTag::Pending,
1922 "no pending view resolves on None / missing / unknown reporters",
1923 );
1924 }
1925 assert_eq!(
1926 probe(&owner_handle, client, view_a.clone())
1927 .await
1928 .waiter_count,
1929 2,
1930 "A's waiters are intact before the matching report",
1931 );
1932
1933 owner_handle.post(
1937 client,
1938 make_mesh_failure(Some(controller_a.clone()), subject.clone()),
1939 );
1940 let a_err = rxa
1941 .recv()
1942 .await?
1943 .expect_err("view A resolves to a typed failure");
1944 assert!(
1945 matches!(a_err, RdmaInitError::InitFailed(_)),
1946 "controller report resolves as InitFailed, got {a_err:?}",
1947 );
1948 assert_eq!(
1949 probe(&owner_handle, client, view_a.clone()).await.state,
1950 EntryStateTag::Failed,
1951 );
1952 assert_eq!(
1953 probe(&owner_handle, client, view_b.clone()).await.state,
1954 EntryStateTag::Pending,
1955 "B (overlapping proc) is untouched by A's live report",
1956 );
1957 assert_eq!(
1958 probe(&owner_handle, client, view_c.clone()).await.state,
1959 EntryStateTag::Pending,
1960 );
1961
1962 owner_handle.post(
1965 client,
1966 Some(make_mesh_failure(
1967 Some(controller_a.clone()),
1968 subject.clone(),
1969 )),
1970 );
1971 let (rca, rcarx) = client.open_once_port::<Result<(), RdmaInitError>>();
1972 owner_handle
1973 .ensure_rdma_manager(client, view_a.clone(), rca.bind())
1974 .await?;
1975 assert_eq!(
1976 rcarx.recv().await?.expect_err("A cached terminal error"),
1977 a_err,
1978 "A's cached error is immutable across a late subscriber-route duplicate",
1979 );
1980
1981 let (ack_tx, ack_rx) = oneshot::channel();
1986 owner_handle.post(
1987 client,
1988 UnsubscribeController {
1989 controller: ctrl_b.clone(),
1990 ack: ack_tx,
1991 },
1992 );
1993 ack_rx.await?;
1994 assert_eq!(
1995 owner_subscriber_count(&owner_handle, client, ctrl_b).await,
1996 0,
1997 "B unsubscribed before the overlap proof",
1998 );
1999
2000 let ctrl_a_handle = ctrl_a.downcast_handle(client).expect("local A controller");
2001 ctrl_a_handle.kill("test: kill A controller")?;
2002 let b_err = timeout(Duration::from_secs(30), rxb.recv())
2003 .await
2004 .expect("B resolved after A-controller kill")?
2005 .expect_err("B fails via overlap from the Failed source");
2006 assert!(
2007 matches!(b_err, RdmaInitError::Supervision(_)),
2008 "B: {b_err:?}"
2009 );
2010 let a_status = ctrl_a_handle.await;
2014 assert!(
2015 matches!(a_status, ActorStatus::Failed(_)),
2016 "A controller failed after kill: {a_status:?}",
2017 );
2018 assert_eq!(
2019 probe(&owner_handle, client, view_c.clone()).await.state,
2020 EntryStateTag::Pending,
2021 "disjoint C untouched by A-controller overlap",
2022 );
2023
2024 let (rca2, rca2rx) = client.open_once_port::<Result<(), RdmaInitError>>();
2026 owner_handle
2027 .ensure_rdma_manager(client, view_a.clone(), rca2.bind())
2028 .await?;
2029 assert_eq!(rca2rx.recv().await?.expect_err("A cached"), a_err);
2030
2031 owner_handle.stop("test complete")?;
2032 let _ = owner_handle.await;
2033 host_mesh.shutdown(client).await?;
2034 Ok(())
2035 }
2036
2037 #[timed_test::async_timed_test(timeout_secs = 60)]
2045 async fn ready_absorbs_late_mesh_failure() -> anyhow::Result<()> {
2046 let config = hyperactor_config::global::lock();
2047 let _guard = config.override_key(crate::config::RDMA_ALLOW_TCP_FALLBACK, true);
2048
2049 let cx = context().await;
2050 let client = cx.actor_instance;
2051
2052 let mut host_mesh = HostMesh::local_in_process().await?;
2053 let proc_mesh = host_mesh
2054 .spawn(
2055 client,
2056 "rdma_procs",
2057 hyperactor_mesh::extent!(procs = 1),
2058 None,
2059 None,
2060 )
2061 .await?;
2062 let key: ProcMeshRef = (*proc_mesh).clone();
2063
2064 let owner = RdmaManagerOwnerActor::new((), &ActorEnvironment::default()).await?;
2065 let owner_handle = client.spawn(owner);
2066 let subject = owner_handle.actor_addr().clone();
2067
2068 let (entered_tx, entered_rx) = oneshot::channel();
2071 let (release_tx, release_rx) = oneshot::channel();
2072 owner_handle.post(
2073 client,
2074 Pause {
2075 entered: entered_tx,
2076 release: release_rx,
2077 },
2078 );
2079 entered_rx.await.expect("owner entered Pause");
2080
2081 let (r, rx) = client.open_once_port::<Result<(), RdmaInitError>>();
2082 owner_handle
2083 .ensure_rdma_manager(client, key.clone(), r.bind())
2084 .await?;
2085 let (ht, hrx) = oneshot::channel();
2086 owner_handle.post(
2087 client,
2088 HoldPending {
2089 key: key.clone(),
2090 reply: ht,
2091 },
2092 );
2093 let _ = release_tx.send(());
2094
2095 let (entry, controller, ctrl) = hrx.await?.expect("held pending");
2096 assert_eq!(
2098 owner_subscriber_count(&owner_handle, client, ctrl.clone()).await,
2099 1,
2100 "a held pending entry has exactly one subscriber",
2101 );
2102 wait_for_remaining(&owner_handle, client, key.clone(), 1).await;
2103 owner_handle.post(client, ReadyAck { entry });
2104 assert_eq!(rx.recv().await?, Ok(()));
2105 assert_eq!(
2106 probe(&owner_handle, client, key.clone()).await.state,
2107 EntryStateTag::Ready,
2108 );
2109 assert_eq!(
2112 owner_subscriber_count(&owner_handle, client, ctrl).await,
2113 0,
2114 "reaching Ready removes the controller subscription",
2115 );
2116
2117 owner_handle.post(
2119 client,
2120 make_mesh_failure(Some(controller.clone()), subject.clone()),
2121 );
2122 owner_handle.post(client, Some(make_mesh_failure(Some(controller), subject)));
2123 assert_eq!(
2124 probe(&owner_handle, client, key.clone()).await.state,
2125 EntryStateTag::Ready,
2126 );
2127
2128 let (r2, rx2) = client.open_once_port::<Result<(), RdmaInitError>>();
2129 owner_handle
2130 .ensure_rdma_manager(client, key.clone(), r2.bind())
2131 .await?;
2132 assert_eq!(rx2.recv().await?, Ok(()), "Ready is cached");
2133
2134 owner_handle.stop("test complete")?;
2135 let _ = owner_handle.await;
2136 host_mesh.shutdown(client).await?;
2137 Ok(())
2138 }
2139
2140 #[timed_test::async_timed_test(timeout_secs = 60)]
2151 async fn subscription_lifetime_handles_heartbeat_replay_and_duplicates() -> anyhow::Result<()> {
2152 let config = hyperactor_config::global::lock();
2153 let _guard = config.override_key(crate::config::RDMA_ALLOW_TCP_FALLBACK, true);
2154
2155 let cx = context().await;
2156 let client = cx.actor_instance;
2157
2158 let mut host_mesh = HostMesh::local_in_process().await?;
2159 let proc_mesh = host_mesh
2160 .spawn(
2161 client,
2162 "rdma_procs",
2163 hyperactor_mesh::extent!(procs = 1),
2164 None,
2165 None,
2166 )
2167 .await?;
2168 let key: ProcMeshRef = (*proc_mesh).clone();
2169
2170 let owner = RdmaManagerOwnerActor::new((), &ActorEnvironment::default()).await?;
2171 let owner_handle = client.spawn(owner);
2172
2173 let (entered_tx, entered_rx) = oneshot::channel();
2176 let (release_tx, release_rx) = oneshot::channel();
2177 owner_handle.post(
2178 client,
2179 Pause {
2180 entered: entered_tx,
2181 release: release_rx,
2182 },
2183 );
2184 entered_rx.await.expect("owner entered Pause");
2185
2186 let (r, rx) = client.open_once_port::<Result<(), RdmaInitError>>();
2188 owner_handle
2189 .ensure_rdma_manager(client, key.clone(), r.bind())
2190 .await?;
2191 let (ht, hrx) = oneshot::channel();
2192 owner_handle.post(
2193 client,
2194 HoldPending {
2195 key: key.clone(),
2196 reply: ht,
2197 },
2198 );
2199 let _ = release_tx.send(());
2200 let (_entry, _controller_id, controller) = hrx.await?.expect("held pending");
2201
2202 assert_eq!(
2204 owner_subscriber_count(&owner_handle, client, controller.clone()).await,
2205 1,
2206 "ensure registered the owner as the sole subscriber",
2207 );
2208
2209 owner_handle.post(client, None::<MeshFailure>);
2211 assert_eq!(
2212 probe(&owner_handle, client, key.clone()).await.state,
2213 EntryStateTag::Pending,
2214 );
2215
2216 let (mid_tx, mut mid_rx) = client.open_port::<usize>();
2220 owner_handle.post(
2221 client,
2222 ReplaySequence {
2223 controller: controller.clone(),
2224 midpoint_port: mid_tx.bind(),
2225 },
2226 );
2227 assert_eq!(
2228 mid_rx.recv().await?,
2229 0,
2230 "subscriber removed before the latch"
2231 );
2232
2233 let err = timeout(Duration::from_secs(30), rx.recv())
2234 .await
2235 .expect("replay resolved the waiter")?
2236 .expect_err("replayed whole-view stop resolves the barrier");
2237 assert!(
2238 matches!(err, RdmaInitError::InitFailed(_)),
2239 "replay: {err:?}"
2240 );
2241 assert_eq!(
2242 probe(&owner_handle, client, key.clone()).await.state,
2243 EntryStateTag::Failed,
2244 );
2245
2246 assert_eq!(
2250 owner_subscriber_count(&owner_handle, client, controller.clone()).await,
2251 0,
2252 "transition's Unsubscribe removed the replay subscription",
2253 );
2254
2255 let (cap_tx, mut cap_rx) = client.open_port::<Option<MeshFailure>>();
2258 controller.post(client, Subscribe(cap_tx.bind()));
2259 let latched = timeout(Duration::from_secs(30), cap_rx.recv())
2260 .await
2261 .expect("controller replayed the latched report")?
2262 .expect("latched report is Some");
2263 controller.post(client, Unsubscribe(cap_tx.bind()));
2264 let (cap_cnt_tx, mut cap_cnt_rx) = client.open_port::<usize>();
2268 controller.post(client, GetSubscriberCount(cap_cnt_tx.bind()));
2269 assert_eq!(
2270 cap_cnt_rx.recv().await?,
2271 0,
2272 "capture subscription removed before replaying duplicates",
2273 );
2274
2275 owner_handle.post(client, latched.clone());
2276 owner_handle.post(client, Some(latched));
2277 let (r2, r2rx) = client.open_once_port::<Result<(), RdmaInitError>>();
2278 owner_handle
2279 .ensure_rdma_manager(client, key.clone(), r2.bind())
2280 .await?;
2281 assert_eq!(
2282 r2rx.recv().await?.expect_err("cached"),
2283 err,
2284 "cached error immutable across late duplicates on both routes",
2285 );
2286
2287 owner_handle.stop("test complete")?;
2288 let _ = owner_handle.await;
2289 host_mesh.shutdown(client).await?;
2290 Ok(())
2291 }
2292
2293 #[timed_test::async_timed_test(timeout_secs = 60)]
2302 async fn ready_controller_termination_resolves_only_overlapping_pending_views()
2303 -> anyhow::Result<()> {
2304 let config = hyperactor_config::global::lock();
2305 let _guard = config.override_key(crate::config::RDMA_ALLOW_TCP_FALLBACK, true);
2306
2307 let cx = context().await;
2308 let client = cx.actor_instance;
2309
2310 let mut host_mesh = HostMesh::local_in_process().await?;
2311 let proc_mesh = host_mesh
2312 .spawn(
2313 client,
2314 "rdma_procs",
2315 hyperactor_mesh::extent!(procs = 3),
2316 None,
2317 None,
2318 )
2319 .await?;
2320 let view_a: ProcMeshRef = proc_mesh.range("procs", 0..2)?; let view_b: ProcMeshRef = proc_mesh.range("procs", 0..1)?; let view_c: ProcMeshRef = proc_mesh.range("procs", 1..2)?; let view_d: ProcMeshRef = proc_mesh.range("procs", 2..3)?; let owner = RdmaManagerOwnerActor::new((), &ActorEnvironment::default()).await?;
2326 let owner_handle = client.spawn(owner);
2327
2328 let (a_entered_tx, a_entered_rx) = oneshot::channel();
2332 let (a_release_tx, a_release_rx) = oneshot::channel();
2333 owner_handle.post(
2334 client,
2335 Pause {
2336 entered: a_entered_tx,
2337 release: a_release_rx,
2338 },
2339 );
2340 a_entered_rx.await.expect("owner entered Pause (A)");
2341 let (ra, _rxa) = client.open_once_port::<Result<(), RdmaInitError>>();
2342 owner_handle
2343 .ensure_rdma_manager(client, view_a.clone(), ra.bind())
2344 .await?;
2345 let (hta, hrxa) = oneshot::channel();
2346 owner_handle.post(
2347 client,
2348 HoldPending {
2349 key: view_a.clone(),
2350 reply: hta,
2351 },
2352 );
2353 let _ = a_release_tx.send(());
2354 let (entry_a, _ctrl_a_id, ctrl_a) = hrxa.await?.expect("A held pending");
2355 wait_for_remaining(&owner_handle, client, view_a.clone(), 1).await;
2356 owner_handle.post(client, ReadyAck { entry: entry_a });
2357 wait_until_state(&owner_handle, client, view_a.clone(), EntryStateTag::Ready).await;
2358 assert_eq!(
2359 owner_subscriber_count(&owner_handle, client, ctrl_a.clone()).await,
2360 0,
2361 "a Ready controller is unsubscribed (RMO-16)",
2362 );
2363 let ctrl_a_handle = ctrl_a.downcast_handle(client).expect("local A controller");
2364
2365 let (bcd_entered_tx, bcd_entered_rx) = oneshot::channel();
2369 let (bcd_release_tx, bcd_release_rx) = oneshot::channel();
2370 owner_handle.post(
2371 client,
2372 Pause {
2373 entered: bcd_entered_tx,
2374 release: bcd_release_rx,
2375 },
2376 );
2377 bcd_entered_rx.await.expect("owner entered Pause (B/C/D)");
2378 let (rb, rxb) = client.open_once_port::<Result<(), RdmaInitError>>();
2379 owner_handle
2380 .ensure_rdma_manager(client, view_b.clone(), rb.bind())
2381 .await?;
2382 let (rc, rxc) = client.open_once_port::<Result<(), RdmaInitError>>();
2383 owner_handle
2384 .ensure_rdma_manager(client, view_c.clone(), rc.bind())
2385 .await?;
2386 let (rd, rxd) = client.open_once_port::<Result<(), RdmaInitError>>();
2387 owner_handle
2388 .ensure_rdma_manager(client, view_d.clone(), rd.bind())
2389 .await?;
2390 let (htb, hrxb) = oneshot::channel();
2391 owner_handle.post(
2392 client,
2393 HoldPending {
2394 key: view_b.clone(),
2395 reply: htb,
2396 },
2397 );
2398 let (htc, hrxc) = oneshot::channel();
2399 owner_handle.post(
2400 client,
2401 HoldPending {
2402 key: view_c.clone(),
2403 reply: htc,
2404 },
2405 );
2406 let (htd, hrxd) = oneshot::channel();
2407 owner_handle.post(
2408 client,
2409 HoldPending {
2410 key: view_d.clone(),
2411 reply: htd,
2412 },
2413 );
2414 let _ = bcd_release_tx.send(());
2415 let (_eb, _cb_id, ctrl_b) = hrxb.await?.expect("B held pending");
2416 let (_ec, _cc_id, ctrl_c) = hrxc.await?.expect("C held pending");
2417 let (_ed, _cd_id, ctrl_d) = hrxd.await?.expect("D held pending");
2418
2419 for ctrl in [ctrl_b.clone(), ctrl_c.clone()] {
2422 let (ack_tx, ack_rx) = oneshot::channel();
2423 owner_handle.post(
2424 client,
2425 UnsubscribeController {
2426 controller: ctrl,
2427 ack: ack_tx,
2428 },
2429 );
2430 ack_rx.await?;
2431 }
2432 assert_eq!(
2433 owner_subscriber_count(&owner_handle, client, ctrl_b.clone()).await,
2434 0,
2435 "B unsubscribed before the overlap proof",
2436 );
2437 assert_eq!(
2438 owner_subscriber_count(&owner_handle, client, ctrl_c).await,
2439 0,
2440 "C unsubscribed before the overlap proof",
2441 );
2442
2443 ctrl_a_handle.stop("test: stop A controller")?;
2447 let b_err = timeout(Duration::from_secs(30), rxb.recv())
2448 .await
2449 .expect("B resolved after A-controller stop")?
2450 .expect_err("B fails via overlap");
2451 assert!(
2452 matches!(b_err, RdmaInitError::Supervision(_)),
2453 "B: {b_err:?}"
2454 );
2455 let c_err = timeout(Duration::from_secs(30), rxc.recv())
2456 .await
2457 .expect("C resolved after A-controller stop")?
2458 .expect_err("C fails via overlap");
2459 assert!(
2460 matches!(c_err, RdmaInitError::Supervision(_)),
2461 "C: {c_err:?}"
2462 );
2463 let a_status = ctrl_a_handle.await;
2467 assert!(
2468 matches!(a_status, ActorStatus::Stopped(_)),
2469 "A controller stopped cleanly: {a_status:?}",
2470 );
2471
2472 assert_eq!(
2474 probe(&owner_handle, client, view_d.clone()).await.state,
2475 EntryStateTag::Pending,
2476 "disjoint D untouched by A-controller overlap",
2477 );
2478 let (ra2, ra2rx) = client.open_once_port::<Result<(), RdmaInitError>>();
2479 owner_handle
2480 .ensure_rdma_manager(client, view_a.clone(), ra2.bind())
2481 .await?;
2482 assert_eq!(ra2rx.recv().await?, Ok(()), "A source stays cached Ready");
2483
2484 let ctrl_d_handle = ctrl_d.downcast_handle(client).expect("local D controller");
2489 ctrl_d_handle.kill("test: kill D controller")?;
2490 let d_err = timeout(Duration::from_secs(30), rxd.recv())
2491 .await
2492 .expect("D resolved after its controller was killed")?
2493 .expect_err("D fails on its own controller termination");
2494 assert!(
2495 matches!(d_err, RdmaInitError::Supervision(_)),
2496 "D: {d_err:?}"
2497 );
2498 let d_status = ctrl_d_handle.await;
2500 assert!(
2501 matches!(d_status, ActorStatus::Failed(_)),
2502 "D controller failed after kill: {d_status:?}",
2503 );
2504
2505 owner_handle.stop("test complete")?;
2506 let _ = owner_handle.await;
2507 host_mesh.shutdown(client).await?;
2508 Ok(())
2509 }
2510
2511 #[timed_test::async_timed_test(timeout_secs = 60)]
2517 async fn invalid_reference_does_not_kill_owner() -> anyhow::Result<()> {
2518 let config = hyperactor_config::global::lock();
2519 let _guard = config.override_key(crate::config::RDMA_ALLOW_TCP_FALLBACK, true);
2520
2521 let cx = context().await;
2522 let client = cx.actor_instance;
2523
2524 let mut host_mesh = HostMesh::local_in_process().await?;
2525 let proc_mesh = host_mesh
2526 .spawn(
2527 client,
2528 "rdma_procs",
2529 hyperactor_mesh::extent!(procs = 1),
2530 None,
2531 None,
2532 )
2533 .await?;
2534 let key: ProcMeshRef = (*proc_mesh).clone();
2535
2536 let owner = RdmaManagerOwnerActor::new((), &ActorEnvironment::default()).await?;
2537 let owner_handle = client.spawn(owner);
2538 let owner_addr = owner_handle.actor_addr().clone();
2539
2540 let (entered_tx, entered_rx) = oneshot::channel();
2544 let (release_tx, release_rx) = oneshot::channel();
2545 owner_handle.post(
2546 client,
2547 Pause {
2548 entered: entered_tx,
2549 release: release_rx,
2550 },
2551 );
2552 entered_rx.await.expect("owner entered Pause");
2553 let (r, rx) = client.open_once_port::<Result<(), RdmaInitError>>();
2554 owner_handle
2555 .ensure_rdma_manager(client, key.clone(), r.bind())
2556 .await?;
2557 let (ht, hrx) = oneshot::channel();
2558 owner_handle.post(
2559 client,
2560 HoldPending {
2561 key: key.clone(),
2562 reply: ht,
2563 },
2564 );
2565 let _ = release_tx.send(());
2566 let (entry, _controller_id, _controller) = hrx.await?.expect("held pending");
2567
2568 let mut env = MessageEnvelope::new(
2571 owner_addr.clone(),
2572 owner_addr.port_addr(0.into()),
2573 wirevalue::Any::serialize(&0u64).unwrap(),
2574 Flattrs::new(),
2575 );
2576 env.push_delivery_failure(DeliveryFailure::new(InvalidReference::new(
2577 owner_addr.clone(),
2578 InvalidReferenceReason::ActorNotExist,
2579 )));
2580 let undeliverable_port =
2581 PortRef::<Undeliverable<MessageEnvelope>>::attest_handler_port(&owner_addr);
2582 undeliverable_port.post(client, Undeliverable::Returned(env));
2583
2584 wait_for_remaining(&owner_handle, client, key.clone(), 1).await;
2586 assert_eq!(
2587 probe(&owner_handle, client, key.clone()).await.state,
2588 EntryStateTag::Pending,
2589 "an invalid-reference return must not resolve a pending entry",
2590 );
2591
2592 owner_handle.post(client, ReadyAck { entry });
2595 assert_eq!(
2596 rx.recv().await?,
2597 Ok(()),
2598 "held entry resolves normally after the swallow",
2599 );
2600 let (r2, rx2) = client.open_once_port::<Result<(), RdmaInitError>>();
2601 owner_handle
2602 .ensure_rdma_manager(client, key.clone(), r2.bind())
2603 .await?;
2604 assert_eq!(
2605 rx2.recv().await?,
2606 Ok(()),
2607 "owner alive and caching after an invalid-reference return",
2608 );
2609
2610 owner_handle.stop("test complete")?;
2611 let _ = owner_handle.await;
2612 host_mesh.shutdown(client).await?;
2613 Ok(())
2614 }
2615}