Skip to main content

monarch_rdma/
rdma_manager_owner.rs

1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of this source tree.
7 */
8
9//! # RDMA Manager Owner Actor
10//!
11//! Controllerless-singleton coordinator that fronts the per-proc `rdma_manager`
12//! service. Given a target proc mesh it get-or-spawns a mesh of
13//! [`RdmaManagerActor`]s (creating or reusing the per-proc actor on each proc),
14//! waits until every rank has finished `init()` and posted a [`ReadyAck`], then
15//! answers the caller on a reply port with `Result<(), RdmaInitError>`.
16//!
17//! ## Topology
18//!
19//! Three distinct objects, kept separate throughout this module:
20//!
21//! 1. **Owner entry** — one [`MeshState`], identified by a small owner-local
22//!    [`EntryId`], per requested `ProcMeshRef` view (`by_mesh` maps the view
23//!    to its `EntryId`). Two overlapping `ProcMeshRef` views are *distinct*
24//!    entries even when they share procs.
25//! 2. **Manager mesh + controller** — the first `EnsureRdmaManager` for a view
26//!    calls `spawn_service`, which materializes a new
27//!    `ActorMesh<RdmaManagerActor>` view plus one `ActorMeshController`, and
28//!    *creates or reuses* the per-proc actor on each proc in the view. The
29//!    entry retains the mesh to cast to.
30//! 3. **Per-proc service actor** — on each proc the `rdma_manager` service is a
31//!    per-proc singleton (by reserved name). Overlapping views reuse the same
32//!    physical actor on a shared proc; the physical actors are shared while the
33//!    owner entries and mesh views stay distinct.
34//!
35//! ## Correlation key
36//!
37//! A `ProcMeshRef` carries every rank ref, so hashing or serializing one is
38//! O(N) in the rank count. To keep readiness O(N) overall rather than O(N²),
39//! the owner hashes the `ProcMeshRef` only on the ensure path — one lookup
40//! hash per ensure, plus one insertion hash when a new view is created —
41//! never on the ack path, and thereafter correlates by the small [`EntryId`]:
42//! `RdmaManagerReady` and `ReadyAck` carry the `EntryId`, so the N per-rank
43//! messages neither carry nor hash a full `ProcMeshRef`, and the
44//! per-`EntryId` `entries` map is the hot path.
45//!
46//! ## Invariants (RMO-*)
47//!
48//! - **RMO-1** (single owner): Exactly one owner is a statically declared service
49//!   of the program's root ProcAgent. Callers may live anywhere; the root owns the
50//!   service lifetime, and requester exit does not tear it down.
51//! - **RMO-2** (terminal-absorbing): `Ready`/`Failed` are terminal — an
52//!   `EnsureRdmaManager` for a resolved view replies from cache, and every
53//!   resolution path (`ReadyAck`, a live controller report, or controller-child
54//!   supervision, all through [`RdmaManagerOwnerActor::transition`]) no-ops on a
55//!   non-`Pending` entry. A terminal controller-child event may use its retained
56//!   controller id to recover the source view, whose proc overlap then selects
57//!   pending siblings, without changing the source (RMO-17).
58//! - **RMO-3** (single-transition guard): [`RdmaManagerOwnerActor::transition`]
59//!   is the sole `Pending -> terminal` mutator; it drains `waiters` on resolve.
60//! - **RMO-4** (operational inputs never fail the owner): spawn, cast, a live
61//!   controller report, direct controller-child termination, an invalid-reference
62//!   return, and a departed waiter all resolve their entry or are absorbed
63//!   without returning a handler error. This is not immunity from unrelated
64//!   runtime faults (e.g. the default oversized-frame undeliverable path).
65//! - **RMO-5** (non-returning replies): Every reply ref gets
66//!   `return_undeliverable(false)` before it is posted or stashed.
67//! - **RMO-6** (readiness-after-init): `Ready` is set only once every rank's
68//!   post-`init()` `ReadyAck` has arrived (`remaining_acks == 0`).
69//! - **RMO-7** (typed controller attribution): A live controller report — on the
70//!   owner sink or the subscriber stream — is matched to its pending source only
71//!   by `MeshFailure.reporting_controller` (an `ActorId`), compared against the
72//!   entry's recorded `controller_id`. Event subject, headers, mesh names, and
73//!   ranks are never attribution inputs. Proc identities are used only for a
74//!   terminal controller, after its id has selected the source view.
75//! - **RMO-8** (manager per-proc-shared): Each target proc has at most one
76//!   `rdma_manager` service actor, so manager intersection is exactly
77//!   `ProcMeshRef` intersection. Overlapping views reuse the per-proc actor while
78//!   keeping distinct owner entries; the existing `by_mesh` membership drives
79//!   cold-path overlap without a per-rank index.
80//! - **RMO-9** (no artificial timer): Readiness has no timeout; a view resolves
81//!   only from a spawn/cast outcome, its `ReadyAck`s, a controller report, or
82//!   controller supervision. This barrier covers *delivered* terminal inputs: a
83//!   live controller that stays up but stops servicing its mailbox emits no
84//!   report, heartbeat, or terminal child event, so its entry can remain
85//!   `Pending` indefinitely. TODO: evaluate an external actor monitor if
86//!   silent-stall detection ever becomes a requirement; none is added here.
87//! - **RMO-10** (bounded-spawn is the only block): `spawn_service().await` is
88//!   the sole await; the ack gather is message-driven.
89//! - **RMO-11** (retain-to-message, not teardown): The retained manager mesh is
90//!   kept to cast to; its `Drop` is inert, so retention is not teardown.
91//! - **RMO-13** (controller topology): The owner service is controllerless;
92//!   every manager mesh it creates is controller-ful, with its controller owned
93//!   as a direct child of the owner — which is why a controller's own terminal
94//!   status arrives on the owner's `handle_supervision_event` channel.
95//! - **RMO-14** (delivered terminal-input coverage): The owner resolves on every
96//!   *delivered, attributable* terminal input — an error owner-sink `MeshFailure`
97//!   or a subscriber `Some(MeshFailure)` (error or a published clean terminal
98//!   report for `Stopped`/`NotExist`/`Timeout`) carrying a known
99//!   `reporting_controller`, and
100//!   any terminal controller-child supervision event for a known controller —
101//!   routing each through the source (RMO-7) or overlap (RMO-17) reduction. A
102//!   delivered input with no or unknown attribution is absorbed, not resolved,
103//!   and no claim is made about a controller that never delivers such an input
104//!   (the RMO-9 liveness boundary).
105//! - **RMO-15** (registration ordering): a new view inserts `Pending`, then posts
106//!   `Subscribe`, then casts readiness. The controller replays a latched report
107//!   on `Subscribe` and broadcasts later reports, so no report it publishes can
108//!   race registration.
109//! - **RMO-16** (pending-only subscription): at most one subscription per
110//!   controller while its entry is pending; a `None` heartbeat is inert; the sole
111//!   `Pending -> terminal` transition posts a non-returning `Unsubscribe`, applied
112//!   asynchronously by the controller (which may itself be the failed subject).
113//! - **RMO-17** (controller-view overlap): the controller id is retained across
114//!   `Pending`, `Ready`, and controller-backed `Failed`, associated with the
115//!   existing `ProcMeshRef` key. A live report stays source-local; controller
116//!   termination fails every pending view intersecting the source's full proc
117//!   view. Terminal sources and disjoint pending views are left unchanged.
118//! - **RMR-1** (ack-after-init): Each per-proc manager posts `ReadyAck` from
119//!   its `RdmaManagerReady` handler, which runs strictly after `init()`.
120//!
121//! (`RMO-12`, a retired header-stamping premise, is intentionally left unused.)
122//!
123//! The owner implements `Handler<MeshFailure>` (the error-only owner sink
124//! required by `spawn_service`) and `Handler<Option<MeshFailure>>` (the manager
125//! controller's subscriber stream, which also carries published clean terminal
126//! reports and `None` heartbeats). As the controller's parent, it additionally
127//! receives the controller's own terminal supervision events. Together these inputs
128//! cover the delivered terminal inputs of the initialization-barrier failure
129//! algebra (RMO-14); a live but silent controller is out of scope (RMO-9). The
130//! owner is spawned controllerless, so spawning it imposes no such bound.
131
132use 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/// Owner-local handle for a `ProcMeshRef`.
169///
170/// A `ProcMeshRef` contains O(N) rank references. Carrying it in each of N
171/// readiness acknowledgements would make correlation O(N²). The ensure path
172/// hashes the `ProcMeshRef` once to obtain this id; the N per-rank
173/// `RdmaManagerReady`/`ReadyAck` messages then carry only this small id, so
174/// readiness correlation stays O(N).
175#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
176pub struct EntryId(u64);
177
178/// Owner -> manager-mesh readiness cast, delivered to every per-proc
179/// `RdmaManagerActor`. Carries the owner ref so each rank can post [`ReadyAck`]
180/// back after its own `init()` (RMR-1).
181#[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/// Per-proc manager -> owner readiness acknowledgement, one per rank.
198#[derive(Handler, HandleClient, RefClient, Debug, Serialize, Deserialize, Named)]
199pub struct ReadyAck {
200    pub entry: EntryId,
201}
202wirevalue::register_type!(ReadyAck);
203
204/// Caller -> owner request to ensure a proc mesh's per-proc managers are ready.
205///
206/// `reply` is a plain port field (not `#[reply]`) so the handler can stash
207/// it on a `Pending` entry and post the result once the mesh is ready.
208#[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/// Per-view coordination state, held in `entries` keyed by [`EntryId`].
216#[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        /// The manager-mesh view returned by `spawn_service`, containing one
224        /// actor reference per target proc. Overlapping views may reference the
225        /// same per-proc service actors. Retained for the readiness cast and
226        /// `Ready` state; `Drop` is inert and does not control teardown
227        /// (RMO-11).
228        manager_mesh: ActorMesh<RdmaManagerActor>,
229        /// Controller `ActorId` for this mesh view, captured at spawn.
230        /// Readiness is correlated by the small `EntryId`; this id is the
231        /// separate key that attributes a controller failure — a typed
232        /// `MeshFailure` (RMO-7) or the controller child's own supervision event
233        /// (RMO-14) — to this entry, and drives overlap invalidation (RMO-17).
234        controller_id: ActorId,
235        /// The exact owner `Handler<Option<MeshFailure>>` port registered with
236        /// this controller's subscriber set while pending (RMO-15/RMO-16). The
237        /// sole terminal transition posts `Unsubscribe` to request its removal;
238        /// the controller applies that removal asynchronously when it handles the
239        /// message.
240        subscriber: PortRef<Option<MeshFailure>>,
241        /// Outstanding post-`init()` `ReadyAck`s (RMO-6, RMR-1): decremented as
242        /// each rank acks; the entry resolves to `Ready` when it reaches 0.
243        remaining_acks: usize,
244        /// Callers awaiting this view's result. Each reply ref is non-returning
245        /// (RMO-5) and drained exactly once by `transition` on resolution (RMO-3).
246        waiters: Vec<OncePortRef<Result<(), RdmaInitError>>>,
247    },
248    Ready {
249        /// Retained after resolution so its `Drop` stays inert (RMO-11); the
250        /// per-proc service actors are not torn down when the view is `Ready`.
251        manager_mesh: ActorMesh<RdmaManagerActor>,
252        /// Retained so a later terminal of this controller can invalidate other
253        /// still-`Pending` views sharing its per-proc managers (RMO-17), without
254        /// rewriting this cached success (RMO-2).
255        controller_id: ActorId,
256    },
257    Failed {
258        /// The cached terminal error, returned to every later `EnsureRdmaManager`
259        /// for this view and never rewritten once set (RMO-2).
260        err: RdmaInitError,
261        /// `Some` for a controller-backed failure; `None` only for a
262        /// pre-controller `spawn_service` failure. Retained for RMO-17 overlap.
263        controller_id: Option<ActorId>,
264    },
265}
266
267impl MeshState {
268    /// The retained controller id for any controller-backed state; `None` only
269    /// for a pre-controller `spawn_service` failure. Locates the source entry of
270    /// a terminal controller event (RMO-17).
271    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
281/// The logical `ProcId`s spanned by a `ProcMeshRef`. Per RMO-8 (`rdma_manager`
282/// is a per-proc singleton), two views share a manager iff their proc-id sets
283/// intersect, so this drives cold-path overlap without a per-rank index.
284fn 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
291/// Whether any proc in `mesh` is in `procs`. Per RMO-8 (`rdma_manager` is a
292/// per-proc singleton) this is exactly shared-manager overlap. Scanning each
293/// candidate view against the single materialized source set avoids allocating a
294/// fresh `HashSet` per view on the cold controller-termination path.
295fn 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
300/// Static client-root service name for the RDMA manager owner.
301pub 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    /// Maps each requested view to its entry id. The `ProcMeshRef` is hashed
315    /// here (one lookup per ensure, plus one insertion for a new view), never
316    /// on the ack path.
317    by_mesh: HashMap<ProcMeshRef, EntryId>,
318    /// Per-entry coordination state, keyed by the small `EntryId`.
319    entries: HashMap<EntryId, MeshState>,
320    /// Monotonic source of `EntryId`s. The actor runs one `&mut self` handler at
321    /// a time (the borrow is held across the handler's awaits), so this counter
322    /// is bumped without races and needs no atomic or lock.
323    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    /// The single `Pending -> terminal` mutator (RMO-3). Resolves a `Pending`
337    /// entry to `Ready`/`Failed`, draining `waiters` with `result`. No-op when
338    /// the entry is absent or already terminal (RMO-2).
339    fn transition(
340        &mut self,
341        cx: &impl context::Actor<A = Self>,
342        entry: EntryId,
343        result: Result<(), RdmaInitError>,
344    ) {
345        // Act only on Pending; absent/terminal entries are ignored
346        // (RMO-2/RMO-3).
347        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        // Post `Unsubscribe` for this entry's subscription (RMO-16) before
370        // installing the terminal state; the controller applies the removal
371        // asynchronously when it handles the message. The controller may itself
372        // be the failed subject, so the post is made non-returning and cannot
373        // bounce into the owner.
374        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        // Every waiter was made non-returning before storage (RMO-5), so a
384        // departed caller drops its reply instead of bouncing it to the owner.
385        for reply in waiters {
386            reply.post(cx, result.clone());
387        }
388
389        // Retain the controller id across the terminal state (RMO-17): a later
390        // terminal of this controller can invalidate other pending views sharing
391        // its managers, without rewriting this cached result (RMO-2).
392        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    /// Resolve a **live** controller report (RMO-7): fail only the `Pending`
410    /// entry whose recorded `controller_id` matches, through [`transition`]
411    /// (RMO-3). A live report is source-local — it never fans out to overlapping
412    /// views (only controller *termination* does, see
413    /// [`resolve_controller_termination`]). Returns whether a `Pending` entry
414    /// matched, so callers can distinguish a resolution from an unrelated, late,
415    /// or already-terminal report (which they only log).
416    ///
417    /// [`resolve_controller_termination`]: Self::resolve_controller_termination
418    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        // Cold path over a small set of views: a linear scan avoids a second
436        // `controller_id -> EntryId` index whose consistency would be another
437        // invariant to uphold on every terminal path. Copy the matching
438        // `EntryId` out before the terminal mutation so no map borrow is live
439        // across `transition`.
440        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    /// Resolve a terminal controller event (RMO-14/RMO-17). Locate the source
456    /// entry by `controller_id` across *every* controller-backed state (via
457    /// [`MeshState::controller_id`]); using that source's retained `ProcMeshRef`
458    /// view, fail every still-`Pending` entry whose proc view intersects it. A
459    /// terminal source is left unchanged (RMO-2); disjoint pending views are
460    /// untouched. By RMO-8 (per-proc singleton managers) proc-view intersection
461    /// is exactly shared-manager intersection. Returns `None` when no source
462    /// view can be recovered, or `Some(n)` with the number of entries
463    /// transitioned.
464    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        // 1. Find the source entry id by controller id (any state).
479        let source_id = self
480            .entries
481            .iter()
482            .find_map(|(id, s)| (s.controller_id() == Some(controller_id)).then_some(*id))?;
483        // 2. Recover the source's proc view via the existing `by_mesh` key.
484        let source_procs = self
485            .by_mesh
486            .iter()
487            .find_map(|(mesh, id)| (*id == source_id).then(|| proc_ids(mesh)))?;
488        // 3. Collect every still-`Pending` entry whose view intersects the
489        //    source view. Scan each candidate against the single source set (no
490        //    per-view `HashSet`), and build the target list before any transition
491        //    so no map borrow is live across the terminal mutation.
492        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    /// Attribute a live controller report (owner sink or subscriber `Some`) by
509    /// its typed `reporting_controller` and fail that source entry if pending
510    /// (RMO-7, source-local). `None`/unknown/already-terminal reporters are
511    /// logged and ignored.
512    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        // No pending source matched. A report for a controller whose entry is
526        // already terminal is expected — it may be a late report, or the same
527        // error arriving on both the owner sink and the subscriber stream (RMO-2);
528        // only a report for a wholly unknown controller is anomalous.
529        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    /// The manager-mesh controller is a direct child of the owner, so any
565    /// *terminal* status of the controller itself — clean `Stopped` as well as
566    /// `Failed` — is delivered here (RMO-14). It is matched by `event.actor_id`
567    /// on this direct parent/child channel (deliberately distinct from
568    /// `MeshFailure` attribution). Because a terminating controller's cleanup can
569    /// affect per-proc managers shared with other pending views, this
570    /// conservatively fails every pending view intersecting the source
571    /// controller's retained proc view (RMO-17), leaving a terminal source and
572    /// disjoint pending views unchanged.
573    /// Non-terminal events are absorbed. Every event returns `Ok(true)` so no
574    /// child event terminates this singleton (RMO-1/RMO-4); the owner's only
575    /// children are manager controllers.
576    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                    // A known controller with no overlapping pending view is the
587                    // expected teardown case (e.g. a `Ready` source stopping with no
588                    // pending sibling to fail).
589                    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    /// Initialization control traffic — the readiness cast to a manager, or the
607    /// `Subscribe` to a controller — can return to the owner as an invalid
608    /// reference if its target has already died; the default policy would `bail!`
609    /// and terminate the owner. Swallow it (RMO-4). The authoritative terminal
610    /// signal is the controller report (RMO-7) or the controller-child event
611    /// (RMO-14), never the returned envelope — which is not parsed.
612    /// `handle_undeliverable_message` is deliberately left at its default (benign
613    /// except for an oversized frame).
614    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// Implements the get-or-initialize barrier for one proc-mesh view. Existing
630// entries reply from terminal state or join the pending waiter set. New entries
631// spawn or reuse the per-proc service actors, record `Pending` before casting
632// readiness, and resolve only after every rank acknowledges. Operational
633// failures are returned through `reply`, never propagated from this handler.
634#[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        // RMO-5: never bounce an undeliverable reply back to the owner. Set
646        // before the ref is posted or enters `waiters`.
647        reply.return_undeliverable(false);
648
649        // Hash the (O(N)) `ProcMeshRef` here (one lookup; plus one insertion
650        // below for a new view) — never on the ack path. `EntryId: Copy`, so
651        // this borrow of `by_mesh` ends before `entries` is touched.
652        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                    // `by_mesh` and `entries` are inserted together, so this is
673                    // unreachable; reply rather than hang the caller if it ever
674                    // desyncs.
675                    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        // Absent view: allocate its entry id, then materialize a
686        // controller-ful manager mesh. `spawn_service` applies the singleton
687        // `rdma_manager` resource id on each target proc, creating missing
688        // actors and reusing existing actors from overlapping views (RMO-8).
689        // This is the sole blocking await (RMO-10).
690        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                // Spawn failure is cached terminally (RMO-2): no `Pending`
698                // exists yet, so insert `Failed` directly and reply inline.
699                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        // Capture the controller ref (cloned so it outlives the `mesh` move) and
713        // derive its id — the attribution/overlap key (RMO-7/RMO-14/RMO-17). A
714        // controller-ful `spawn_service` always sets one (RMO-13), so `expect`
715        // here asserts that invariant.
716        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        // The owner's shared `Handler<Option<MeshFailure>>` port; each pending
725        // entry registers this same port with its own controller's subscriber
726        // set (RMO-16). Reports are demultiplexed by typed `reporting_controller`.
727        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        // Record `Pending` BEFORE subscribing and casting, so a replayed terminal
732        // or a cast failure has an entry to drain terminally (RMO-2/RMO-15).
733        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        // Subscribe to the controller's lifecycle stream before casting (RMO-15):
746        // `Subscribe` replays any latched report and later reports are broadcast
747        // to the now-inserted port, so no published report races registration.
748        controller.post(cx, Subscribe(subscriber));
749
750        // 0-rank mesh: no ack will ever come, so resolve immediately (the
751        // transition also posts `Unsubscribe`).
752        if remaining_acks == 0 {
753            self.transition(cx, entry, Ok(()));
754            return Ok(());
755        }
756
757        // Cast readiness to every rank; each rank acks post-`init()` (RMR-1).
758        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// Counts the post-`init()` acknowledgement from each manager rank. Unknown or
767// terminal entries absorb late acknowledgements; only the final acknowledgement
768// resolves a `Pending` entry through the guarded transition path.
769#[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        // Decrement under a scoped borrow; resolve after it is released.
774        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            // Resolved (Ready/Failed) or absent -> ignore (RMO-2 late-signal
785            // absorption).
786            false
787        };
788        if resolved {
789            // RMO-3/RMO-6: the final acknowledgement performs the entry's sole
790            // `Pending -> Ready` transition through the centralized guard.
791            self.transition(cx, entry, Ok(()));
792        }
793        Ok(())
794    }
795}
796
797// The two live-report routes. The owner sink `Handler<MeshFailure>` (required by
798// `spawn_service`) carries only *error* reports; the subscriber
799// `Handler<Option<MeshFailure>>` additionally carries published *clean*
800// terminal reports (`Stopped`/`NotExist`/`Timeout`) and `None` heartbeats
801// (RMO-14). Both attribute by the typed `reporting_controller` and fail only the
802// source pending entry (RMO-7); an error that arrives on both is absorbed by the
803// terminal-absorbing transition (RMO-2). Neither returns `Err`, so a report
804// cannot terminate the owner (RMO-4).
805#[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        // `None` is a healthy heartbeat (RMO-16); only `Some` is a report.
825        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    /// Happy path: ensuring a manager on a two-proc mesh replies `Ok` only once
882    /// every rank posts its post-`init()` `ReadyAck` (RMO-6, RMO-8, RMR-1). TCP
883    /// fallback lets the manager `init()` succeed without RDMA hardware.
884    #[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    // Test-only local probe messages, delivered via the owner's ActorHandle.
922
923    #[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    /// Reports a view's entry state, the owner's total entry count, and (when
942    /// `Pending`) its waiter and remaining-ack counts.
943    #[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    /// The member `ActorId`s of a manager mesh (one per proc in the view).
1014    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    /// Once an entry is `Ready`, a duplicate ensure replies from the cached
1024    /// terminal state without adding another owner entry (RMO-2).
1025    #[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    /// Blocks the owner's message loop: signals `entered`, then awaits
1074    /// `release`. Lets a test enqueue messages behind it to control ordering.
1075    #[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    /// A second ensure for the same view arriving while the first is still
1091    /// `Pending` joins that entry as an extra waiter rather than spawning
1092    /// again; both waiters then resolve `Ok` together (RMO-2, RMO-3).
1093    /// `Pause` + mailbox FIFO make the in-flight overlap deterministic.
1094    #[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        // Block the owner, then enqueue (FIFO, behind the pause) two ensures
1118        // for the same key and a Probe.
1119        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        // Release: FIFO runs ensure 1 (records `Pending` + casts), ensure 2
1149        // (joins as a waiter), then the Probe -- all before any `ReadyAck`.
1150        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    /// While one view's readiness acks are outstanding, the owner keeps
1172    /// servicing other messages: two ensures for different views both reach
1173    /// `Pending` before any ack, so the gather is message-driven, not a
1174    /// blocking await (RMO-10).
1175    #[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        // Block the owner, then enqueue (FIFO, behind the pause) ensures for
1200        // two distinct views and a Probe of each.
1201        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        // Both views are Pending before any ack: view B was serviced without
1241        // blocking on view A's outstanding acks.
1242        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    /// The owner is spawned as a controllerless, ProcAgent-managed singleton
1258    /// (as production does via `spawn_controllerless_service`): its mesh has no
1259    /// controller (RMO-13), and after the returned `ActorMesh` is dropped the
1260    /// owner still serves via the retained rank-0 `ActorRef` (RMO-1).
1261    #[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        // Spawn the owner the way production does: a controllerless singleton.
1282        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 the returned mesh; the ProcAgent-managed singleton persists.
1301        drop(owner_mesh);
1302
1303        // The retained ref still reaches the owner, which serves the request.
1304        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    /// Spawning the owner service twice yields the same singleton actor: the
1315    /// controllerless-singleton owner is deduped by its reserved name, so there
1316    /// is exactly one per process (RMO-1).
1317    #[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    /// Overlapping views share the per-proc `rdma_manager`: the shared proc
1373    /// runs one physical actor (identical `ActorId`) referenced by both views'
1374    /// manager meshes, while each view keeps a distinct owner entry (RMO-8).
1375    /// Verified in both ensure orders (whole-first and slice-first).
1376    #[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        // The reuse path crosses three independently-blockable stages: the
1416        // second `spawn_service`, the reused actor's second `ReadyAck`, and the
1417        // teardown of two overlapping controllers. Bound each separately so a
1418        // timeout names its stage rather than tripping the whole-test deadline.
1419
1420        // First view resolves against fresh per-proc managers.
1421        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        // Second view overlaps the first. Probe before awaiting its reply: if
1431        // the owner is blocked inside the second `spawn_service`, it cannot
1432        // service this Probe and the wait elapses (isolates a spawn hang).
1433        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        // spawn_service returned; a hang here is the reused actor failing to
1452        // deliver the second view's `ReadyAck`.
1453        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        // Distinct owner entries, one per view.
1462        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        // The shared proc runs one physical manager, referenced by both views.
1467        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        // Teardown drops two overlapping controllers over shared procs.
1485        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    // Failure-path fixtures, helpers, and tests.
1497    //
1498    // The failure tests reuse production-created entries rather than fabricating
1499    // `MeshState`s. `HoldPending` adds one sentinel to `remaining_acks` on a real
1500    // `Pending` entry so its real per-rank `ReadyAck`s cannot resolve it, keeping
1501    // the entry deterministically `Pending` for failure injection; it preserves
1502    // `by_mesh`/`entries` consistency and the entry's non-returning waiter
1503    // (RMO-5), and returns the real controller ref for tests that drive the
1504    // controller's own lifecycle.
1505
1506    /// Build a `MeshFailure` the way a controller posts one: `reporting_controller`
1507    /// is the attribution key (RMO-7); the event subject is deliberately
1508    /// unrelated to the controller.
1509    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    /// Poll the entry for `key` until `remaining_acks == want`, giving the real
1524    /// per-rank `ReadyAck` time to land behind the held sentinel.
1525    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    /// Hold a real `Pending` entry open (sentinel) and report its `EntryId`,
1541    /// recorded `controller_id`, and the real manager-mesh controller ref, so a
1542    /// test can drive that controller's own lifecycle via `downcast_handle` or
1543    /// query it with `GetSubscriberCount`.
1544    #[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    /// Poll the entry for `key` until it reaches `want`.
1590    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    /// Test-only owner op: remove the owner's shared `Option<MeshFailure>`
1606    /// subscription from `controller`, using the exact well-known handler port
1607    /// the owner registers. Owner-originated so a following owner-originated
1608    /// `CountSubscribers` is ordered behind it at the controller.
1609    #[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    /// Test-only owner op: query `controller`'s subscriber count from the owner,
1632    /// so the query is ordered behind any prior owner-posted `Subscribe`/
1633    /// `Unsubscribe`/`transition` unsubscribe to that same controller (RMO-16).
1634    #[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            // Owner-originated: this `GetSubscriberCount` is ordered behind any
1648            // prior owner post to the same controller (Subscribe / Unsubscribe /
1649            // the transition drop), so the reported count reflects them.
1650            msg.controller.post(cx, GetSubscriberCount(msg.count_port));
1651            Ok(())
1652        }
1653    }
1654
1655    /// Post an owner-originated subscriber-count query and await the answer. The
1656    /// reply port lives on the caller's mailbox; the owner only forwards it, so
1657    /// the query is ordered behind the owner's prior posts to that controller.
1658    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    /// Test-only owner op that drives the replay-before-insert path (RMO-15)
1677    /// deterministically, without any timing. All four messages are owner-posted
1678    /// to the same controller, so they are FIFO-ordered there: remove the owner's
1679    /// subscription, report the count at that midpoint (must be `0`), latch a
1680    /// whole-view non-error report via `resource::Stop`, then re-subscribe the
1681    /// same owner handler port. Only the `Subscribe` replay can then resolve the
1682    /// held entry, since the latched report carries the controller id it recorded.
1683    #[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    /// A manager with no available backend (ibverbs disabled, TCP fallback off)
1713    /// fails to initialize: the barrier terminates with a typed error, caches
1714    /// it, and the owner survives. The spawn-boundary race means the typed error
1715    /// is `SpawnFailed` if failure is visible before `spawn_service` returns, or
1716    /// `InitFailed` from the controller report otherwise (RMO-2/4/7).
1717    #[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            // Both delivery paths carry the forced cause. If the failure is
1751            // visible after `Pending` is installed, the controller report renders
1752            // it through the `MeshFailure` display (InitFailed); if it is visible
1753            // during the bounded spawn-wait, it surfaces as `Status::Failed(reason)`
1754            // inside `ActorSpawnError`, whose display SpawnFailed captures. The
1755            // only failure source in this config is the missing backend, so the
1756            // substring must appear either way.
1757            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        // Terminal + cached: a second ensure replies from cache without
1767        // spawning again (RMO-2).
1768        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    /// Typed source attribution (RMO-7, the linchpin) plus the `Failed`-source
1790    /// half of RMO-17. A live `MeshFailure` is source-local: it fails only the
1791    /// view whose recorded `controller_id` matches its `reporting_controller`,
1792    /// even though its `crashed_ranks` name a proc another pending view shares —
1793    /// so a stale rank-fanout implementation fails this test. `None`, missing,
1794    /// and unknown reporters resolve nothing (asserted BEFORE the match, so a
1795    /// regression cannot hide behind the later resolution). The identical report
1796    /// through the subscriber route is a late duplicate that cannot rewrite the
1797    /// cached error. Finally, killing the already-`Failed` source's real
1798    /// controller (a `Failed` controller terminal) invalidates the overlapping
1799    /// pending view by proc-view overlap, with the sibling's own subscription
1800    /// removed. A dropped waiter's drained reply must not bounce into the owner
1801    /// (RMO-5).
1802    #[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)?; // [0, 1], source
1821        let view_b: ProcMeshRef = proc_mesh.range("procs", 0..1)?; // [0], overlaps A
1822        let view_c: ProcMeshRef = proc_mesh.range("procs", 2..3)?; // [2], disjoint
1823
1824        let owner = RdmaManagerOwnerActor::new((), &ActorEnvironment::default()).await?;
1825        let owner_handle = client.spawn(owner);
1826        let subject = owner_handle.actor_addr().clone();
1827
1828        // Prequeue every ensure and hold behind a pause so each entry is created
1829        // and held before any real ack can resolve it.
1830        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        // A second waiter joins A; drop its receiver so its drained reply is
1892        // undeliverable and must not bounce into the owner (RMO-5).
1893        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        // Ignore-before-match: an outer `None` heartbeat, a missing reporter, and
1911        // an unknown reporter resolve nothing. An owner round-trip drains the
1912        // queue behind them, then every view must still be pending.
1913        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        // The matching report resolves ONLY A, draining both its waiters. Its
1934        // `crashed_ranks = [0]` name B's proc; source-local attribution must
1935        // leave B (and disjoint C) pending.
1936        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        // The identical report through the subscriber route is a late duplicate:
1963        // A is terminal, so its cached error is unchanged.
1964        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        // `Failed`-source half of RMO-17: unsubscribe B so it cannot fail via its
1982        // own subscriber, then terminate A's real controller. The owner uses A's
1983        // retained controller id and A's `ProcMeshRef` to fail B by proc-view
1984        // overlap; disjoint C stays pending.
1985        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        // A is already a `Failed` source; killing its controller drives the
2011        // Failed-source + Failed-controller cell of RMO-17. A's controller
2012        // completes `Failed`.
2013        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        // A's cached error is still exactly the original InitFailed.
2025        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    /// Once a view is `Ready`, a live failure naming its controller is absorbed
2038    /// and the cached `Ok` is unchanged — the owner is an initialization barrier,
2039    /// not a health oracle (RMO-2). `Ready` retains its `controller_id` (for
2040    /// RMO-17 overlap), but a live report is source-local and acts only on a
2041    /// `Pending` entry, so it resolves nothing on either delivery route. Also
2042    /// pins the subscription lifetime around the success transition: exactly one
2043    /// subscriber while `Pending`, none once `Ready` (RMO-16).
2044    #[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        // Capture the controller id while `Pending` (it is also retained in
2069        // `Ready`), then let the entry resolve to `Ready`.
2070        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        // Held `Pending`: exactly one subscriber (the owner) is registered.
2097        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        // The success transition posted `Unsubscribe`; an owner-ordered count
2110        // query, FIFO behind it at the controller, now reports 0.
2111        assert_eq!(
2112            owner_subscriber_count(&owner_handle, client, ctrl).await,
2113            0,
2114            "reaching Ready removes the controller subscription",
2115        );
2116
2117        // A late failure naming the former controller is absorbed on both routes.
2118        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    /// Subscription lifetime end to end (RMO-15/16), proved without timing. A held
2141    /// pending entry has one subscriber and ignores an outer `None` heartbeat.
2142    /// Driving `Unsubscribe -> count -> resource::Stop -> Subscribe` from the
2143    /// owner to the same controller latches a whole-view non-error report with no
2144    /// subscriber present, so only the `Subscribe` replay-before-insert resolves
2145    /// the waiter; the midpoint count is `0`. The resolving `transition` then
2146    /// removes that re-subscription (final owner-ordered count `0`). Capturing the
2147    /// exact latched report through a temporary test subscription lets us replay
2148    /// it through both handler routes as late duplicates that leave the cached
2149    /// error and the owner unchanged.
2150    #[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        // Prequeue ensure + hold behind a pause so the entry is created and held
2174        // (sentinel added) before any real `ReadyAck` can resolve it (RMO-15).
2175        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        // Hold a real pending entry and capture its real controller.
2187        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        // The production ensure path registered exactly one subscriber (RMO-15/16).
2203        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        // An outer `None` heartbeat resolves nothing.
2210        owner_handle.post(client, None::<MeshFailure>);
2211        assert_eq!(
2212            probe(&owner_handle, client, key.clone()).await.state,
2213            EntryStateTag::Pending,
2214        );
2215
2216        // Replay path (no timing): only the final `Subscribe` replay can resolve
2217        // the held entry; the midpoint count proves the owner was unsubscribed
2218        // before the latch.
2219        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        // `transition` posted `Unsubscribe` on resolution; the final owner-ordered
2247        // count is `0`, proving replay-before-insert did not leak the
2248        // re-subscription.
2249        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        // Capture the exact latched report with a temporary test subscription,
2256        // then remove it, and prove both late-duplicate routes are absorbed.
2257        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        // Removal barrier: a client-originated count query is FIFO-ordered behind
2265        // the client's `Unsubscribe(cap_tx)` at the controller, so it observes the
2266        // capture port removed before the duplicates are replayed.
2267        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    /// The load-bearing RMO-17 proof, on the real manager-mesh controller and its
2294    /// real cleanup path. Cleanly stopping a `Ready` controller invalidates every
2295    /// still-`Pending` view sharing one of its per-proc managers, and only those;
2296    /// sibling subscriptions are removed first so the overlap cannot leak through
2297    /// the subscriber stream. Killing a disjoint pending view's controller then
2298    /// proves the error-terminal branch (RMO-14). Every terminal input keeps the
2299    /// owner alive (RMO-4) and leaves a terminal source and disjoint views
2300    /// unchanged (RMO-2).
2301    #[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)?; // [0, 1]
2321        let view_b: ProcMeshRef = proc_mesh.range("procs", 0..1)?; // [0]
2322        let view_c: ProcMeshRef = proc_mesh.range("procs", 1..2)?; // [1]
2323        let view_d: ProcMeshRef = proc_mesh.range("procs", 2..3)?; // [2]
2324
2325        let owner = RdmaManagerOwnerActor::new((), &ActorEnvironment::default()).await?;
2326        let owner_handle = client.spawn(owner);
2327
2328        // Hold A pending to capture its real controller, then release its
2329        // sentinel so A reaches `Ready` (which unsubscribes it). Prequeue ensure
2330        // + hold behind a pause so A is held before any real `ReadyAck` lands.
2331        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        // Hold B[0], C[1] (both share a proc with A) and disjoint D[2] pending.
2366        // Prequeue their ensures + holds behind a pause so each is held before
2367        // any real `ReadyAck` lands.
2368        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        // Remove B and C subscriptions (owner-originated) so their failure can
2420        // only come from A-controller overlap, not their own subscriber stream.
2421        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        // Cleanly stop A's real controller; its cleanup stops the managers shared
2444        // with B[0] and C[1]. The owner learns of A's terminal via direct child
2445        // supervision and fails B and C by proc-view overlap (RMO-17).
2446        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        // A's controller completed a clean stop (the clean-terminal branch). The
2464        // B/C waiter errors above, not this join, are the proof that the owner
2465        // processed A's terminal child event.
2466        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        // D is disjoint ([2]) and stays pending; A stays cached `Ready`.
2473        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        // Self-termination path, error-terminal branch: kill D's own controller
2485        // so it completes as `Failed` (a crash, not a clean stop). The owner gates
2486        // on `is_terminal()`, so `Failed` drives the same resolution A's clean
2487        // `Stopped` did; D resolves directly (RMO-14).
2488        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        // D's controller completed as `Failed` (the error-terminal branch).
2499        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    /// An invalid-reference return (such as a readiness-cast bounce to a dead
2512    /// rank) can reach the owner; the default policy would `bail!` and kill the
2513    /// owner. The override swallows it (RMO-4) and does not mutate a held pending
2514    /// entry, which still resolves normally afterwards. Delivered through the
2515    /// owner's real delivery-failure dispatch, not a direct callback call.
2516    #[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        // Hold a real entry pending so the swallow is proved non-mutating.
2541        // Prequeue ensure + hold behind a pause so the entry is held before any
2542        // real `ReadyAck` lands.
2543        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        // Build an invalid-reference return and deliver it through the owner's
2569        // real delivery-failure dispatch (blanket `Handler<Undeliverable>`).
2570        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        // The swallow did not mutate the held entry: it stays pending.
2585        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        // Release the sentinel; the entry resolves normally and the owner still
2593        // serves a cached repeat ensure.
2594        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}