Skip to main content

liminal_server/server/connection/
supervisor.rs

1use std::collections::HashMap;
2#[cfg(test)]
3use std::collections::VecDeque;
4use std::collections::hash_map::Entry;
5use std::net::{SocketAddr, TcpStream};
6use std::os::fd::RawFd;
7#[cfg(test)]
8use std::sync::Barrier;
9use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
10use std::sync::mpsc::{Receiver, RecvTimeoutError, Sender, TryRecvError, channel};
11use std::sync::{Arc, Condvar, Mutex, MutexGuard, PoisonError, Weak};
12use std::thread;
13use std::time::{Duration, Instant};
14
15use beamr::atom::{Atom, AtomTable};
16use beamr::module::ModuleRegistry;
17use beamr::native::native_process::NativeHandlerFactory;
18use beamr::process::ExitReason;
19use beamr::scheduler::{
20    ExitEvent, ExitEventSubscription, ReadinessToken, Scheduler, SchedulerConfig, SchedulerServices,
21};
22use beamr::timer::TimerRef;
23
24use liminal::protocol::WorkerRegistration;
25use liminal_protocol::wire::ConnectionIncarnation;
26
27use super::incarnation::ConnectionIncarnationAuthority;
28use super::loopback::{LoopbackConnectionProcess, LoopbackServerEnd};
29use super::notifier::ConnectionNotifier;
30use super::process::ConnectionProcess;
31use super::services::{
32    ConnectionServices, LiminalConnectionServices, ProductionSubsystems, SubsystemFactory,
33    build_connection_services_via,
34};
35use crate::ServerError;
36use crate::config::types::{LimitsConfig, ServerConfig};
37use crate::server::mount::MountKind;
38use crate::server::participant::{
39    ConnectionFateClass, InstalledParticipantService, ParticipantSemanticHandler,
40    ParticipantServiceFatal,
41};
42use crate::server::shutdown::ShutdownHandle;
43
44const CONNECTION_SCHEDULER_THREADS: usize = 4;
45const CONNECTION_SHUTDOWN_CONTROL_ATOM: &str = "liminal_server_connection_shutdown_control";
46/// R6 (§1.2(4)): the single `READY` wake vocabulary for a connection. One atom;
47/// any marker (or N coalesced) triggers one full slice servicing all sources.
48const CONNECTION_READY_ATOM: &str = "liminal_server_connection_ready";
49
50#[cfg(test)]
51#[path = "supervisor_fate_tests.rs"]
52mod fate_tests;
53#[cfg(test)]
54#[path = "supervisor_tests.rs"]
55mod tests;
56
57/// Supervisor that owns the beamr scheduler for per-connection processes.
58#[derive(Clone, Debug)]
59pub struct ConnectionSupervisor {
60    inner: Arc<SupervisorInner>,
61}
62
63impl ConnectionSupervisor {
64    /// Creates a connection supervisor backed by the services the config's
65    /// `[services]` profile selects: the full liminal channel/conversation stack
66    /// (the default) or the capability-scoped worker front door. Profile
67    /// enforcement is [`build_connection_services`](super::services::build_connection_services)'s,
68    /// so this constructor can never build full services for a worker-front-door
69    /// config.
70    ///
71    /// # Errors
72    /// Returns [`ServerError`] when service construction or scheduler startup fails.
73    pub fn from_config(config: &ServerConfig) -> Result<Self, ServerError> {
74        Self::from_config_via(config, &ProductionSubsystems)
75    }
76
77    /// [`Self::from_config`] with the §9 D2 subsystem factory injected.
78    ///
79    /// The factory is the only route to every scheduler-owning subsystem the
80    /// services construction builds, so a recording factory observes exactly what
81    /// was constructed; the connection scheduler itself (built below for BOTH
82    /// profiles) is the census baseline, not a census entry.
83    fn from_config_via(
84        config: &ServerConfig,
85        subsystems: &dyn SubsystemFactory,
86    ) -> Result<Self, ServerError> {
87        let services = build_connection_services_via(config, subsystems)?;
88        // The configured token (if any) is carried opaquely as bytes for a
89        // constant-time comparison against the handshake's `auth_token`. Absent
90        // `[auth]` leaves it `None`, so the connection stays open-access.
91        let auth_token = config
92            .auth
93            .as_ref()
94            .map(|auth| auth.token.clone().into_bytes());
95        SupervisorInner::new(services, None, auth_token, config.limits, None).map(|inner| Self {
96            inner: Arc::new(inner),
97        })
98    }
99
100    /// Creates a connection supervisor with no configured channels.
101    ///
102    /// # Errors
103    /// Returns [`ServerError`] when scheduler startup fails.
104    pub fn new() -> Result<Self, ServerError> {
105        Self::with_services(Arc::new(LiminalConnectionServices::empty()?))
106    }
107
108    /// Creates a connection supervisor using an explicit service adapter.
109    ///
110    /// # Errors
111    /// Returns [`ServerError`] when scheduler startup fails.
112    pub fn with_services(services: Arc<dyn ConnectionServices>) -> Result<Self, ServerError> {
113        SupervisorInner::new(services, None, None, LimitsConfig::default(), None).map(|inner| {
114            Self {
115                inner: Arc::new(inner),
116            }
117        })
118    }
119
120    /// Creates a connection supervisor with an explicit service adapter and the
121    /// configured connection auth token.
122    ///
123    /// This is the production constructor for callers that build services
124    /// themselves (the runtime needs the shared channel cluster before the
125    /// supervisor takes ownership) and therefore cannot use
126    /// [`Self::from_config`]: without it the configured `[auth]` token would be
127    /// silently dropped and the server would run open-access.
128    ///
129    /// # Errors
130    /// Returns [`ServerError`] when scheduler startup fails.
131    pub fn with_services_and_auth(
132        services: Arc<dyn ConnectionServices>,
133        auth_token: Option<Vec<u8>>,
134    ) -> Result<Self, ServerError> {
135        Self::with_services_auth_and_limits(services, auth_token, LimitsConfig::default())
136    }
137
138    /// Creates a connection supervisor with explicit services, authentication,
139    /// and operational limits.
140    ///
141    /// Production runtime construction uses this form so the durable
142    /// incarnation stream's complete-reference bound is the same signed
143    /// `max_connections` bound enforced by connection admission.
144    ///
145    /// # Errors
146    /// Returns [`ServerError`] when incarnation startup or scheduler startup fails.
147    pub fn with_services_auth_and_limits(
148        services: Arc<dyn ConnectionServices>,
149        auth_token: Option<Vec<u8>>,
150        limits: LimitsConfig,
151    ) -> Result<Self, ServerError> {
152        Self::with_services_auth_limits_and_fatal_shutdown(services, auth_token, limits, None)
153    }
154
155    /// Production composition with the process-wide shutdown activation that a
156    /// post-Open participant fatal must join.
157    pub(crate) fn with_fatal_shutdown(
158        services: Arc<dyn ConnectionServices>,
159        auth_token: Option<Vec<u8>>,
160        limits: LimitsConfig,
161        fatal_shutdown: ShutdownHandle,
162    ) -> Result<Self, ServerError> {
163        Self::with_services_auth_limits_and_fatal_shutdown(
164            services,
165            auth_token,
166            limits,
167            Some(fatal_shutdown),
168        )
169    }
170
171    fn with_services_auth_limits_and_fatal_shutdown(
172        services: Arc<dyn ConnectionServices>,
173        auth_token: Option<Vec<u8>>,
174        limits: LimitsConfig,
175        fatal_shutdown: Option<ShutdownHandle>,
176    ) -> Result<Self, ServerError> {
177        SupervisorInner::new(services, None, auth_token, limits, fatal_shutdown).map(|inner| Self {
178            inner: Arc::new(inner),
179        })
180    }
181
182    /// Creates a connection supervisor with an explicit service adapter and a
183    /// connection-keyed worker-registration notifier.
184    ///
185    /// The `notifier` is invoked when a worker registers on a connection and when
186    /// such a connection closes. Supervisors built via [`Self::with_services`],
187    /// [`Self::from_config`], or [`Self::new`] carry no notifier, so liminal still
188    /// runs standalone; a `WorkerRegister` frame is then accepted without any
189    /// application callback.
190    ///
191    /// # Errors
192    /// Returns [`ServerError`] when scheduler startup fails.
193    pub fn with_services_and_notifier(
194        services: Arc<dyn ConnectionServices>,
195        notifier: Arc<dyn ConnectionNotifier>,
196    ) -> Result<Self, ServerError> {
197        SupervisorInner::new(
198            services,
199            Some(notifier),
200            None,
201            LimitsConfig::default(),
202            None,
203        )
204        .map(|inner| Self {
205            inner: Arc::new(inner),
206        })
207    }
208
209    /// Spawns one supervised beamr process that owns `stream`.
210    ///
211    /// # Errors
212    /// Returns [`ServerError`] when stream configuration or beamr spawn fails.
213    pub fn spawn_connection(&self, stream: TcpStream) -> Result<ConnectionHandle, ServerError> {
214        self.inner.spawn_connection(stream)
215    }
216
217    /// Returns the underlying beamr scheduler.
218    #[must_use]
219    pub fn scheduler(&self) -> Arc<Scheduler> {
220        Arc::clone(&self.inner.scheduler)
221    }
222
223    /// Reaps connection processes that have exited outside the normal handler path.
224    #[must_use]
225    pub fn reap_crashed_connections(&self) -> usize {
226        self.inner.runtime.reap_crashed(&self.inner.scheduler)
227    }
228
229    /// Returns true when `pid` is still tracked by the supervisor.
230    #[must_use]
231    pub fn is_tracked(&self, pid: u64) -> bool {
232        self.inner.runtime.contains(pid)
233    }
234
235    /// Returns the number of tracked live connections.
236    #[must_use]
237    pub fn active_connection_count(&self) -> usize {
238        self.inner.runtime.active_count()
239    }
240
241    /// Parks until every tracked connection has been removed or `deadline`
242    /// elapses, returning `true` when the drain completed and `false` when the
243    /// single admitted deadline won.
244    ///
245    /// The TOLD drain-completion replacement (W4 leg 3, §4.3): the waiter is woken
246    /// by the one `remove()` funnel every connection exit reaches — never by a
247    /// reap/count timer. Both the graceful drain and the force-close settle call
248    /// this same waiter, each with its own one-shot deadline; there is no second
249    /// settle poll loop.
250    #[must_use]
251    pub(crate) fn wait_for_connections_drained(&self, deadline: Instant) -> bool {
252        self.inner
253            .runtime
254            .wait_for_active_connections_drained(deadline)
255    }
256
257    /// FIX A-ii shutdown flush barrier: parks until every active connection has
258    /// fanned out its accepted publishes to its socket, or `deadline` elapses.
259    /// Called in `run_shutdown_sequence` BEFORE the shutdown Disconnect broadcast
260    /// so an accepted-but-unfanned-out publish can no longer be overtaken by it.
261    #[must_use]
262    pub(crate) fn wait_for_delivery_quiesced(&self, deadline: Instant) -> bool {
263        self.inner.runtime.wait_for_delivery_quiesced(deadline)
264    }
265
266    /// Returns the first latched post-Open participant fatal, if any.
267    ///
268    /// The production runtime reads this after its existing shutdown handle wakes,
269    /// then returns the typed fatal after the ordinary drain and durable flush.
270    pub(crate) fn participant_service_fatal(
271        &self,
272    ) -> Result<Option<ParticipantServiceFatal>, ServerError> {
273        self.inner.runtime.participant_service_fatal()
274    }
275
276    /// Returns the beamr process ids of the currently tracked live connections.
277    ///
278    /// Useful for addressing a specific connection — e.g. as the `pid` argument to
279    /// [`push_to_connection`](Self::push_to_connection) when the caller knows there
280    /// is a single connected client.
281    #[must_use]
282    pub fn active_connection_pids(&self) -> Vec<u64> {
283        self.inner
284            .runtime
285            .active_connections()
286            .into_iter()
287            .map(|connection| connection.pid)
288            .collect()
289    }
290
291    /// Broadcasts a best-effort shutdown notification to active connections.
292    ///
293    /// Connections with no active subscriptions ignore the notification. Failures
294    /// to enqueue the control message are logged and skipped; they are not retried.
295    pub fn notify_shutdown_subscribers(&self) {
296        self.inner
297            .broadcast_control(&ConnectionControl::NotifyShutdown);
298    }
299
300    /// Sends a force-close control message to every tracked connection process.
301    ///
302    /// Each live process attempts one shutdown notification before closing its
303    /// stream and exiting normally. Enqueue failures are logged and skipped.
304    pub fn force_close_active_connections(&self) {
305        for connection in self.inner.runtime.active_connections() {
306            tracing::warn!(
307                connection_pid = connection.pid,
308                peer_addr = ?connection.peer_addr,
309                "forcefully closing connection after drain timeout"
310            );
311            if !self
312                .inner
313                .enqueue_control(connection.pid, ConnectionControl::ForceClose)
314            {
315                tracing::warn!(
316                    connection_pid = connection.pid,
317                    peer_addr = ?connection.peer_addr,
318                    "failed to request forceful connection close; process is not live"
319                );
320            }
321        }
322    }
323
324    /// Pushes an opaque payload to a specific connected client over that client's
325    /// existing connection and returns an awaiter for the client's correlated reply.
326    ///
327    /// This is the server-initiated leg (server-to-client), the inverse of every
328    /// other request frame. It allocates a correlation id, registers a one-shot
329    /// reply slot keyed by that id, and enqueues a [`ConnectionControl::Push`] for
330    /// the connection process owning `pid`; that process writes a [`Frame::Push`]
331    /// out on its socket. When the client answers with a `PushReply` carrying the
332    /// same correlation id, the connection process resolves the awaiter's slot. The
333    /// returned [`PushReplyAwaiter`] blocks (bounded) for that reply.
334    ///
335    /// The reply's lifetime belongs to the push, not to any one
336    /// [`PushReplyAwaiter::receive`] call: this no-deadline push reserves a slot
337    /// that is reclaimed only by (a) the reply being consumed or (b) the
338    /// connection closing. An elapsed `receive` poll is a benign re-arm, never a
339    /// failure and never a cancellation. The §5
340    /// `max_pending_pushes_per_connection` cap bounds abandonment; use
341    /// [`push_to_connection_with_deadline`](Self::push_to_connection_with_deadline)
342    /// when the reply must have an explicit expiry.
343    ///
344    /// # Errors
345    /// Returns [`ServerError`] when the correlation id cannot be allocated, the
346    /// reply slot cannot be registered, or the control message cannot be enqueued
347    /// for the (possibly already-gone or concurrently-closing) connection
348    /// process. PUBLICATION INVARIANT: an `Err` guarantees no `Push` control was
349    /// published — the client never sees a `Push` frame for a failed call.
350    /// Conversely `Ok` promises ADMISSION, not delivery: the awaiter's outcome
351    /// is the delivery truth (a push admitted just as its connection closes
352    /// resolves to the truthful disconnected outcome, never to a lost reply).
353    pub fn push_to_connection(
354        &self,
355        pid: u64,
356        payload: Vec<u8>,
357    ) -> Result<PushReplyAwaiter, ServerError> {
358        self.push_with_deadline(pid, payload, None)
359    }
360
361    /// Like [`push_to_connection`](Self::push_to_connection) but attaches an
362    /// explicit reply deadline to the reserved slot: `deadline` is a DURATION
363    /// FROM NOW bounding the reply's lifetime — a property of THIS push rather
364    /// than of any [`PushReplyAwaiter::receive`] wait quantum.
365    ///
366    /// Deadline expiry is evaluated HOST-SIDE and LAZILY — at the next `receive`
367    /// touch, and at connection close at the latest. It never wakes the connection
368    /// process, adds no timer thread, and runs no periodic sweeper: a push that is
369    /// abandoned and never polled resolves at the next host-side touch (connection
370    /// close). At expiry the slot resolves to [`ServerError::PushReplyExpired`],
371    /// is removed, and its §5 `max_pending_pushes_per_connection` cap admission is
372    /// released. An elapsed `receive` poll BEFORE the deadline is still a benign
373    /// re-arm. A `receive` call in flight when the deadline falls due returns
374    /// the terminal expiry PROMPTLY — it waits the earlier of its quantum and
375    /// the deadline, so a large quantum can never extend the reply's lifetime
376    /// and the terminal outcome is quantum-independent.
377    ///
378    /// The deadline is evaluated at OBSERVATION POINTS, not enforced against the
379    /// wall clock: a reply that arrives before expiry is observed is delivered
380    /// normally, even if it arrives after the deadline instant. The deadline
381    /// bounds waiting and slot occupancy; it is not a delivery-freshness
382    /// guarantee. (This is deliberate — a reply is checked for at every
383    /// observation point before the deadline is, so an answer in hand always
384    /// beats an expiry.)
385    ///
386    /// # Errors
387    /// Returns [`ServerError`] when `deadline` is not representable on the
388    /// monotonic clock (an extreme duration is refused, never a panic), the
389    /// correlation id cannot be allocated, the reply slot cannot be registered,
390    /// or the control message cannot be enqueued for the (possibly already-gone
391    /// or concurrently-closing) connection process. PUBLICATION INVARIANT: an
392    /// `Err` guarantees no `Push` control was published — the client never sees
393    /// a `Push` frame for a failed call. Conversely `Ok` promises ADMISSION,
394    /// not delivery: the awaiter's outcome is the delivery truth.
395    pub fn push_to_connection_with_deadline(
396        &self,
397        pid: u64,
398        payload: Vec<u8>,
399        deadline: Duration,
400    ) -> Result<PushReplyAwaiter, ServerError> {
401        self.push_with_deadline(pid, payload, Some(deadline))
402    }
403
404    /// Shared body for the no-deadline and explicit-deadline push paths. With
405    /// `deadline == None` this is byte-for-byte the historical
406    /// `push_to_connection` behaviour (no per-slot deadline); with `Some`, the
407    /// slot carries an absolute expiry evaluated lazily at `receive`.
408    fn push_with_deadline(
409        &self,
410        pid: u64,
411        payload: Vec<u8>,
412        deadline: Option<Duration>,
413    ) -> Result<PushReplyAwaiter, ServerError> {
414        // S5: an extreme `Duration` must surface as this fallible API's typed
415        // error, not an `Instant` addition panic. Checked BEFORE any slot is
416        // registered so a refused deadline leaves nothing to roll back.
417        let deadline_at = match deadline {
418            None => None,
419            Some(window) => {
420                Some(
421                    Instant::now()
422                        .checked_add(window)
423                        .ok_or_else(|| ServerError::ListenerAccept {
424                            message: format!(
425                                "cannot push to connection process {pid}: reply deadline of {window:?} overflows the monotonic clock"
426                            ),
427                        })?,
428                )
429            }
430        };
431        let correlation_id = self.inner.runtime.next_push_correlation_id();
432        let receiver = self
433            .inner
434            .runtime
435            .register_push(pid, correlation_id, deadline_at)?;
436        // S3+S7 close-vs-register wall, ordered INSERT -> CONFIRM -> PUBLISH.
437        // The confirmation runs BEFORE the control is enqueued, which yields the
438        // PUBLICATION INVARIANT: an `Err` from this method guarantees no `Push`
439        // control was published — the client never sees a Push for a failed
440        // call. (Confirming after the enqueue was S7's non-linearizable race: a
441        // close could sweep, the published Push could already be answered and
442        // resolved, and the failed confirmation then returned `Err` for a push
443        // the client had received.) A close landing AFTER a successful confirm
444        // linearizes after push admission: the enqueue either fails (process
445        // gone — rollback below, `Err` truthful, nothing delivered) or succeeds
446        // with the slot already swept, and the awaiter then reads the truthful
447        // DISCONNECTED while a late client reply is the pinned harmless no-op.
448        // The exactly-one-side-observes argument lives at
449        // `confirm_push_registration`.
450        if !self
451            .inner
452            .runtime
453            .confirm_push_registration(pid, correlation_id)
454        {
455            return Err(ServerError::ListenerAccept {
456                message: format!(
457                    "cannot push to connection process {pid}: the connection closed during push registration"
458                ),
459            });
460        }
461        let control = ConnectionControl::Push {
462            correlation_id,
463            payload,
464        };
465        if self.inner.enqueue_control(pid, control) {
466            Ok(PushReplyAwaiter {
467                correlation_id,
468                receiver,
469                deadline: deadline_at,
470                runtime: Arc::downgrade(&self.inner.runtime),
471            })
472        } else {
473            // The process is gone AND the control provably never reached a
474            // consumer: `enqueue_control` returns false only when its failed-wake
475            // rollback REMOVED the queued control (S8 — an entry a drain already
476            // consumed counts as published and returns true, with the slot
477            // lifecycle carrying the delivery truth). Dropping the now-unreachable
478            // reply slot here therefore keeps the publication invariant exact on
479            // every `Err` path.
480            self.inner.runtime.cancel_push(correlation_id);
481            Err(ServerError::ListenerAccept {
482                message: format!("cannot push to connection process {pid}: process is not live"),
483            })
484        }
485    }
486
487    /// Flushes durable channel state through the configured liminal services.
488    ///
489    /// # Errors
490    /// Returns [`ServerError::ShutdownFlush`] when the underlying service flush fails.
491    pub fn flush_durable_state(&self) -> Result<(), ServerError> {
492        self.inner.runtime.services().flush_durable_state()
493    }
494
495    /// LP-WS-TRANSPORT R1.3 sibling-transport spawn seam (ADDITIVE ONLY).
496    ///
497    /// Admits, allocates a durable connection incarnation for, spawns, and
498    /// registers a connection process whose handler is built by `build_factory`
499    /// over this supervisor's shared [`ConnectionRuntime`]. The WebSocket
500    /// sibling acceptor uses this so its connections share the ONE §5
501    /// `max_connections` admission bound, the one incarnation authority, the
502    /// one registry (controls, pushes, crash reap, drain, forced close), and the
503    /// one `apply_frame` seam with TCP connections. The TCP accept path above
504    /// (`spawn_connection`) is byte-for-byte untouched and never calls this.
505    ///
506    /// `fd_guard` is a host-held duplicate of the connection's underlying
507    /// socket, exactly like the TCP path's: it keeps the fd alive until the
508    /// single record-removal path has synchronously deregistered readiness. It
509    /// is `None` for a transport that owns no descriptor.
510    ///
511    /// `mount` is the admitting door's own name for itself (design §10), which
512    /// is why this seam takes it rather than deriving it: the caller IS the
513    /// door, and no other party — least of all the client — has any input.
514    ///
515    /// This method exists because Rust module privacy makes the runtime,
516    /// admission counter, incarnation authority, and registry unreachable from
517    /// the sibling `websocket` module family; it is the narrow additive seam
518    /// that shares them without generalizing any TCP hot path.
519    ///
520    /// # Errors
521    /// Returns [`ServerError`] when admission is refused
522    /// ([`ServerError::ConnectionLimitReached`]), incarnation allocation fails,
523    /// or beamr spawn/registration fails.
524    pub(super) fn spawn_transport_connection(
525        &self,
526        peer_addr: Option<SocketAddr>,
527        fd_guard: Option<TcpStream>,
528        mount: MountKind,
529        build_factory: &dyn Fn(
530            Arc<ConnectionRuntime>,
531            Option<ConnectionIncarnation>,
532        ) -> NativeHandlerFactory,
533    ) -> Result<ConnectionHandle, ServerError> {
534        self.inner
535            .spawn_transport_connection(peer_addr, fd_guard, mount, build_factory)
536    }
537
538    /// Admits one in-process connection over `server_end` (design §8 step 3).
539    ///
540    /// This replaces exactly the listener's `accept()` + `spawn_connection`
541    /// pair, and NOTHING else about admission. It runs the same
542    /// `try_reserve_admission` against the same §5 slot pool — an in-process
543    /// connect at capacity is refused with the same typed
544    /// [`ServerError::ConnectionLimitReached`] a socket connect is — allocates a
545    /// real durable [`ConnectionIncarnation`] from the same authority, so
546    /// participant binding, resume, and fate records work identically, and
547    /// registers the same record. Only two fields differ, and both are honest
548    /// descriptions rather than semantics: no fd guard (there is no descriptor
549    /// to keep alive) and `peer_addr: None` (there is no socket to have an
550    /// address). Nothing on this path reads a socket fact.
551    ///
552    /// The connection's wake is installed by the process itself on its first
553    /// serviced slice, which is the earliest point at which its pid and host
554    /// record — the two things the wake names — both exist.
555    ///
556    /// `pub(crate)` rather than `pub`: the embedding handle that grants
557    /// loopback connections lives in this crate, and widening the surface
558    /// further would hand an outside caller a way to reach the runtime that
559    /// module privacy currently denies it.
560    ///
561    /// # Errors
562    /// Returns [`ServerError`] when admission is refused
563    /// ([`ServerError::ConnectionLimitReached`]), incarnation allocation fails,
564    /// or beamr spawn/registration fails.
565    pub(crate) fn spawn_loopback_connection(
566        &self,
567        server_end: LoopbackServerEnd,
568    ) -> Result<ConnectionHandle, ServerError> {
569        // The same interior-mutability handoff the socket path uses: the native
570        // handler factory is `Fn + Send + Sync`, so the duplex end cannot be
571        // moved into it and the FIRST handler build takes it out exactly once.
572        let holder = Arc::new(Mutex::new(Some(server_end)));
573        let build = move |runtime: Arc<ConnectionRuntime>,
574                          incarnation: Option<ConnectionIncarnation>|
575              -> NativeHandlerFactory {
576            let holder = Arc::clone(&holder);
577            Box::new(move || {
578                Box::new(LoopbackConnectionProcess::from_loopback_holder(
579                    Arc::clone(&runtime),
580                    &holder,
581                    incarnation,
582                ))
583            })
584        };
585        self.inner
586            .spawn_transport_connection(None, None, MountKind::Loopback, &build)
587    }
588
589    /// Stops the beamr scheduler used by connection processes.
590    pub fn shutdown(&self) {
591        // Remove every host record while the readiness owner is still live. The
592        // removal path ACKs deregistration and only then releases each fd guard;
593        // scheduler shutdown subsequently drops the process-owned handles.
594        for connection in self.inner.runtime.active_connections() {
595            self.inner.runtime.finish(connection.pid);
596        }
597        self.inner.scheduler.shutdown();
598    }
599
600    /// R7 test instrument: slices serviced by connection `pid` since spawn.
601    #[cfg(test)]
602    pub(crate) fn slice_count(&self, pid: u64) -> u64 {
603        self.inner.runtime.slice_count(pid)
604    }
605
606    /// The mount the admitting door stamped on `pid`'s registry record, or
607    /// `None` when no record is tracked (design §10).
608    #[cfg(test)]
609    pub(crate) fn connection_mount(&self, pid: u64) -> Option<MountKind> {
610        self.inner.runtime.connection_mount(pid)
611    }
612
613    /// Whether `pid`'s registry record holds an fd guard, or `None` when no
614    /// record is tracked. A loopback record must answer `Some(false)`: there is
615    /// no descriptor for the guard to keep alive.
616    #[cfg(test)]
617    pub(crate) fn connection_has_fd_guard(&self, pid: u64) -> Option<bool> {
618        self.inner.runtime.connection_has_fd_guard(pid)
619    }
620
621    /// Installs a one-use readiness marker for the next serviced slice of `pid`.
622    #[cfg(test)]
623    pub(crate) fn observe_next_slice(&self, pid: u64) -> Receiver<u64> {
624        self.inner.runtime.observe_next_slice(pid)
625    }
626
627    /// Installs a one-use readiness marker for the next genuine scheduler park of
628    /// `pid`. The delivered value is the process's slice count at the final probe
629    /// that selected `Wait`.
630    #[cfg(test)]
631    pub(crate) fn observe_next_park(&self, pid: u64) -> Receiver<u64> {
632        self.inner.runtime.observe_next_park(pid)
633    }
634
635    /// Returns a marker for the current park when `pid` is already settled, or
636    /// the next park when a coalesced readiness event has started another slice.
637    #[cfg(test)]
638    pub(crate) fn observe_settled_park(&self, pid: u64) -> Receiver<u64> {
639        self.inner.runtime.observe_settled_park(pid)
640    }
641
642    /// Queues an explicit outbound capacity for the next TCP process constructed.
643    #[cfg(test)]
644    pub(crate) fn queue_next_outbound_capacity(&self, capacity: usize) {
645        self.inner.runtime.queue_next_outbound_capacity(capacity);
646    }
647
648    #[cfg(test)]
649    pub(crate) fn install_participant_holdback_pause(&self, pid: u64) -> Receiver<()> {
650        self.inner.runtime.install_participant_holdback_pause(pid)
651    }
652
653    #[cfg(test)]
654    pub(crate) fn resume_test_process(&self, pid: u64) -> bool {
655        self.inner.runtime.ready_waker(pid).is_some_and(|waker| {
656            waker.fire();
657            true
658        })
659    }
660
661    /// Reserved push reply slots outstanding (test observability for the public
662    /// push paths — lets e2e tests assert slot reclamation and cap accounting).
663    #[cfg(test)]
664    pub(super) fn pending_push_count(&self) -> usize {
665        self.inner.runtime.pending_push_count()
666    }
667
668    /// R6 test seam: a [`ReadyWaker`](super::wake::ReadyWaker) for `pid` — the same
669    /// handle a subscription-inbox or reply-availability notifier fires.
670    #[cfg(test)]
671    pub(super) fn ready_waker(&self, pid: u64) -> Option<super::wake::ReadyWaker> {
672        self.inner.runtime.ready_waker(pid)
673    }
674
675    /// Registered readiness tokens held in host records (test observability).
676    #[cfg(test)]
677    pub(super) fn readiness_registration_count(&self) -> usize {
678        self.inner.runtime.readiness_registration_count()
679    }
680
681    /// Kernel fd registered for `pid` (test observability for fd-reuse races).
682    #[cfg(test)]
683    pub(super) fn readiness_fd(&self, pid: u64) -> Option<RawFd> {
684        self.inner.runtime.readiness_fd(pid)
685    }
686
687    /// The readiness token registered for `pid` (test observability). Lets the
688    /// fd-reuse successor oracle capture a stale token before reclamation and
689    /// replay its deregister afterwards, proving it is a keyed no-op.
690    #[cfg(test)]
691    pub(super) fn readiness_token(&self, pid: u64) -> Option<ReadinessToken> {
692        self.inner.runtime.readiness_token(pid)
693    }
694
695    /// Installs the pid-specific reclamation gate (oracle 26) and returns its
696    /// `(reached, release, done)` endpoints.
697    #[cfg(test)]
698    pub(super) fn install_reclaim_barrier(
699        &self,
700        pid: u64,
701    ) -> (Arc<Barrier>, Arc<Barrier>, Arc<Barrier>) {
702        self.inner.runtime.install_reclaim_barrier(pid)
703    }
704
705    /// A weak handle to the connection runtime (test observability): lets a
706    /// lifetime test assert the runtime — and transitively the durable store's
707    /// writer lock — is released synchronously at supervisor drop rather than
708    /// held by the detached reclaim reactor.
709    #[cfg(test)]
710    pub(super) fn runtime_weak(&self) -> Weak<ConnectionRuntime> {
711        Arc::downgrade(&self.inner.runtime)
712    }
713
714    /// Installs a one-use observation for the process-owned stream at `fd` being
715    /// dropped. External scheduler termination removes the process-table entry
716    /// before an executing native handler is destroyed, so table absence is not
717    /// sufficient evidence that the descriptor is reusable.
718    #[cfg(test)]
719    pub(super) fn observe_process_stream_drop(&self, fd: RawFd) -> Receiver<()> {
720        self.inner.runtime.observe_process_stream_drop(fd)
721    }
722
723    /// Installs a one-use arm-to-probe barrier and returns its test endpoints.
724    #[cfg(test)]
725    pub(super) fn install_pre_wait_barrier(&self) -> (Arc<Barrier>, Arc<Barrier>) {
726        self.inner.runtime.install_pre_wait_barrier()
727    }
728
729    /// Barrier-staged final probes that observed newly arrived work.
730    #[cfg(test)]
731    pub(super) fn pre_wait_probe_hits(&self) -> u64 {
732        self.inner.runtime.pre_wait_probe_hits()
733    }
734
735    /// Installs the one-use drain-park gate (oracles 18, 19) and returns its
736    /// `(armed, release)` endpoints.
737    #[cfg(test)]
738    pub(super) fn install_drain_park_barrier(&self) -> (Arc<Barrier>, Arc<Barrier>) {
739        self.inner.runtime.install_drain_park_barrier()
740    }
741
742    /// Drain waiter wakes that observed a real connection removal (oracle 12).
743    #[cfg(test)]
744    pub(super) fn drain_exit_wakes(&self) -> u64 {
745        self.inner.runtime.drain_exit_wakes()
746    }
747
748    /// Drain waiter deadline expirations (oracles 12, 16).
749    #[cfg(test)]
750    pub(super) fn drain_deadline_hits(&self) -> u64 {
751        self.inner.runtime.drain_deadline_hits()
752    }
753}
754
755/// Handle for one supervised connection process.
756#[derive(Clone, Debug)]
757pub struct ConnectionHandle {
758    pid: u64,
759    peer_addr: Option<SocketAddr>,
760    connection_incarnation: Option<ConnectionIncarnation>,
761    supervisor: Arc<SupervisorInner>,
762}
763
764impl ConnectionHandle {
765    /// Returns the beamr process id for this connection.
766    #[must_use]
767    pub const fn pid(&self) -> u64 {
768        self.pid
769    }
770
771    /// Returns the peer address if it was available from the accepted stream.
772    #[must_use]
773    pub const fn peer_addr(&self) -> Option<SocketAddr> {
774        self.peer_addr
775    }
776
777    /// Returns the durable participant connection incarnation, when this
778    /// supervisor has a complete participant service installed.
779    ///
780    /// `None` identifies a services adapter that does not advertise participant
781    /// lifecycle semantics.
782    #[must_use]
783    pub const fn connection_incarnation(&self) -> Option<ConnectionIncarnation> {
784        self.connection_incarnation
785    }
786
787    /// Returns whether the beamr process is still live.
788    #[must_use]
789    pub fn is_live(&self) -> bool {
790        self.supervisor
791            .scheduler
792            .process_table()
793            .get(self.pid)
794            .is_some()
795    }
796
797    /// Requests an error exit for tests and supervisor control paths.
798    ///
799    /// # Errors
800    /// Returns [`ServerError`] when the process is no longer live.
801    pub fn request_crash(&self) -> Result<(), ServerError> {
802        if self
803            .supervisor
804            .scheduler
805            .enqueue_atom_message(self.pid, Atom::ERROR)
806        {
807            Ok(())
808        } else {
809            Err(ServerError::ListenerAccept {
810                message: format!("connection process {} is not live", self.pid),
811            })
812        }
813    }
814}
815
816/// Awaits the correlated reply to a single server-initiated push.
817///
818/// Returned by [`ConnectionSupervisor::push_to_connection`]. The reply slot is
819/// resolved when the originating connection process receives a `PushReply` frame
820/// carrying the same correlation id, so [`PushReplyAwaiter::receive`] blocks
821/// (bounded) for that one correlated answer.
822#[derive(Debug)]
823pub struct PushReplyAwaiter {
824    correlation_id: u64,
825    receiver: Receiver<Vec<u8>>,
826    /// This push's absolute reply deadline, mirrored from its slot. `None` (the
827    /// default push) selects the no-deadline receive path, which NEVER touches
828    /// the runtime — byte-compatible with 0.2.3, no shared-lock exposure.
829    /// `Some` lets `receive` wait `min(caller quantum, time until deadline)` and
830    /// resolve expiry promptly, so the caller's quantum can never select a
831    /// deadlined push's terminal outcome.
832    deadline: Option<Instant>,
833    /// Weak handle to the owning runtime, used ONLY by the explicit-deadline
834    /// path to resolve expiry host-side at [`receive`](Self::receive). A
835    /// no-deadline push never upgrades it. `Weak` so the awaiter never keeps the
836    /// runtime alive; if it is already gone, the slot (and its sender) is gone
837    /// with it — the connection side is torn down.
838    runtime: Weak<ConnectionRuntime>,
839}
840
841impl PushReplyAwaiter {
842    /// Returns the correlation id this awaiter is matched on.
843    #[must_use]
844    pub const fn correlation_id(&self) -> u64 {
845        self.correlation_id
846    }
847
848    /// Blocks up to `timeout` for the client's correlated reply payload.
849    ///
850    /// `timeout` is a WAIT QUANTUM ONLY — a MAXIMUM wait, not a promise to
851    /// block: an elapsed poll is a benign re-arm, never a failure; the reply's
852    /// lifetime belongs to the push. A caller may re-invoke `receive`
853    /// indefinitely after a [`ServerError::PushReplyTimeout`]: the reserved slot
854    /// is untouched and a later reply is still delivered byte-exact. The poll
855    /// quantum never changes the protocol outcome — for a deadlined push the
856    /// call waits no longer than the EARLIER of the caller's quantum and the
857    /// push's deadline, so the terminal expiry is returned promptly once due,
858    /// never held until the quantum ends and never deferred past it.
859    ///
860    /// A push with no explicit deadline never touches shared supervisor state
861    /// here: the elapsed quantum returns straight from the channel wait
862    /// (behaviour-compatible with 0.2.3 — no registry lock, no contention, no
863    /// poison exposure on the unchanged API).
864    ///
865    /// # Errors
866    /// Returns [`ServerError::PushReplyTimeout`] when no reply arrived within this
867    /// `timeout` quantum and the push's deadline (if any) is not yet due (a
868    /// benign re-arm — call again to keep waiting);
869    /// [`ServerError::PushReplyExpired`] when the push carried an explicit reply
870    /// deadline (via
871    /// [`push_to_connection_with_deadline`](ConnectionSupervisor::push_to_connection_with_deadline))
872    /// and that deadline is due (terminal: the slot is removed and its §5 cap
873    /// admission released; returned as soon as the deadline passes, even
874    /// mid-quantum — but evaluated at observation points, not against the wall
875    /// clock: a reply already delivered when this call observes the slot wins
876    /// over expiry, even if it arrived after the deadline instant); or
877    /// [`ServerError::PushReplyDisconnected`] when the connection process
878    /// dropped the reply slot (the connection closed — the prompt worker-death
879    /// signal). The variants are distinct so callers classify by type, not
880    /// message.
881    pub fn receive(&self, timeout: Duration) -> Result<Vec<u8>, ServerError> {
882        self.deadline.map_or_else(
883            || self.receive_no_deadline(timeout),
884            |deadline| self.receive_deadlined(timeout, deadline),
885        )
886    }
887
888    /// The default-push receive: exactly the 0.2.3 shape. One bounded channel
889    /// wait; an elapsed quantum is a benign timeout straight from the channel —
890    /// no runtime upgrade, no registry lock, EVER (unrelated registry work can
891    /// never stretch this call past its quantum, and registry poison cannot
892    /// reach it).
893    fn receive_no_deadline(&self, timeout: Duration) -> Result<Vec<u8>, ServerError> {
894        match self.receiver.recv_timeout(timeout) {
895            Ok(payload) => Ok(payload),
896            Err(RecvTimeoutError::Timeout) => Err(ServerError::PushReplyTimeout {
897                correlation_id: self.correlation_id,
898            }),
899            Err(RecvTimeoutError::Disconnected) => Err(ServerError::PushReplyDisconnected {
900                correlation_id: self.correlation_id,
901            }),
902        }
903    }
904
905    /// The deadlined receive: waits `min(caller quantum, time until deadline)`
906    /// and re-evaluates reply-first-then-expiry on every wake, so the caller's
907    /// quantum can never select the terminal outcome (S1). Order per iteration:
908    ///
909    /// 1. Deliver a reply already in hand — an answer that is here must never be
910    ///    reported as a timeout OR an expiry (the observation-point rule).
911    /// 2. If the deadline is due, resolve expiry atomically against the registry
912    ///    (`expire_slot`) and return the terminal outcome promptly — even when
913    ///    the caller's quantum has time left (the quantum is a max wait).
914    /// 3. Otherwise wait for the earlier of quantum-remaining and deadline; a
915    ///    wake re-runs 1-2, and an exhausted quantum before the deadline is the
916    ///    benign `PushReplyTimeout` re-arm with the slot untouched.
917    fn receive_deadlined(
918        &self,
919        timeout: Duration,
920        deadline: Instant,
921    ) -> Result<Vec<u8>, ServerError> {
922        let started = Instant::now();
923        loop {
924            if let Some(result) = self.try_take_reply() {
925                return result;
926            }
927            let now = Instant::now();
928            if now >= deadline {
929                return self.expire_slot();
930            }
931            let quantum_left = timeout.saturating_sub(now.duration_since(started));
932            if quantum_left.is_zero() {
933                return Err(ServerError::PushReplyTimeout {
934                    correlation_id: self.correlation_id,
935                });
936            }
937            match self
938                .receiver
939                .recv_timeout(quantum_left.min(deadline.duration_since(now)))
940            {
941                Ok(payload) => return Ok(payload),
942                Err(RecvTimeoutError::Disconnected) => {
943                    return Err(ServerError::PushReplyDisconnected {
944                        correlation_id: self.correlation_id,
945                    });
946                }
947                // Re-loop: deliver a reply that raced the wake, expire a
948                // now-due deadline, or report the exhausted quantum benignly.
949                Err(RecvTimeoutError::Timeout) => {}
950            }
951        }
952    }
953
954    /// Resolves a due deadline against the registry's atomic removal transition.
955    fn expire_slot(&self) -> Result<Vec<u8>, ServerError> {
956        let timeout_error = || ServerError::PushReplyTimeout {
957            correlation_id: self.correlation_id,
958        };
959        let Some(runtime) = self.runtime.upgrade() else {
960            // The runtime is gone, and the slot map (with every sender) with it:
961            // the connection side is torn down. Re-check the channel so the
962            // dropped sender reads as the established DISCONNECTED outcome — a
963            // dead runtime must not be misreported as a benign healthy-but-slow
964            // timeout (S4).
965            return self
966                .try_take_reply()
967                .unwrap_or(Err(ServerError::PushReplyDisconnected {
968                    correlation_id: self.correlation_id,
969                }));
970        };
971        match runtime.expire_push_if_due(self.correlation_id) {
972            PushSlotDisposition::Expired => Err(ServerError::PushReplyExpired {
973                correlation_id: self.correlation_id,
974            }),
975            // Unreachable by construction (this is only called with the deadline
976            // due, and the registry re-reads a monotonic clock); honest benign
977            // fallback rather than a panic.
978            PushSlotDisposition::Live => Err(timeout_error()),
979            // Another path (a concurrent `resolve_push`, or connection close)
980            // removed the slot under the registry lock while we waited on it. Its
981            // send, if any, happens under that same lock, so re-check the channel:
982            // a delivered reply is present now; a dropped sender is disconnected.
983            PushSlotDisposition::Absent => self
984                .try_take_reply()
985                .unwrap_or_else(|| Err(timeout_error())),
986        }
987    }
988
989    /// Non-blocking check for a reply already sitting in the channel. `Some` with
990    /// the payload or a disconnected error; `None` when the channel is still empty
991    /// (no reply yet — the caller re-arms).
992    fn try_take_reply(&self) -> Option<Result<Vec<u8>, ServerError>> {
993        match self.receiver.try_recv() {
994            Ok(payload) => Some(Ok(payload)),
995            Err(TryRecvError::Disconnected) => Some(Err(ServerError::PushReplyDisconnected {
996                correlation_id: self.correlation_id,
997            })),
998            Err(TryRecvError::Empty) => None,
999        }
1000    }
1001}
1002
1003/// The kernel-parked exit-event reactor (W4 leg 1 reclamation carve-out, §4.1).
1004/// It is the single TOLD source that reclaims connection host records for
1005/// processes that exit WITHOUT a final handler slice, replacing the retired
1006/// per-accept `reap_crashed` scan for that class.
1007///
1008/// It blocks on beamr's sole exit-event subscription — never polling, never
1009/// timed — and on each delivered [`ExitEvent::Exited`] it (1) drains the
1010/// retained additive outcome so beamr's exactly-once outcome store stays bounded
1011/// (we are the sole subscriber and therefore the sole drainer) and (2) reclaims
1012/// the pid through [`ConnectionRuntime::reclaim_terminated`], which funnels into
1013/// `remove()`. On the bounded queue's [`ExitEvent::Lagged`] overflow marker it
1014/// runs exactly one reconciliation pass over the tracked records (beamr's
1015/// documented recovery), driven by that one TELL — not a timer.
1016///
1017/// It holds WEAK handles to both the scheduler and the runtime and upgrades them
1018/// per event, so it never keeps either alive past supervisor drop. It returns
1019/// when the subscription disconnects (scheduler and publisher dropped) OR when a
1020/// per-event upgrade fails — both observed at an event delivery, never sampled,
1021/// so there is no stop flag (LAW-1). The runtime and its durable store therefore
1022/// release synchronously at supervisor drop rather than after the reactor exits.
1023fn run_reclaim_reactor(
1024    subscription: &ExitEventSubscription,
1025    scheduler: &Weak<Scheduler>,
1026    runtime: &Weak<ConnectionRuntime>,
1027) {
1028    loop {
1029        match subscription.recv() {
1030            Ok(ExitEvent::Exited { pid, reason }) => {
1031                let Some(runtime) = runtime.upgrade() else {
1032                    return;
1033                };
1034                runtime.deliver_reclamation(scheduler, pid, reason);
1035            }
1036            Ok(ExitEvent::Lagged) => {
1037                let (Some(runtime), Some(scheduler)) = (runtime.upgrade(), scheduler.upgrade())
1038                else {
1039                    return;
1040                };
1041                runtime.reap_crashed(&scheduler);
1042            }
1043            Err(_) => return,
1044        }
1045    }
1046}
1047
1048pub(super) struct SupervisorInner {
1049    scheduler: Arc<Scheduler>,
1050    runtime: Arc<ConnectionRuntime>,
1051    incarnations: Option<Arc<ConnectionIncarnationAuthority>>,
1052}
1053
1054impl std::fmt::Debug for SupervisorInner {
1055    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1056        formatter
1057            .debug_struct("SupervisorInner")
1058            .field("runtime", &self.runtime)
1059            .finish_non_exhaustive()
1060    }
1061}
1062
1063impl SupervisorInner {
1064    fn new(
1065        services: Arc<dyn ConnectionServices>,
1066        notifier: Option<Arc<dyn ConnectionNotifier>>,
1067        auth_token: Option<Vec<u8>>,
1068        limits: LimitsConfig,
1069        fatal_shutdown: Option<ShutdownHandle>,
1070    ) -> Result<Self, ServerError> {
1071        let installed_services = ConnectionServiceInstallation::capture(services);
1072        let incarnations = installed_services
1073            .participant_service
1074            .as_ref()
1075            .map(
1076                |service| -> Result<Arc<ConnectionIncarnationAuthority>, ServerError> {
1077                    ConnectionIncarnationAuthority::startup(
1078                        service.durable_store(),
1079                        limits.max_connections,
1080                        service.publication_conversation_limit(),
1081                        service,
1082                    )
1083                    .map(Arc::new)
1084                },
1085            )
1086            .transpose()?;
1087        let atoms = AtomTable::with_common_atoms();
1088        let control_atom = atoms.intern(CONNECTION_SHUTDOWN_CONTROL_ATOM);
1089        let registry = Arc::new(ModuleRegistry::new());
1090
1091        let scheduler = Scheduler::with_services(
1092            SchedulerConfig {
1093                thread_count: Some(CONNECTION_SCHEDULER_THREADS),
1094                ..SchedulerConfig::default()
1095            },
1096            SchedulerServices::from_config().owned_readiness(),
1097            registry,
1098        )
1099        .map_err(|message| ServerError::ListenerAccept {
1100            message: format!("failed to start connection scheduler: {message}"),
1101        })?;
1102        let ready_atom = atoms.intern(CONNECTION_READY_ATOM);
1103        let scheduler = Arc::new(scheduler);
1104        // The runtime captures a WEAK handle to the connection scheduler so
1105        // notifier wakes (R3/R1(vi)) can be fired from another actor's slice
1106        // without a strong scheduler↔process↔runtime cycle that would leak the
1107        // whole connection scheduler.
1108        let runtime = Arc::new(ConnectionRuntime::new(
1109            ConnectionRuntimeInstallation {
1110                services: installed_services,
1111                incarnations: incarnations.clone(),
1112                fatal_shutdown,
1113            },
1114            control_atom,
1115            ready_atom,
1116            Arc::downgrade(&scheduler),
1117            notifier,
1118            auth_token,
1119            limits,
1120        ));
1121        // W4 leg 1 reclamation carve-out (§4.1): the kernel-parked exit-event
1122        // reactor is the TOLD source that reclaims a connection host record whose
1123        // process exited WITHOUT a final handler slice (external/panic
1124        // termination). It blocks on beamr's single exit-event subscription —
1125        // never a poll — and routes every reclamation through the same `remove()`
1126        // funnel as an ordinary exit. Detached on purpose: it exits when the
1127        // scheduler (and so its event publisher) drops, so there is no stop flag
1128        // to sample (LAW-1).
1129        match scheduler.subscribe_exit_events() {
1130            Some(subscription) => {
1131                let reactor_scheduler = Arc::downgrade(&scheduler);
1132                // WEAK, symmetric with the scheduler handle: the reactor must not
1133                // keep the runtime (and its durable store's writer lock) alive past
1134                // supervisor drop. It upgrades per event and exits on a failed
1135                // upgrade, so the runtime is released synchronously at drop.
1136                let reactor_runtime = Arc::downgrade(&runtime);
1137                thread::Builder::new()
1138                    .name("liminal-connection-reclaim".to_owned())
1139                    .spawn(move || {
1140                        run_reclaim_reactor(&subscription, &reactor_scheduler, &reactor_runtime);
1141                    })
1142                    .map_err(|error| ServerError::ListenerAccept {
1143                        message: format!("failed to start connection reclamation reactor: {error}"),
1144                    })?;
1145            }
1146            None => {
1147                tracing::error!(
1148                    "connection scheduler exit-event subscription unavailable; \
1149                     external-termination reclamation has no TOLD exit source (the \
1150                     shutdown-drain scan that once backstopped it was retired by W4 leg 3)"
1151                );
1152            }
1153        }
1154        Ok(Self {
1155            scheduler,
1156            runtime,
1157            incarnations,
1158        })
1159    }
1160
1161    fn spawn_connection(
1162        self: &Arc<Self>,
1163        stream: TcpStream,
1164    ) -> Result<ConnectionHandle, ServerError> {
1165        // §5 `max_connections`: ATOMIC admission reservation acquired BEFORE any
1166        // process construction (review round 1 item 7 — a signed bound must not
1167        // be exceedable by concurrent callers; check-then-spawn across an
1168        // unlocked window was). The CAS reservation is released on every failure
1169        // path below and converts into the connection record at `register`;
1170        // thereafter the single record-removal path (`remove`) releases it. An
1171        // over-cap accept therefore costs nothing and the bound holds under any
1172        // concurrency.
1173        self.runtime.try_reserve_admission()?;
1174        let reservation = AdmissionReservation {
1175            runtime: &self.runtime,
1176            armed: true,
1177        };
1178        stream
1179            .set_nonblocking(true)
1180            .map_err(|error| ServerError::ListenerAccept {
1181                message: format!("failed to configure connection stream: {error}"),
1182            })?;
1183        let peer_addr = stream.peer_addr().ok();
1184        // The host-held duplicate keeps the fd alive until the single record-removal
1185        // path has synchronously deregistered readiness. External process death can
1186        // therefore never let fd reuse overtake host-side deregistration.
1187        let fd_guard = stream
1188            .try_clone()
1189            .map_err(|error| ServerError::ListenerAccept {
1190                message: format!("failed to retain connection fd for teardown: {error}"),
1191            })?;
1192        let connection_incarnation = self.allocate_connection_incarnation()?;
1193        let holder = Arc::new(Mutex::new(Some(stream)));
1194        let runtime = Arc::clone(&self.runtime);
1195        let process_holder = Arc::clone(&holder);
1196        let factory: NativeHandlerFactory = Box::new(move || {
1197            Box::new(ConnectionProcess::from_holder(
1198                Arc::clone(&runtime),
1199                peer_addr,
1200                &process_holder,
1201                connection_incarnation,
1202            ))
1203        });
1204        let pid =
1205            self.scheduler
1206                .spawn_native(factory)
1207                .map_err(|error| ServerError::ListenerAccept {
1208                    message: format!("failed to spawn connection process: {error}"),
1209                })?;
1210        if let Err(error) = self.runtime.register_connection(
1211            pid,
1212            peer_addr,
1213            connection_incarnation,
1214            MountKind::Tcp,
1215            Some(fd_guard),
1216        ) {
1217            // Registration failure leaves no host record to reap. Terminate the
1218            // just-spawned process explicitly so neither its stream nor admission
1219            // reservation can escape this failed spawn.
1220            self.scheduler.terminate_process(pid, ExitReason::Error);
1221            return Err(error);
1222        }
1223        // The reservation is now owned by the registered record: `remove` (the
1224        // single record-removal path — finish/mark_crashed/reap all funnel
1225        // through it) releases the admission when the record goes away.
1226        reservation.convert();
1227        Ok(ConnectionHandle {
1228            pid,
1229            peer_addr,
1230            connection_incarnation,
1231            supervisor: Arc::clone(self),
1232        })
1233    }
1234
1235    /// LP-WS-TRANSPORT R1.3: the sibling-transport spawn body. Mirrors
1236    /// [`Self::spawn_connection`]'s admission → incarnation → spawn → register →
1237    /// convert sequence exactly (same reservation guard, same failure rollback,
1238    /// same single record-removal ownership), differing only in that the caller
1239    /// supplies the native handler factory and the host-held fd guard instead of
1240    /// a raw `TcpStream`. Purely additive; the TCP path never calls this.
1241    fn spawn_transport_connection(
1242        self: &Arc<Self>,
1243        peer_addr: Option<SocketAddr>,
1244        fd_guard: Option<TcpStream>,
1245        mount: MountKind,
1246        build_factory: &dyn Fn(
1247            Arc<ConnectionRuntime>,
1248            Option<ConnectionIncarnation>,
1249        ) -> NativeHandlerFactory,
1250    ) -> Result<ConnectionHandle, ServerError> {
1251        self.runtime.try_reserve_admission()?;
1252        let reservation = AdmissionReservation {
1253            runtime: &self.runtime,
1254            armed: true,
1255        };
1256        let connection_incarnation = self.allocate_connection_incarnation()?;
1257        let factory = build_factory(Arc::clone(&self.runtime), connection_incarnation);
1258        let pid =
1259            self.scheduler
1260                .spawn_native(factory)
1261                .map_err(|error| ServerError::ListenerAccept {
1262                    message: format!("failed to spawn connection process: {error}"),
1263                })?;
1264        if let Err(error) = self.runtime.register_connection(
1265            pid,
1266            peer_addr,
1267            connection_incarnation,
1268            mount,
1269            fd_guard,
1270        ) {
1271            self.scheduler.terminate_process(pid, ExitReason::Error);
1272            return Err(error);
1273        }
1274        reservation.convert();
1275        Ok(ConnectionHandle {
1276            pid,
1277            peer_addr,
1278            connection_incarnation,
1279            supervisor: Arc::clone(self),
1280        })
1281    }
1282
1283    fn allocate_connection_incarnation(
1284        &self,
1285    ) -> Result<Option<ConnectionIncarnation>, ServerError> {
1286        let Some(authority) = self.incarnations.as_ref() else {
1287            return Ok(None);
1288        };
1289        // Production-era uniqueness invariant: every published incarnation is
1290        // unique against ALL durable references — binding epochs committed
1291        // into conversation logs included — by allocator-log monotonicity
1292        // alone, not by the completeness of the reference set below.
1293        //
1294        //   1. Startup replays the durable allocator stream and STRICTLY
1295        //      increments the server incarnation, fsyncing the Startup event
1296        //      before any listener becomes ready
1297        //      (`IncarnationStream::startup`); a server value is never wrapped
1298        //      or reused, so no two process lifetimes share one.
1299        //   2. Within a lifetime, allocations are serialized under this
1300        //      authority's mutex, candidates start strictly above the durable
1301        //      `last_examined_connection_ordinal`
1302        //      (`allocate_connection_incarnation`), and every allocation's
1303        //      event is appended and flushed BEFORE its pair is published
1304        //      (`StartedIncarnationStream::allocate`), so ordinals never
1305        //      repeat within a lifetime and replay restores a head at or
1306        //      above every published ordinal.
1307        //   3. A durable reference can only name a pair this allocator
1308        //      previously PUBLISHED (binding epochs are committed only after
1309        //      their connection was admitted), and the same store's flush
1310        //      barrier orders the allocator event before any conversation-log
1311        //      entry that references it.
1312        //
1313        // The live-connection reference set below is therefore defense in
1314        // depth — a bounded collision skip against a rolled-back or divergent
1315        // allocator stream — never the uniqueness foundation, and never a raw
1316        // caller-supplied matrix.
1317        let references = self.runtime.complete_active_incarnation_references()?;
1318        authority.allocate(&references).map(Some)
1319    }
1320
1321    fn broadcast_control(&self, control: &ConnectionControl) {
1322        for connection in self.runtime.active_connections() {
1323            if !self.enqueue_control(connection.pid, control.clone()) {
1324                tracing::debug!(
1325                    connection_pid = connection.pid,
1326                    peer_addr = ?connection.peer_addr,
1327                    ?control,
1328                    "connection control message skipped because process is not live"
1329                );
1330            }
1331        }
1332    }
1333
1334    /// Queues `control` for `pid` and wakes the process. Returns whether the
1335    /// control was PUBLISHED (left in the queue with a successful wake, or
1336    /// already consumed by a drain) — `false` guarantees no consumer ever saw
1337    /// it.
1338    ///
1339    /// S8: a failed wake does NOT prove the queued control was never consumed.
1340    /// The insert releases the queue lock before the wake attempt, and a
1341    /// process already executing a control drain (each control atom drains ALL
1342    /// queued controls for the pid) can pop the just-inserted entry in that
1343    /// window, then exit before the wake check. Publication is therefore
1344    /// disambiguated BY OBSERVATION on the failed-wake path: `remove_control`
1345    /// finding and removing the entry proves no consumer saw it (truly
1346    /// unpublished — `false`); finding nothing proves a drain consumed it
1347    /// (`pop_control` is the only other remover of queue entries, and the
1348    /// removal key embeds the push's runtime-unique correlation id, so it can
1349    /// never match a different entry) — the control was published and the
1350    /// caller's slot lifecycle carries the delivery truth (`true`).
1351    fn enqueue_control(&self, pid: u64, control: ConnectionControl) -> bool {
1352        // Keep a key for the failure-path removal before the control is moved into
1353        // the queue, so a non-`Copy` (push) control can still be located and pulled
1354        // back out if the scheduler wakeup fails.
1355        let removal_key = control.clone();
1356        if self.runtime.push_control(pid, control).is_err() {
1357            return false;
1358        }
1359        // Deterministic test seam in the insert->wake window (S8 staging).
1360        #[cfg(test)]
1361        self.runtime.run_pre_wake_barrier();
1362        if self
1363            .scheduler
1364            .enqueue_atom_message(pid, self.runtime.control_atom())
1365        {
1366            true
1367        } else {
1368            // Failed wake: the entry's fate is the publication verdict. Removed
1369            // here => nobody consumed it => unpublished. Already gone => a
1370            // drain consumed it before the wake check => published. (A poisoned
1371            // queue lock reads as not-removed => published — the safe
1372            // direction: the slot lifecycle then reports the truthful outcome,
1373            // whereas claiming "unpublished" could be a lie.)
1374            !self.runtime.remove_control(pid, &removal_key)
1375        }
1376    }
1377}
1378
1379/// RAII guard for one §5 `max_connections` admission reservation.
1380///
1381/// Acquired (via [`ConnectionRuntime::try_reserve_admission`]) before any process
1382/// construction in `spawn_connection`; every early-return failure path releases
1383/// it through `Drop`, and a successful `register` converts it into the
1384/// connection record (whose removal releases the admission instead). RAII means
1385/// no failure path — present or future — can leak a reservation.
1386struct AdmissionReservation<'a> {
1387    runtime: &'a ConnectionRuntime,
1388    armed: bool,
1389}
1390
1391impl AdmissionReservation<'_> {
1392    /// Converts the reservation into record ownership: `Drop` no longer releases
1393    /// it, because the registered record's removal will.
1394    fn convert(mut self) {
1395        self.armed = false;
1396    }
1397}
1398
1399impl Drop for AdmissionReservation<'_> {
1400    fn drop(&mut self) {
1401        if self.armed {
1402            self.runtime.release_admission();
1403        }
1404    }
1405}
1406
1407#[derive(Debug, Clone, PartialEq, Eq)]
1408pub(super) enum ConnectionControl {
1409    NotifyShutdown,
1410    ForceClose,
1411    /// Server-initiated push of an opaque payload, correlated by `correlation_id`,
1412    /// to be written out as a [`Frame::Push`] by the receiving connection process.
1413    Push {
1414        correlation_id: u64,
1415        payload: Vec<u8>,
1416    },
1417}
1418
1419#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1420pub struct ActiveConnection {
1421    pid: u64,
1422    peer_addr: Option<SocketAddr>,
1423}
1424
1425#[cfg(test)]
1426#[derive(Debug, Clone)]
1427struct PreWaitBarrier {
1428    armed: Arc<Barrier>,
1429    release: Arc<Barrier>,
1430}
1431
1432/// Pid-specific, one-use deterministic gate on the reclamation delivery path
1433/// (oracle 26). The exit-event reactor stages it only for the targeted pid, so
1434/// unrelated exits still reclaim immediately; for the target it rendezvouses
1435/// `reached` (proving the pid is dead but its record still tracked — the S8
1436/// reclamation window), then `release` (the harness lets the reclaim proceed),
1437/// then `done` (the `remove()` funnel completed). It changes no production
1438/// semantics — the whole type and its call sites are `#[cfg(test)]`.
1439#[cfg(test)]
1440#[derive(Debug, Clone)]
1441struct ReclaimBarrier {
1442    pid: u64,
1443    reached: Arc<Barrier>,
1444    release: Arc<Barrier>,
1445    done: Arc<Barrier>,
1446}
1447
1448#[derive(Debug)]
1449struct ConnectionServiceInstallation {
1450    services: Arc<dyn ConnectionServices>,
1451    participant_service: Option<InstalledParticipantService>,
1452}
1453
1454impl ConnectionServiceInstallation {
1455    /// Captures the service adapter's capability posture exactly once, before
1456    /// participant incarnation startup or connection process construction.
1457    fn capture(services: Arc<dyn ConnectionServices>) -> Self {
1458        let participant_service = services.participant_service();
1459        Self {
1460            services,
1461            participant_service,
1462        }
1463    }
1464}
1465
1466#[derive(Debug)]
1467struct ConnectionRuntimeInstallation {
1468    services: ConnectionServiceInstallation,
1469    incarnations: Option<Arc<ConnectionIncarnationAuthority>>,
1470    fatal_shutdown: Option<ShutdownHandle>,
1471}
1472
1473#[derive(Debug)]
1474pub(super) struct ConnectionRuntime {
1475    services: Arc<dyn ConnectionServices>,
1476    /// Complete participant handler/store bundle captured at supervisor startup.
1477    /// `Some` is paired with an incarnation authority on `SupervisorInner`.
1478    participant_service: Option<InstalledParticipantService>,
1479    /// Same started authority used for allocation, shared for terminal Open/Complete.
1480    incarnations: Option<Arc<ConnectionIncarnationAuthority>>,
1481    /// Existing runtime shutdown activation notified by the first post-Open fatal.
1482    /// Test-only/runtime-less constructors intentionally carry `None`.
1483    fatal_shutdown: Option<ShutdownHandle>,
1484    records: Mutex<HashMap<u64, ConnectionRecord>>,
1485    controls: Mutex<Vec<QueuedConnectionControl>>,
1486    control_atom: Atom,
1487    /// R6 single `READY` wake atom for this connection scheduler. Fired by every
1488    /// wake source's notifier (R3/R1(vi)); coalescing and duplicates are harmless.
1489    ready_atom: Atom,
1490    /// Weak handle to the connection scheduler, used to build [`ReadyWaker`]s a
1491    /// notifier fires from another actor's slice. Weak so it never keeps the
1492    /// scheduler alive (the scheduler owns the processes that own this runtime).
1493    scheduler: Weak<Scheduler>,
1494    /// W4 leg 3 (§4.3) TOLD drain-completion primitive. Reuses the
1495    /// [`ShutdownHandle`] `Condvar` shape (`shutdown.rs` reuse candidate (c)): a
1496    /// monotonic connection-removal generation guarded by [`Self::drain_removed`]'s
1497    /// mutex, bumped once whenever [`Self::remove`] actually drops a record — the
1498    /// single removal funnel every exit route (in-slice `mark_crashed`/`finish`,
1499    /// the reclaim reactor, and the reconciliation scan) reaches. The
1500    /// shutdown-sequence drain/settle waiter parks on the `Condvar` and wakes only
1501    /// on a delivered exit (a generation bump + `notify_all`) or the one admitted
1502    /// deadline it passes to `wait_timeout`. No periodic reap or count scan.
1503    drain_generation: Mutex<u64>,
1504    /// Woken on every connection-record removal; the drain/settle waiter parks
1505    /// here. Paired with [`Self::drain_generation`] under the same mutex so an
1506    /// exit delivered between the waiter's arm-before-observe snapshot and its
1507    /// park cannot be lost (oracle 18).
1508    drain_removed: Condvar,
1509    /// FIX A-ii shutdown flush barrier: a delivery-quiescence generation of the
1510    /// exact TOLD `drain_generation` shape. Bumped whenever a connection parks
1511    /// with every accepted publish already fanned out to its socket — but only
1512    /// while [`Self::settle_armed`] is set (the flush barrier is waiting) — so
1513    /// normal operation pays nothing. Guards [`Self::settle_changed`]'s mutex.
1514    settle_generation: Mutex<u64>,
1515    /// Woken when a connection reaches delivery quiescence during shutdown; the
1516    /// flush barrier parks here, paired with [`Self::settle_generation`] for the
1517    /// same arm-before-observe safety as the drain waiter.
1518    settle_changed: Condvar,
1519    /// Set only while the flush barrier is actively waiting, so a park bumps the
1520    /// settle generation and wakes the barrier ONLY when someone is listening.
1521    settle_armed: AtomicBool,
1522    /// Test-only count of drain waiter wakes that observed a real removal
1523    /// (generation advanced across the park). A quiet drain records zero — it
1524    /// wakes only for the single deadline (oracle 12).
1525    #[cfg(test)]
1526    drain_exit_wakes: AtomicU64,
1527    /// Test-only count of drain waiter deadline expirations. A quiet drain that
1528    /// times out records exactly one — one arming, one delivery, no helper tick
1529    /// (oracles 12, 16).
1530    #[cfg(test)]
1531    drain_deadline_hits: AtomicU64,
1532    /// Test-only one-use gate in the drain waiter's observe->park window, so a
1533    /// harness can deliver an exit strictly after the completion observation and
1534    /// before the park to pin the arm-before-observe barrier (oracles 18, 19).
1535    #[cfg(test)]
1536    drain_park_barrier: Mutex<Option<PreWaitBarrier>>,
1537    /// R7 (§1.2(6)) test-only per-connection slice counter, keyed by pid. Bumped
1538    /// once at the head of every serviced slice. The park-flip's permanent rule-1
1539    /// assertion (a parked connection's counter must not advance without an event)
1540    /// reads this; the instrument lands now with a test proving it counts slices.
1541    #[cfg(test)]
1542    slice_counts: Mutex<HashMap<u64, u64>>,
1543    /// One-use readiness markers for the next serviced slice of a process.
1544    #[cfg(test)]
1545    slice_observers: Mutex<HashMap<u64, Sender<u64>>>,
1546    /// One-use readiness markers emitted only after the real final probe selects
1547    /// `Wait`, immediately before the native process returns to the scheduler.
1548    #[cfg(test)]
1549    park_observers: Mutex<HashMap<u64, Sender<u64>>>,
1550    /// Most recent slice count whose real final probe selected `Wait`.
1551    #[cfg(test)]
1552    park_counts: Mutex<HashMap<u64, u64>>,
1553    /// Explicit capacities consumed in TCP process construction order.
1554    #[cfg(test)]
1555    next_outbound_capacities: Mutex<VecDeque<usize>>,
1556    #[cfg(test)]
1557    participant_holdback_pauses: Mutex<HashMap<u64, Sender<()>>>,
1558    /// Deterministic test gate placed after arm and before the final probe.
1559    #[cfg(test)]
1560    pre_wait_barrier: Mutex<Option<PreWaitBarrier>>,
1561    /// Deterministic test gate in `enqueue_control`'s insert->wake window (S8).
1562    #[cfg(test)]
1563    pre_wake_barrier: Mutex<Option<PreWaitBarrier>>,
1564    /// Pid-specific one-use gate held on the reclamation delivery path so a test
1565    /// can pin the dead-but-tracked S8 window deterministically (oracle 26).
1566    #[cfg(test)]
1567    reclaim_barrier: Mutex<Option<ReclaimBarrier>>,
1568    /// Barrier-staged slices where the final probe found newly arrived work.
1569    #[cfg(test)]
1570    pre_wait_probe_hits: AtomicU64,
1571    /// One-use observers for process-owned streams reaching their actual drop
1572    /// boundary after external scheduler termination.
1573    #[cfg(test)]
1574    process_stream_drop_observers: Mutex<HashMap<RawFd, Sender<()>>>,
1575    /// One-shot reply slots for in-flight server pushes, keyed by correlation id.
1576    /// The supervisor registers a slot in `push_to_connection`; the connection
1577    /// process resolves it when the matching `PushReply` frame arrives. Each slot
1578    /// records the owning connection pid so the close path can drop a connection's
1579    /// outstanding slots and wake their awaiters with a prompt disconnected error.
1580    push_replies: Mutex<HashMap<u64, PendingPush>>,
1581    /// Monotonic source of push correlation ids. Server-allocated, so it never
1582    /// collides with a client-chosen id on this connection.
1583    next_push_id: AtomicU64,
1584    /// §5 `max_connections` admission counter. Incremented atomically (CAS
1585    /// against the limit) BEFORE a connection process is constructed and
1586    /// decremented on every spawn-failure path and on final record removal, so
1587    /// the signed bound holds under concurrent spawns — admission is never
1588    /// derived from the records-map length across an unlocked window.
1589    admissions: AtomicU64,
1590    /// Optional application hook invoked on worker registration and on the close
1591    /// of a connection that had registered. `None` keeps liminal standalone: a
1592    /// `WorkerRegister` is accepted with no callback.
1593    notifier: Option<Arc<dyn ConnectionNotifier>>,
1594    /// Configured connection auth token (the `[auth]` section's token as opaque
1595    /// bytes). `Some` gates the `Connect` handshake — the frame's `auth_token` must
1596    /// match under a constant-time comparison; `None` leaves the server open-access,
1597    /// byte-identical to the pre-auth behaviour.
1598    auth_token: Option<Vec<u8>>,
1599    /// Operational caps (§5). Enforced with typed refusals at admission:
1600    /// per-connection subscription, conversation, push, and pending-reply counts,
1601    /// plus the shared inbox byte budget. Non-config constructors carry the signed
1602    /// defaults ([`LimitsConfig::default`]).
1603    limits: LimitsConfig,
1604}
1605
1606impl ConnectionRuntime {
1607    fn new(
1608        installation: ConnectionRuntimeInstallation,
1609        control_atom: Atom,
1610        ready_atom: Atom,
1611        scheduler: Weak<Scheduler>,
1612        notifier: Option<Arc<dyn ConnectionNotifier>>,
1613        auth_token: Option<Vec<u8>>,
1614        limits: LimitsConfig,
1615    ) -> Self {
1616        let ConnectionRuntimeInstallation {
1617            services:
1618                ConnectionServiceInstallation {
1619                    services,
1620                    participant_service,
1621                },
1622            incarnations,
1623            fatal_shutdown,
1624        } = installation;
1625        Self {
1626            services,
1627            participant_service,
1628            incarnations,
1629            fatal_shutdown,
1630            records: Mutex::new(HashMap::new()),
1631            controls: Mutex::new(Vec::new()),
1632            control_atom,
1633            ready_atom,
1634            scheduler,
1635            drain_generation: Mutex::new(0),
1636            drain_removed: Condvar::new(),
1637            settle_generation: Mutex::new(0),
1638            settle_changed: Condvar::new(),
1639            settle_armed: AtomicBool::new(false),
1640            #[cfg(test)]
1641            drain_exit_wakes: AtomicU64::new(0),
1642            #[cfg(test)]
1643            drain_deadline_hits: AtomicU64::new(0),
1644            #[cfg(test)]
1645            drain_park_barrier: Mutex::new(None),
1646            #[cfg(test)]
1647            slice_counts: Mutex::new(HashMap::new()),
1648            #[cfg(test)]
1649            slice_observers: Mutex::new(HashMap::new()),
1650            #[cfg(test)]
1651            park_observers: Mutex::new(HashMap::new()),
1652            #[cfg(test)]
1653            park_counts: Mutex::new(HashMap::new()),
1654            #[cfg(test)]
1655            next_outbound_capacities: Mutex::new(VecDeque::new()),
1656            #[cfg(test)]
1657            participant_holdback_pauses: Mutex::new(HashMap::new()),
1658            #[cfg(test)]
1659            pre_wait_barrier: Mutex::new(None),
1660            #[cfg(test)]
1661            pre_wake_barrier: Mutex::new(None),
1662            #[cfg(test)]
1663            reclaim_barrier: Mutex::new(None),
1664            #[cfg(test)]
1665            pre_wait_probe_hits: AtomicU64::new(0),
1666            #[cfg(test)]
1667            process_stream_drop_observers: Mutex::new(HashMap::new()),
1668            push_replies: Mutex::new(HashMap::new()),
1669            next_push_id: AtomicU64::new(1),
1670            admissions: AtomicU64::new(0),
1671            notifier,
1672            auth_token,
1673            limits,
1674        }
1675    }
1676
1677    /// Atomically reserves one §5 `max_connections` admission slot: a CAS loop
1678    /// against the configured limit, so N concurrent callers racing for the last
1679    /// slot admit EXACTLY one — the bound cannot be transiently exceeded.
1680    ///
1681    /// # Errors
1682    /// Returns [`ServerError::ConnectionLimitReached`] when every slot is taken.
1683    fn try_reserve_admission(&self) -> Result<(), ServerError> {
1684        self.ensure_participant_service_live()?;
1685        let limit = self.limits.max_connections as u64;
1686        let mut current = self.admissions.load(Ordering::Acquire);
1687        loop {
1688            if current >= limit {
1689                return Err(ServerError::ConnectionLimitReached {
1690                    limit: self.limits.max_connections,
1691                });
1692            }
1693            match self.admissions.compare_exchange_weak(
1694                current,
1695                current + 1,
1696                Ordering::AcqRel,
1697                Ordering::Acquire,
1698            ) {
1699                Ok(_) => return Ok(()),
1700                Err(observed) => current = observed,
1701            }
1702        }
1703    }
1704
1705    /// Releases one admission slot. Called by the spawn failure paths (via the
1706    /// [`AdmissionReservation`] guard) and by [`Self::remove`] when a registered
1707    /// record is removed — exactly one release per reservation. Saturating so a
1708    /// spurious release can never wrap the counter.
1709    fn release_admission(&self) {
1710        let mut current = self.admissions.load(Ordering::Acquire);
1711        loop {
1712            let next = current.saturating_sub(1);
1713            match self.admissions.compare_exchange_weak(
1714                current,
1715                next,
1716                Ordering::AcqRel,
1717                Ordering::Acquire,
1718            ) {
1719                Ok(_) => return,
1720                Err(observed) => current = observed,
1721            }
1722        }
1723    }
1724
1725    /// The operational caps (§5) this runtime enforces.
1726    pub(super) const fn limits(&self) -> &LimitsConfig {
1727        &self.limits
1728    }
1729
1730    /// The connection's single R6 `READY` wake atom.
1731    pub(super) const fn ready_atom(&self) -> Atom {
1732        self.ready_atom
1733    }
1734
1735    /// Builds a [`ReadyWaker`] targeting `pid` on the connection scheduler, if the
1736    /// scheduler is still live. `None` when the scheduler is gone (teardown) or in
1737    /// scheduler-free unit tests — a notifier with no waker simply never wakes,
1738    /// which under the busy loop is redundant anyway (the every-slice pump still
1739    /// services the source). This is the seam every wake source installs its
1740    /// notifier through (R3/R1(vi)).
1741    pub(super) fn ready_waker(&self, pid: u64) -> Option<super::wake::ReadyWaker> {
1742        let scheduler = self.scheduler.upgrade()?;
1743        let ready_pending = self
1744            .records
1745            .lock()
1746            .ok()?
1747            .get(&pid)
1748            .map(|record| Arc::clone(&record.ready_pending))?;
1749        Some(super::wake::ReadyWaker::new(
1750            &scheduler,
1751            pid,
1752            self.ready_atom,
1753            ready_pending,
1754        ))
1755    }
1756
1757    /// Acknowledges READY edges whose mailbox atoms were drained before this slice.
1758    pub(super) fn acknowledge_ready(&self, pid: u64) {
1759        if let Ok(records) = self.records.lock()
1760            && let Some(record) = records.get(&pid)
1761        {
1762            record.ready_pending.store(false, Ordering::Release);
1763        }
1764    }
1765
1766    /// Reports a READY edge queued while the current process snapshot is executing.
1767    pub(super) fn ready_pending(&self, pid: u64) -> bool {
1768        self.records
1769            .lock()
1770            .ok()
1771            .and_then(|records| {
1772                records
1773                    .get(&pid)
1774                    .map(|record| record.ready_pending.load(Ordering::Acquire))
1775            })
1776            .unwrap_or(false)
1777    }
1778
1779    /// FIX A-ii: marks `pid` as executing a slice — not parked, so not yet
1780    /// delivery-quiescent. Reuses the registry lock the slice already takes for
1781    /// `is_registered`; it never touches the barrier condvar.
1782    pub(super) fn mark_running(&self, pid: u64) {
1783        if let Ok(records) = self.records.lock()
1784            && let Some(record) = records.get(&pid)
1785        {
1786            record.parked.store(false, Ordering::Release);
1787        }
1788    }
1789
1790    /// FIX A-ii: marks `pid` as parked with every accepted publish already fanned
1791    /// out to its socket, and — only while the shutdown flush barrier is armed —
1792    /// bumps the settle generation and wakes it. The bump/notify is skipped
1793    /// entirely in normal operation, so a park off the shutdown path is just one
1794    /// flag store.
1795    pub(super) fn mark_parked(&self, pid: u64) {
1796        if let Ok(records) = self.records.lock()
1797            && let Some(record) = records.get(&pid)
1798        {
1799            record.parked.store(true, Ordering::Release);
1800        }
1801        if self.settle_armed.load(Ordering::Acquire) {
1802            self.signal_settle_changed();
1803        }
1804    }
1805
1806    /// Bumps the delivery-quiescence generation under its mutex, then wakes the
1807    /// flush barrier — the same lock-then-notify discipline as
1808    /// [`Self::signal_connection_removed`], so a park published before the notify
1809    /// can never be missed by a waiter holding the mutex across its re-check.
1810    fn signal_settle_changed(&self) {
1811        {
1812            let mut generation = recover_lock(&self.settle_generation);
1813            *generation = generation.wrapping_add(1);
1814        }
1815        self.settle_changed.notify_all();
1816    }
1817
1818    /// Reads the current delivery-quiescence generation under its mutex.
1819    fn settle_generation_snapshot(&self) -> u64 {
1820        *recover_lock(&self.settle_generation)
1821    }
1822
1823    /// True when every tracked connection is parked with no pending READY edge —
1824    /// i.e. every accepted publish has been pumped to its subscriber's outbound
1825    /// and no fan-out wake is still in flight. An empty registry is trivially
1826    /// quiescent.
1827    fn all_connections_delivery_quiesced(&self) -> bool {
1828        let Ok(records) = self.records.lock() else {
1829            return false;
1830        };
1831        records.values().all(|record| {
1832            record.parked.load(Ordering::Acquire) && !record.ready_pending.load(Ordering::Acquire)
1833        })
1834    }
1835
1836    /// FIX A-ii: wakes every tracked connection once so it drains its socket and
1837    /// pumps its subscriptions. This is what makes the flush barrier robust to the
1838    /// readiness gap: a publisher whose fire-and-forget publish bytes have arrived
1839    /// but whose readiness wake has not yet rescheduled it still looks "parked",
1840    /// so without this it could be sampled as quiescent before it admits and fans
1841    /// out those publishes. Firing sets each connection's `ready_pending` edge, so
1842    /// the quiescence check below cannot pass until every woken connection has run
1843    /// its slice (reading and admitting any buffered publish, whose admission then
1844    /// fires its subscribers in turn) and re-parked.
1845    fn wake_all_connections_for_flush(&self) {
1846        let pids: Vec<u64> = self
1847            .records
1848            .lock()
1849            .map(|records| records.keys().copied().collect())
1850            .unwrap_or_default();
1851        for pid in pids {
1852            if let Some(waker) = self.ready_waker(pid) {
1853                waker.fire();
1854            }
1855        }
1856    }
1857
1858    /// FIX A-ii: parks until every tracked connection has fanned out its accepted
1859    /// publishes (delivery quiescence) or `deadline` elapses, returning `true` on
1860    /// quiescence and `false` when the single admitted deadline won. The TOLD
1861    /// shape mirrors [`Self::wait_for_active_connections_drained`]: arm the
1862    /// barrier, then snapshot-before-observe so a park delivered between the
1863    /// observation and the wait bumps a generation the wait detects. It samples
1864    /// nothing on a timer — it wakes only on a delivered park (generation bump) or
1865    /// the one deadline.
1866    pub(super) fn wait_for_delivery_quiesced(&self, deadline: Instant) -> bool {
1867        self.settle_armed.store(true, Ordering::Release);
1868        // Force every connection to run once so a publisher whose buffered publish
1869        // bytes have not yet triggered a readiness wake still drains and admits
1870        // them (and fires its subscribers) before the quiescence check can pass.
1871        self.wake_all_connections_for_flush();
1872        let quiesced = loop {
1873            let snapshot = self.settle_generation_snapshot();
1874            if self.all_connections_delivery_quiesced() {
1875                break true;
1876            }
1877            let Some(remaining) = deadline.checked_duration_since(Instant::now()) else {
1878                break false;
1879            };
1880            let outcome = self
1881                .settle_changed
1882                .wait_timeout_while(
1883                    recover_lock(&self.settle_generation),
1884                    remaining,
1885                    |current| *current == snapshot,
1886                )
1887                .unwrap_or_else(PoisonError::into_inner);
1888            drop(outcome);
1889        };
1890        self.settle_armed.store(false, Ordering::Release);
1891        quiesced
1892    }
1893
1894    /// R7: records one serviced slice for `pid`. Bumped at the head of every
1895    /// slice; the park-flip's quiescence assertion reads [`Self::slice_count`].
1896    #[cfg(test)]
1897    pub(super) fn record_slice(&self, pid: u64) {
1898        let count = if let Ok(mut counts) = self.slice_counts.lock() {
1899            let count = counts.entry(pid).or_insert(0);
1900            *count += 1;
1901            *count
1902        } else {
1903            return;
1904        };
1905        if let Ok(mut observers) = self.slice_observers.lock()
1906            && let Some(observer) = observers.remove(&pid)
1907        {
1908            let _ = observer.send(count);
1909        }
1910    }
1911
1912    #[cfg(test)]
1913    fn observe_next_slice(&self, pid: u64) -> Receiver<u64> {
1914        let (sender, receiver) = channel();
1915        if let Ok(mut observers) = self.slice_observers.lock() {
1916            observers.insert(pid, sender);
1917        }
1918        receiver
1919    }
1920
1921    #[cfg(test)]
1922    fn observe_next_park(&self, pid: u64) -> Receiver<u64> {
1923        let (sender, receiver) = channel();
1924        if let Ok(mut observers) = self.park_observers.lock() {
1925            observers.insert(pid, sender);
1926        }
1927        receiver
1928    }
1929
1930    #[cfg(test)]
1931    fn observe_settled_park(&self, pid: u64) -> Receiver<u64> {
1932        let (sender, receiver) = channel();
1933        let Ok(counts) = self.slice_counts.lock() else {
1934            return receiver;
1935        };
1936        let current = counts.get(&pid).copied().unwrap_or(0);
1937        let Ok(parks) = self.park_counts.lock() else {
1938            return receiver;
1939        };
1940        if current > 0 && parks.get(&pid).copied() == Some(current) {
1941            let _ = sender.send(current);
1942        } else if let Ok(mut observers) = self.park_observers.lock() {
1943            observers.insert(pid, sender);
1944        }
1945        drop(parks);
1946        drop(counts);
1947        receiver
1948    }
1949
1950    #[cfg(test)]
1951    pub(super) fn record_park(&self, pid: u64) {
1952        let count = self.slice_count(pid);
1953        if let Ok(mut parks) = self.park_counts.lock() {
1954            parks.insert(pid, count);
1955        }
1956        if let Ok(mut observers) = self.park_observers.lock()
1957            && let Some(observer) = observers.remove(&pid)
1958        {
1959            let _ = observer.send(count);
1960        }
1961    }
1962
1963    #[cfg(test)]
1964    fn queue_next_outbound_capacity(&self, capacity: usize) {
1965        if let Ok(mut capacities) = self.next_outbound_capacities.lock() {
1966            capacities.push_back(capacity);
1967        }
1968    }
1969
1970    #[cfg(test)]
1971    pub(super) fn take_next_outbound_capacity(&self) -> Option<usize> {
1972        self.next_outbound_capacities
1973            .lock()
1974            .ok()
1975            .and_then(|mut capacities| capacities.pop_front())
1976    }
1977
1978    #[cfg(test)]
1979    fn install_participant_holdback_pause(&self, pid: u64) -> Receiver<()> {
1980        let (sender, receiver) = channel();
1981        if let Ok(mut pauses) = self.participant_holdback_pauses.lock() {
1982            pauses.insert(pid, sender);
1983        }
1984        receiver
1985    }
1986
1987    #[cfg(test)]
1988    pub(super) fn pause_participant_holdback(&self, pid: u64) -> bool {
1989        self.participant_holdback_pauses
1990            .lock()
1991            .ok()
1992            .and_then(|mut pauses| pauses.remove(&pid))
1993            .is_some_and(|sender| {
1994                let _ = sender.send(());
1995                true
1996            })
1997    }
1998
1999    /// R7: slices serviced by connection `pid` since spawn (test instrument).
2000    #[cfg(test)]
2001    pub(super) fn slice_count(&self, pid: u64) -> u64 {
2002        self.slice_counts
2003            .lock()
2004            .map_or(0, |counts| counts.get(&pid).copied().unwrap_or(0))
2005    }
2006
2007    #[cfg(test)]
2008    fn install_pre_wait_barrier(&self) -> (Arc<Barrier>, Arc<Barrier>) {
2009        let armed = Arc::new(Barrier::new(2));
2010        let release = Arc::new(Barrier::new(2));
2011        if let Ok(mut slot) = self.pre_wait_barrier.lock() {
2012            *slot = Some(PreWaitBarrier {
2013                armed: Arc::clone(&armed),
2014                release: Arc::clone(&release),
2015            });
2016        }
2017        (armed, release)
2018    }
2019
2020    /// Installs the pid-specific reclamation gate (oracle 26) and returns its
2021    /// `(reached, release, done)` endpoints. The harness rendezvouses `reached`
2022    /// to pin the dead-but-tracked window, `release` to let the reclaim proceed,
2023    /// and `done` to observe the `remove()` funnel completing.
2024    #[cfg(test)]
2025    pub(super) fn install_reclaim_barrier(
2026        &self,
2027        pid: u64,
2028    ) -> (Arc<Barrier>, Arc<Barrier>, Arc<Barrier>) {
2029        let reached = Arc::new(Barrier::new(2));
2030        let release = Arc::new(Barrier::new(2));
2031        let done = Arc::new(Barrier::new(2));
2032        if let Ok(mut slot) = self.reclaim_barrier.lock() {
2033            *slot = Some(ReclaimBarrier {
2034                pid,
2035                reached: Arc::clone(&reached),
2036                release: Arc::clone(&release),
2037                done: Arc::clone(&done),
2038            });
2039        }
2040        (reached, release, done)
2041    }
2042
2043    /// Takes the installed reclamation gate iff it targets `pid` (one-use). Any
2044    /// other pid's delivery is ungated, so unrelated exits reclaim immediately.
2045    #[cfg(test)]
2046    fn stage_reclaim_barrier(&self, pid: u64) -> Option<ReclaimBarrier> {
2047        let mut slot = self.reclaim_barrier.lock().ok()?;
2048        if slot.as_ref().is_some_and(|barrier| barrier.pid == pid) {
2049            slot.take()
2050        } else {
2051            None
2052        }
2053    }
2054
2055    /// Installs the one-use drain-park gate (oracles 18, 19) and returns its
2056    /// `(armed, release)` endpoints. Staged in the drain waiter's observe->park
2057    /// window so a harness can deliver an exit strictly between the completion
2058    /// observation and the park. Entirely `#[cfg(test)]`; changes no production
2059    /// wait semantics.
2060    #[cfg(test)]
2061    pub(super) fn install_drain_park_barrier(&self) -> (Arc<Barrier>, Arc<Barrier>) {
2062        let armed = Arc::new(Barrier::new(2));
2063        let release = Arc::new(Barrier::new(2));
2064        if let Ok(mut slot) = self.drain_park_barrier.lock() {
2065            *slot = Some(PreWaitBarrier {
2066                armed: Arc::clone(&armed),
2067                release: Arc::clone(&release),
2068            });
2069        }
2070        (armed, release)
2071    }
2072
2073    /// Runs the one-use drain-park gate, if installed. One-use so only the staged
2074    /// park rendezvouses; every later park in the same waiter runs ungated.
2075    #[cfg(test)]
2076    fn run_drain_park_barrier(&self) {
2077        let barrier = self
2078            .drain_park_barrier
2079            .lock()
2080            .ok()
2081            .and_then(|mut slot| slot.take());
2082        let Some(barrier) = barrier else {
2083            return;
2084        };
2085        barrier.armed.wait();
2086        barrier.release.wait();
2087    }
2088
2089    /// Test-only count of drain waiter wakes that observed a real removal.
2090    #[cfg(test)]
2091    pub(super) fn drain_exit_wakes(&self) -> u64 {
2092        self.drain_exit_wakes.load(Ordering::SeqCst)
2093    }
2094
2095    /// Test-only count of drain waiter deadline expirations.
2096    #[cfg(test)]
2097    pub(super) fn drain_deadline_hits(&self) -> u64 {
2098        self.drain_deadline_hits.load(Ordering::SeqCst)
2099    }
2100
2101    /// Runs a one-use deterministic test gate after arm. Returns whether the gate
2102    /// was installed so only that staged probe contributes to observability.
2103    #[cfg(test)]
2104    pub(super) fn run_pre_wait_barrier(&self) -> bool {
2105        let barrier = self
2106            .pre_wait_barrier
2107            .lock()
2108            .ok()
2109            .and_then(|mut slot| slot.take());
2110        let Some(barrier) = barrier else {
2111            return false;
2112        };
2113        barrier.armed.wait();
2114        barrier.release.wait();
2115        true
2116    }
2117
2118    /// Installs a one-use barrier in `enqueue_control`'s insert->wake window
2119    /// (S8 staging: lets a test act as the control-drain consumer between the
2120    /// queue insertion and the wake attempt) and returns its test endpoints.
2121    #[cfg(test)]
2122    pub(super) fn install_pre_wake_barrier(&self) -> (Arc<Barrier>, Arc<Barrier>) {
2123        let armed = Arc::new(Barrier::new(2));
2124        let release = Arc::new(Barrier::new(2));
2125        if let Ok(mut slot) = self.pre_wake_barrier.lock() {
2126            *slot = Some(PreWaitBarrier {
2127                armed: Arc::clone(&armed),
2128                release: Arc::clone(&release),
2129            });
2130        }
2131        (armed, release)
2132    }
2133
2134    /// Runs the one-use insert->wake test gate, if installed.
2135    #[cfg(test)]
2136    pub(super) fn run_pre_wake_barrier(&self) {
2137        let barrier = self
2138            .pre_wake_barrier
2139            .lock()
2140            .ok()
2141            .and_then(|mut slot| slot.take());
2142        if let Some(barrier) = barrier {
2143            barrier.armed.wait();
2144            barrier.release.wait();
2145        }
2146    }
2147
2148    #[cfg(test)]
2149    pub(super) fn record_pre_wait_probe_hit(&self) {
2150        self.pre_wait_probe_hits.fetch_add(1, Ordering::AcqRel);
2151    }
2152
2153    #[cfg(test)]
2154    fn pre_wait_probe_hits(&self) -> u64 {
2155        self.pre_wait_probe_hits.load(Ordering::Acquire)
2156    }
2157
2158    #[cfg(test)]
2159    fn observe_process_stream_drop(&self, fd: RawFd) -> Receiver<()> {
2160        let (sender, receiver) = channel();
2161        if let Ok(mut observers) = self.process_stream_drop_observers.lock() {
2162            observers.insert(fd, sender);
2163        }
2164        receiver
2165    }
2166
2167    /// Publishes the process-owned stream's real drop boundary to a waiting test.
2168    #[cfg(test)]
2169    pub(super) fn record_process_stream_drop(&self, fd: RawFd) {
2170        let observer = self
2171            .process_stream_drop_observers
2172            .lock()
2173            .ok()
2174            .and_then(|mut observers| observers.remove(&fd));
2175        if let Some(observer) = observer {
2176            let _ = observer.send(());
2177        }
2178    }
2179
2180    /// Builds a runtime wrapping `services` for unit tests that exercise
2181    /// `apply_frame` without a live scheduler. Uses a fresh interned control atom
2182    /// and no notifier.
2183    #[cfg(test)]
2184    pub(super) fn for_tests(services: Arc<dyn ConnectionServices>) -> Self {
2185        let atoms = AtomTable::with_common_atoms();
2186        let control_atom = atoms.intern(CONNECTION_SHUTDOWN_CONTROL_ATOM);
2187        let ready_atom = atoms.intern(CONNECTION_READY_ATOM);
2188        Self::new(
2189            ConnectionRuntimeInstallation {
2190                services: ConnectionServiceInstallation::capture(services),
2191                incarnations: None,
2192                fatal_shutdown: None,
2193            },
2194            control_atom,
2195            ready_atom,
2196            Weak::new(),
2197            None,
2198            None,
2199            LimitsConfig::default(),
2200        )
2201    }
2202
2203    /// Builds a runtime wrapping `services` with explicit `limits` for unit tests
2204    /// that exercise the §5 admission caps without a live scheduler.
2205    #[cfg(test)]
2206    pub(super) fn for_tests_with_limits(
2207        services: Arc<dyn ConnectionServices>,
2208        limits: LimitsConfig,
2209    ) -> Self {
2210        let atoms = AtomTable::with_common_atoms();
2211        let control_atom = atoms.intern(CONNECTION_SHUTDOWN_CONTROL_ATOM);
2212        let ready_atom = atoms.intern(CONNECTION_READY_ATOM);
2213        Self::new(
2214            ConnectionRuntimeInstallation {
2215                services: ConnectionServiceInstallation::capture(services),
2216                incarnations: None,
2217                fatal_shutdown: None,
2218            },
2219            control_atom,
2220            ready_atom,
2221            Weak::new(),
2222            None,
2223            None,
2224            limits,
2225        )
2226    }
2227
2228    /// Builds a runtime wrapping `services` with a configured auth `token` for unit
2229    /// tests that exercise the `Connect` handshake enforcement without a live
2230    /// scheduler. Uses a fresh interned control atom and no notifier.
2231    #[cfg(test)]
2232    pub(super) fn for_tests_with_auth_token(
2233        services: Arc<dyn ConnectionServices>,
2234        token: Vec<u8>,
2235    ) -> Self {
2236        let atoms = AtomTable::with_common_atoms();
2237        let control_atom = atoms.intern(CONNECTION_SHUTDOWN_CONTROL_ATOM);
2238        let ready_atom = atoms.intern(CONNECTION_READY_ATOM);
2239        Self::new(
2240            ConnectionRuntimeInstallation {
2241                services: ConnectionServiceInstallation::capture(services),
2242                incarnations: None,
2243                fatal_shutdown: None,
2244            },
2245            control_atom,
2246            ready_atom,
2247            Weak::new(),
2248            None,
2249            Some(token),
2250            LimitsConfig::default(),
2251        )
2252    }
2253
2254    /// Builds a runtime wrapping `services` with a `notifier` for unit tests that
2255    /// exercise `apply_frame` and the close path without a live scheduler.
2256    #[cfg(test)]
2257    pub(super) fn for_tests_with_notifier(
2258        services: Arc<dyn ConnectionServices>,
2259        notifier: Arc<dyn ConnectionNotifier>,
2260    ) -> Self {
2261        let atoms = AtomTable::with_common_atoms();
2262        let control_atom = atoms.intern(CONNECTION_SHUTDOWN_CONTROL_ATOM);
2263        let ready_atom = atoms.intern(CONNECTION_READY_ATOM);
2264        Self::new(
2265            ConnectionRuntimeInstallation {
2266                services: ConnectionServiceInstallation::capture(services),
2267                incarnations: None,
2268                fatal_shutdown: None,
2269            },
2270            control_atom,
2271            ready_atom,
2272            Weak::new(),
2273            Some(notifier),
2274            None,
2275            LimitsConfig::default(),
2276        )
2277    }
2278
2279    pub(super) fn services(&self) -> &dyn ConnectionServices {
2280        self.services.as_ref()
2281    }
2282
2283    /// Returns the complete participant service captured at supervisor startup.
2284    pub(super) const fn participant_service(&self) -> Option<&InstalledParticipantService> {
2285        self.participant_service.as_ref()
2286    }
2287
2288    fn participant_service_fatal(&self) -> Result<Option<ParticipantServiceFatal>, ServerError> {
2289        let Some(service) = self.participant_service() else {
2290            return Ok(None);
2291        };
2292        service
2293            .service_fatal()
2294            .map_err(|error| ServerError::ParticipantIncarnation {
2295                phase: "participant fatal latch inspection",
2296                message: error.to_string(),
2297            })
2298    }
2299
2300    fn activate_fatal_shutdown(&self) {
2301        if let Some(shutdown) = self.fatal_shutdown.as_ref() {
2302            shutdown.initiate();
2303        }
2304    }
2305
2306    fn ensure_participant_service_live(&self) -> Result<(), ServerError> {
2307        let Some(fatal) = self.participant_service_fatal()? else {
2308            return Ok(());
2309        };
2310        self.activate_fatal_shutdown();
2311        Err(ServerError::ParticipantServiceFatal { fatal })
2312    }
2313
2314    fn latch_connection_fate_intent_incomplete(
2315        &self,
2316        open_sequence: u64,
2317        conversation_id: u64,
2318    ) -> Result<ParticipantServiceFatal, ServerError> {
2319        let Some(service) = self.participant_service() else {
2320            return Err(ServerError::ParticipantIncarnation {
2321                phase: "connection-fate fatal latch",
2322                message: "a durable Open lacks its installed participant service".to_owned(),
2323            });
2324        };
2325        let fatal = service
2326            .latch_connection_fate_intent_incomplete(open_sequence, conversation_id)
2327            .map_err(|error| ServerError::ParticipantIncarnation {
2328                phase: "connection-fate fatal latch",
2329                message: error.to_string(),
2330            })?;
2331        self.activate_fatal_shutdown();
2332        Ok(fatal)
2333    }
2334
2335    fn complete_connection_fate_fatal(
2336        &self,
2337        open_sequence: u64,
2338        conversations: &[u64],
2339        phase: &'static str,
2340        error: &impl std::fmt::Display,
2341    ) -> ServerError {
2342        tracing::error!(open_sequence, phase, %error, "durable connection-fate intent is incomplete");
2343        let Some(&conversation_id) = conversations.first() else {
2344            return ServerError::ParticipantIncarnation {
2345                phase: "connection-fate fatal target",
2346                message: "a durable Open has no tracked conversation target".to_owned(),
2347            };
2348        };
2349        match self.latch_connection_fate_intent_incomplete(open_sequence, conversation_id) {
2350            Ok(fatal) => ServerError::ParticipantServiceFatal { fatal },
2351            Err(latch_error) => latch_error,
2352        }
2353    }
2354
2355    /// Runs one typed terminal fold after classification and before teardown.
2356    pub(super) fn complete_connection_fate(
2357        &self,
2358        connection_incarnation: Option<ConnectionIncarnation>,
2359        class: ConnectionFateClass,
2360        conversations: &[u64],
2361    ) -> Result<(), ServerError> {
2362        if conversations.is_empty() {
2363            return Ok(());
2364        }
2365        self.ensure_participant_service_live()?;
2366        let (Some(connection_incarnation), Some(service), Some(authority)) = (
2367            connection_incarnation,
2368            self.participant_service(),
2369            self.incarnations.as_ref(),
2370        ) else {
2371            return Err(ServerError::ParticipantIncarnation {
2372                phase: "connection-fate authority composition",
2373                message: "tracked participant conversations lack a complete service/incarnation authority"
2374                    .to_owned(),
2375            });
2376        };
2377        let intent =
2378            authority.open_connection_fate(connection_incarnation, class, conversations)?;
2379        if let Err(error) = service.handle_connection_fate(intent.work_item()) {
2380            return Err(self.complete_connection_fate_fatal(
2381                intent.open_sequence,
2382                conversations,
2383                "handler",
2384                &error,
2385            ));
2386        }
2387        if let Err(error) = authority.complete_connection_fate(intent.open_sequence) {
2388            return Err(self.complete_connection_fate_fatal(
2389                intent.open_sequence,
2390                conversations,
2391                "Complete",
2392                &error,
2393            ));
2394        }
2395        Ok(())
2396    }
2397
2398    /// Resolves the bound-only protocol-error gate from participant authority.
2399    pub(super) fn connection_has_bound_participant(
2400        &self,
2401        connection_incarnation: Option<ConnectionIncarnation>,
2402        conversations: &[u64],
2403    ) -> Result<bool, ServerError> {
2404        if conversations.is_empty() {
2405            return Ok(false);
2406        }
2407        self.ensure_participant_service_live()?;
2408        let (Some(connection_incarnation), Some(service)) =
2409            (connection_incarnation, self.participant_service())
2410        else {
2411            return Err(ServerError::ParticipantIncarnation {
2412                phase: "bound participant classification",
2413                message:
2414                    "tracked participant conversations lack a complete service/incarnation pair"
2415                        .to_owned(),
2416            });
2417        };
2418        service
2419            .connection_has_bound_participant(connection_incarnation, conversations)
2420            .map_err(|error| ServerError::ParticipantIncarnation {
2421                phase: "bound participant classification",
2422                message: error.to_string(),
2423            })
2424    }
2425
2426    /// Returns the configured connection auth token as opaque bytes, or `None` when
2427    /// no `[auth]` section was configured (open access).
2428    pub(super) fn auth_token(&self) -> Option<&[u8]> {
2429        self.auth_token.as_deref()
2430    }
2431
2432    /// Returns the configured connection-keyed notifier, if any.
2433    pub(super) fn notifier(&self) -> Option<&Arc<dyn ConnectionNotifier>> {
2434        self.notifier.as_ref()
2435    }
2436
2437    /// Offers a channel publish to the notifier's observability-drain tap, returning
2438    /// `true` when the application consumed it (so the connection process skips the
2439    /// normal fan-out). `false` when no notifier is installed (liminal standalone) or
2440    /// the notifier did not recognise the channel, so the caller can invoke it
2441    /// unconditionally and fall through to the normal publish path.
2442    pub(super) fn notifier_channel_publish(&self, pid: u64, channel: &str, payload: &[u8]) -> bool {
2443        self.notifier
2444            .as_ref()
2445            .is_some_and(|notifier| notifier.on_channel_publish(pid, channel, payload))
2446    }
2447
2448    /// Stores `registration` on the connection record for `pid`, so the close
2449    /// path can later fire `on_worker_unregistered` for exactly the connections
2450    /// that registered. A missing record (the connection already closed) is a
2451    /// no-op.
2452    ///
2453    /// # Errors
2454    /// Returns [`ServerError`] when the connection registry mutex is poisoned.
2455    pub(super) fn set_registration(
2456        &self,
2457        pid: u64,
2458        registration: WorkerRegistration,
2459    ) -> Result<(), ServerError> {
2460        if let Some(record) = lock(&self.records, "connection registry")?.get_mut(&pid) {
2461            record.registration = Some(registration);
2462        }
2463        Ok(())
2464    }
2465
2466    /// Allocates the next monotonic push correlation id.
2467    fn next_push_correlation_id(&self) -> u64 {
2468        self.next_push_id.fetch_add(1, Ordering::Relaxed)
2469    }
2470
2471    /// Registers a one-shot reply slot for `correlation_id`, owned by connection
2472    /// `pid`, and returns its receiver. `deadline` is the slot's optional absolute
2473    /// reply expiry (`None` = the default no-deadline shape). The connection
2474    /// process resolves the slot via [`resolve_push`]; the close path drops the
2475    /// connection's outstanding slots via [`cancel_pushes_for_connection`]; an
2476    /// explicit deadline resolves it via [`expire_push_if_due`].
2477    ///
2478    /// # Errors
2479    /// Returns [`ServerError`] when the correlation registry mutex is poisoned.
2480    fn register_push(
2481        &self,
2482        pid: u64,
2483        correlation_id: u64,
2484        deadline: Option<Instant>,
2485    ) -> Result<Receiver<Vec<u8>>, ServerError> {
2486        let (sender, receiver) = channel();
2487        let limit = self.limits.max_pending_pushes_per_connection;
2488        {
2489            let mut slots = lock(&self.push_replies, "push correlation registry")?;
2490            // §5 `max_pending_pushes_per_connection`: refuse a new in-flight push
2491            // once this connection already holds the cap. Counted per owning pid so
2492            // one connection cannot exhaust the shared registry; slots free on
2493            // reply, deadline expiry, or connection close. The count-and-insert
2494            // stays under the one lock so the cap is enforced atomically.
2495            let outstanding = slots.values().filter(|pending| pending.pid == pid).count();
2496            if outstanding >= limit {
2497                return Err(ServerError::ConnectionCapReached {
2498                    operation: "server push".to_owned(),
2499                    cap: "max_pending_pushes_per_connection",
2500                    limit,
2501                });
2502            }
2503            slots.insert(
2504                correlation_id,
2505                PendingPush {
2506                    pid,
2507                    sender,
2508                    deadline,
2509                },
2510            );
2511        }
2512        Ok(receiver)
2513    }
2514
2515    /// Host-side, lazy evaluation of a push's reply deadline, called from an
2516    /// elapsed [`PushReplyAwaiter::receive`] quantum. This NEVER wakes the
2517    /// connection process and runs no timer — it inspects supervisor-owned state
2518    /// under the registry lock only.
2519    ///
2520    /// A slot with an explicit deadline that has passed is removed here (dropping
2521    /// its `Sender` and releasing its §5 `max_pending_pushes_per_connection` cap
2522    /// admission, since the cap is the per-pid slot count) and reported
2523    /// [`PushSlotDisposition::Expired`]. A slot with no deadline, or a deadline
2524    /// still in the future, is left UNTOUCHED and reported
2525    /// [`PushSlotDisposition::Live`] — the elapsed quantum is a benign re-arm. A
2526    /// missing slot is [`PushSlotDisposition::Absent`].
2527    fn expire_push_if_due(&self, correlation_id: u64) -> PushSlotDisposition {
2528        // S4: a poisoned registry must NOT read as slot absence — the slot (and
2529        // its cap admission) may still be in the map. Reclamation recovers the
2530        // guard: removal-only operations are sound on a recovered map (a panic
2531        // in another critical section cannot leave the HashMap itself in a
2532        // partial state; only our bookkeeping invariants could be stale, and
2533        // removal restores them). Admission (`register_push`) stays fail-closed.
2534        let mut slots = recover_lock(&self.push_replies);
2535        let Some(pending) = slots.get(&correlation_id) else {
2536            return PushSlotDisposition::Absent;
2537        };
2538        // Copy the deadline out so the immutable borrow of `slots` ends before the
2539        // conditional `remove` below takes a mutable one.
2540        let deadline = pending.deadline;
2541        match deadline {
2542            Some(at) if Instant::now() >= at => {
2543                slots.remove(&correlation_id);
2544                PushSlotDisposition::Expired
2545            }
2546            _ => PushSlotDisposition::Live,
2547        }
2548    }
2549
2550    /// Drops a registered reply slot without resolving it, used on the
2551    /// push-enqueue failure path (the control could not be delivered to a
2552    /// now-gone process, so the just-reserved slot is unreachable). Dropping the
2553    /// slot's `Sender` wakes a still-waiting awaiter with a disconnected error.
2554    ///
2555    /// Returns whether THIS call removed the slot. Removal under the registry
2556    /// mutex is the atomic resolved-vs-cancelled transition: `false` means
2557    /// another path won — [`resolve_push`](Self::resolve_push) already sent the
2558    /// reply (its send happens under the same lock, so the payload is already in
2559    /// the channel when this returns), or the connection's close path dropped
2560    /// the slot (sender gone, channel disconnected).
2561    pub(super) fn cancel_push(&self, correlation_id: u64) -> bool {
2562        // S4: reclamation recovers a poisoned guard — a rollback that silently
2563        // skipped its removal would strand the slot and its cap admission.
2564        recover_lock(&self.push_replies)
2565            .remove(&correlation_id)
2566            .is_some()
2567    }
2568
2569    /// Drops every reply slot owned by connection `pid`, waking each awaiter with a
2570    /// disconnected error (the dropped `Sender` disconnects the awaiter's
2571    /// `Receiver`). Called from the close path so a connection that exits with
2572    /// in-flight pushes signals worker death immediately instead of leaving each
2573    /// awaiter to block the full push-reply timeout. A slot that [`resolve_push`]
2574    /// already removed is gone, so it is untouched here; an unknown pid is a no-op.
2575    fn cancel_pushes_for_connection(&self, pid: u64) {
2576        // S4: the close sweep is the reclamation of last resort ("connection
2577        // close at the latest") — it must complete on a poisoned map too.
2578        recover_lock(&self.push_replies).retain(|_correlation_id, pending| pending.pid != pid);
2579    }
2580
2581    /// S3 second half (shape (b), check-after-insert): pre-publication
2582    /// confirmation that the connection record for `pid` still exists, run in
2583    /// the INSERT -> CONFIRM -> PUBLISH order (S7 — confirming after the
2584    /// enqueue let a close-swept-then-answered push report `Err` for a Push the
2585    /// client had received). `true` leaves the slot in place and the caller may
2586    /// publish; `false` means a concurrent close already removed the record —
2587    /// this call then removes the caller's own just-inserted slot (rolling back
2588    /// its cap admission) so nothing is stranded, and the caller returns
2589    /// WITHOUT publishing: an `Err` from the push methods guarantees no `Push`
2590    /// control was published.
2591    ///
2592    /// Why exactly one side always observes the slot: `remove` (the single
2593    /// record-removal path) removes the host record BEFORE sweeping the pid's
2594    /// push slots, and this check reads the record AFTER inserting the slot and
2595    /// BEFORE the control is published. Both records accesses are serialized by
2596    /// the `records` mutex, so either (i) this read precedes the record removal
2597    /// — then the slot insert precedes the sweep (insert < read < removal <
2598    /// sweep in the happens-before order) and the SWEEP observes and removes
2599    /// the slot: if the control was published in the meantime the awaiter reads
2600    /// the truthful disconnected outcome and a late client reply is a harmless
2601    /// no-op; or (ii) this read follows the record removal — then THIS call
2602    /// observes the absence, rolls the slot back itself, and nothing was
2603    /// published. When both observe (a sweep and a rollback can both run in
2604    /// case (ii) if the insert also preceded the sweep), removal is idempotent
2605    /// and the cap is derived from map membership, so nothing double-releases.
2606    ///
2607    /// Lock discipline: `records` and `push_replies` are NEVER held together —
2608    /// here (`records` read, released, then `push_replies` on rollback), in
2609    /// `remove` (`records` removal, released, then the sweep), and everywhere
2610    /// else in this file the two mutexes are taken strictly sequentially, so no
2611    /// lock-order inversion is possible. This adds ZERO work to the connection
2612    /// slice path: the re-check runs on the push caller's thread only.
2613    pub(super) fn confirm_push_registration(&self, pid: u64, correlation_id: u64) -> bool {
2614        if self.is_registered(pid) {
2615            return true;
2616        }
2617        self.cancel_push(correlation_id);
2618        false
2619    }
2620
2621    /// Number of reserved push reply slots outstanding. A benign wait-quantum
2622    /// timeout must NOT change this (the slot survives); an explicit-deadline
2623    /// expiry, a consumed reply, and connection close each release exactly one.
2624    #[cfg(test)]
2625    pub(super) fn pending_push_count(&self) -> usize {
2626        recover_lock(&self.push_replies).len()
2627    }
2628
2629    /// Reserved push reply slots owned by connection `pid` — the exact quantity
2630    /// the §5 `max_pending_pushes_per_connection` cap counts (test instrument).
2631    #[cfg(test)]
2632    pub(super) fn pending_push_count_for(&self, pid: u64) -> usize {
2633        recover_lock(&self.push_replies)
2634            .values()
2635            .filter(|pending| pending.pid == pid)
2636            .count()
2637    }
2638
2639    /// Resolves the reply slot for `correlation_id` with the client's reply
2640    /// payload, waking the [`PushReplyAwaiter`]. Called by the connection process
2641    /// when a correlated `PushReply` frame arrives. A missing slot — already
2642    /// resolved, expired at its explicit deadline, dropped by connection close, or
2643    /// an unknown id — is a harmless no-op: a late `PushReply` for a slot that is
2644    /// gone is discarded here, never delivered and never a panic or desync.
2645    pub(super) fn resolve_push(&self, correlation_id: u64, payload: Vec<u8>) {
2646        // S4: delivery-plus-removal recovers a poisoned guard — dropping a real
2647        // reply (and stranding its slot) because an unrelated critical section
2648        // panicked would kill reclamation and exact cap accounting.
2649        let mut slots = recover_lock(&self.push_replies);
2650        if let Some(pending) = slots.remove(&correlation_id) {
2651            // The send stays under the registry lock so removal and delivery are
2652            // one atomic step: a timed-out awaiter that observes the slot gone
2653            // (its `cancel_push` returned false) is then GUARANTEED to find the
2654            // payload already in the channel — without this ordering the awaiter
2655            // could see the removal, find the channel still empty, and report a
2656            // timeout for a reply that was about to land. The send itself never
2657            // blocks (unbounded channel), and a receiver dropped after an
2658            // abandoned wait makes it a benign discard.
2659            pending.sender.send(payload).ok();
2660        }
2661    }
2662
2663    pub(super) const fn control_atom(&self) -> Atom {
2664        self.control_atom
2665    }
2666
2667    /// Sole registration path for a connection: the spawn thread inserts the
2668    /// record synchronously, before `spawn_connection` returns the handle, so
2669    /// `is_tracked`/`active_connection_count` reflect the connection
2670    /// immediately. The connection handler never writes the registry (it only
2671    /// reads via `mark_crashed`/`finish`), so there is a single writer here and
2672    /// no register/ensure-register race.
2673    ///
2674    /// Ordering note: `spawn_native` only enqueues the process, so its first
2675    /// slice may run on another worker thread before this insert lands. If that
2676    /// first slice exits immediately (e.g. a missing-stream crash) its
2677    /// `mark_crashed`/`finish` removes nothing and this insert then leaves a
2678    /// record for an already-dead pid. W4 leg 1 retires the per-accept
2679    /// `reap_crashed` scan that used to self-heal that orphan continuously;
2680    /// instead [`Self::reconcile_register_orphan`] closes the race with a SINGLE
2681    /// point check on the registration event itself — never a loop.
2682    /// `fd_guard` is `None` for a transport that owns no descriptor. The guard
2683    /// exists to keep an fd alive until readiness deregistration has been
2684    /// acknowledged; a loopback connection registers no readiness and holds no
2685    /// descriptor, so there is nothing to guard and the slot is honestly empty
2686    /// rather than filled with a placeholder.
2687    fn register_connection(
2688        &self,
2689        pid: u64,
2690        peer_addr: Option<SocketAddr>,
2691        connection_incarnation: Option<ConnectionIncarnation>,
2692        mount: MountKind,
2693        fd_guard: Option<TcpStream>,
2694    ) -> Result<(), ServerError> {
2695        self.register_record(pid, peer_addr, connection_incarnation, mount, fd_guard)?;
2696        self.reconcile_register_orphan(pid);
2697        Ok(())
2698    }
2699
2700    /// Closes the register-orphan race (see [`Self::register_with_fd`]) with one
2701    /// point check driven by the registration event — not a periodic scan. If
2702    /// the just-registered pid is already absent from the scheduler process
2703    /// table, its first slice has run and exited, so the record this
2704    /// registration inserted is an orphan the retiring reap scan used to sweep;
2705    /// reclaim it immediately through the ordinary `remove()` funnel. A pid still
2706    /// present is live and needs nothing here: a later external termination rides
2707    /// the exit-event reactor and an ordinary exit its own final slice.
2708    fn reconcile_register_orphan(&self, pid: u64) {
2709        let Some(scheduler) = self.scheduler.upgrade() else {
2710            return;
2711        };
2712        // The process-table lookup returns a sharded guard; bind only the
2713        // presence bool so the guard is released before `reclaim_terminated`
2714        // takes the connection registry lock (no cross-lock hold).
2715        let already_exited = scheduler.process_table().get(pid).is_none();
2716        if !already_exited {
2717            return;
2718        }
2719        let reason = scheduler
2720            .peek_exit_reason(pid)
2721            .unwrap_or(ExitReason::Normal);
2722        self.reclaim_terminated(pid, reason);
2723    }
2724
2725    /// TOLD reclamation of a connection whose process exited WITHOUT running a
2726    /// final handler slice — external/panic termination, where
2727    /// [`ConnectionProcess::Drop`] runs but no `mark_crashed`/`finish` does, and
2728    /// the register-orphan race above. Delivered the instant beamr publishes the
2729    /// process's [`ExitEvent`] (via [`run_reclaim_reactor`]) or at the
2730    /// registration point check, and routed through the SAME [`Self::remove`]
2731    /// funnel as every other teardown — no third funnel, no periodic scan.
2732    /// Idempotent: a record already removed in-slice, by the orphan reconcile, or
2733    /// by a duplicate delivery is a no-op here (remove returns `None`), so the §5
2734    /// admission gauge is released exactly once.
2735    fn reclaim_terminated(&self, pid: u64, reason: ExitReason) {
2736        let Some(record) = self.remove(pid) else {
2737            return;
2738        };
2739        self.fire_unregistered(pid, &record);
2740        tracing::warn!(
2741            connection_pid = pid,
2742            peer_addr = ?record.peer_addr,
2743            mount = ?record.mount,
2744            reason = ?reason,
2745            "connection process exited without a final slice; host record reclaimed by delivery"
2746        );
2747    }
2748
2749    /// One exit-event delivery: drain beamr's retained outcome (sole drainer,
2750    /// bounding its store) then reclaim through [`Self::reclaim_terminated`]. The
2751    /// only non-production element is the `#[cfg(test)]` reclamation gate, which
2752    /// is staged for at most one targeted pid and compiled out entirely in
2753    /// production — the delivery semantics are identical with or without it.
2754    fn deliver_reclamation(&self, scheduler: &Weak<Scheduler>, pid: u64, reason: ExitReason) {
2755        if let Some(scheduler) = scheduler.upgrade() {
2756            // The reason is already in-hand from the event; the drained outcome
2757            // is discarded, its purpose being only to bound beamr's store.
2758            drop(scheduler.take_exit_outcome(pid));
2759        }
2760        #[cfg(test)]
2761        let staged = self.stage_reclaim_barrier(pid);
2762        #[cfg(test)]
2763        if let Some(barrier) = staged.as_ref() {
2764            // Rendezvous: the pid is now dead but its record is still tracked —
2765            // the S8 reclamation window (oracle 26). Then wait for the harness to
2766            // release the reclaim.
2767            barrier.reached.wait();
2768            barrier.release.wait();
2769        }
2770        self.reclaim_terminated(pid, reason);
2771        #[cfg(test)]
2772        if let Some(barrier) = staged.as_ref() {
2773            // Signal the funnel completed so the harness can observe the record
2774            // gone without sampling.
2775            barrier.done.wait();
2776        }
2777    }
2778
2779    #[cfg(test)]
2780    fn register(&self, pid: u64, peer_addr: Option<SocketAddr>) -> Result<(), ServerError> {
2781        self.register_record(pid, peer_addr, None, MountKind::Tcp, None)
2782    }
2783
2784    fn register_record(
2785        &self,
2786        pid: u64,
2787        peer_addr: Option<SocketAddr>,
2788        connection_incarnation: Option<ConnectionIncarnation>,
2789        mount: MountKind,
2790        fd_guard: Option<TcpStream>,
2791    ) -> Result<(), ServerError> {
2792        match lock(&self.records, "connection registry")?.entry(pid) {
2793            // ENFORCED (not comment-only): pids are fresh per spawn and a record
2794            // is reclaimed through the single `remove` funnel before its pid can
2795            // recycle, so an occupied slot here is a supervision defect. Refuse
2796            // the whole registration rather than silently replacing — a replace
2797            // would drop the displaced record's `fd_guard` outside the teardown
2798            // funnel (orphaning a live connection's stream) and increment the
2799            // `liminal_connections_active` gauge a second time with no paired
2800            // decrement. The prior record stays intact; both spawn paths roll the
2801            // fresh process back on this error. Same idiom as
2802            // `StreamTable::insert` (entry-vacant → typed error) and the
2803            // fail-closed duplicate-conversation refusal in `apply.rs`.
2804            Entry::Occupied(_) => return Err(ServerError::ConnectionPidCollision { pid }),
2805            Entry::Vacant(entry) => {
2806                entry.insert(ConnectionRecord {
2807                    peer_addr,
2808                    mount,
2809                    connection_incarnation,
2810                    registration: None,
2811                    readiness: None,
2812                    ready_pending: Arc::new(AtomicBool::new(false)),
2813                    parked: AtomicBool::new(false),
2814                    fd_guard,
2815                });
2816            }
2817        }
2818        // Single-writer vacant-only insert (see doc above and the refusal arm)
2819        // pairs EXACTLY one gauge increment with the decrement in `remove`,
2820        // keeping `liminal_connections_active` equal to the live record count on
2821        // every teardown route; a refused duplicate increments nothing.
2822        crate::metrics::connection_spawned();
2823        Ok(())
2824    }
2825
2826    pub(super) fn mark_crashed(&self, pid: u64, reason: ExitReason, peer_addr: Option<SocketAddr>) {
2827        let removed = self.remove(pid);
2828        if let Some(record) = removed.as_ref() {
2829            self.fire_unregistered(pid, record);
2830        }
2831        let removed_peer_addr = removed
2832            .as_ref()
2833            .and_then(|record| record.peer_addr)
2834            .or(peer_addr);
2835        // The mount names the door this connection came through — the one fact
2836        // that tells a reader whether a crashed connection was a socket peer or
2837        // a co-resident caller, which `peer_addr: None` alone cannot.
2838        let mount = removed.as_ref().map(|record| record.mount);
2839        tracing::warn!(
2840            connection_pid = pid,
2841            peer_addr = ?removed_peer_addr,
2842            mount = ?mount,
2843            reason = ?reason,
2844            "connection process crashed"
2845        );
2846    }
2847
2848    /// Whether the spawn thread has installed the host record. A first native
2849    /// slice can win the enqueue-vs-record race and must remain runnable until it
2850    /// has somewhere host-reachable to publish its readiness token.
2851    pub(super) fn is_registered(&self, pid: u64) -> bool {
2852        self.contains(pid)
2853    }
2854
2855    /// Removes a token minted in-slice when publishing it to the host record fails.
2856    pub(super) fn deregister_unpublished_readiness(&self, token: ReadinessToken) {
2857        if let Some(scheduler) = self.scheduler.upgrade() {
2858            scheduler.readiness_deregister(token);
2859        }
2860    }
2861
2862    /// Cancels deadline timers detached by reply completion or connection close.
2863    pub(super) fn cancel_deadline_timers(&self, timers: Vec<TimerRef>) {
2864        let Some(scheduler) = self.scheduler.upgrade() else {
2865            return;
2866        };
2867        if let Ok(mut wheel) = scheduler.timers().lock() {
2868            for timer in timers {
2869                wheel.cancel(timer);
2870            }
2871        }
2872    }
2873
2874    pub(super) fn finish(&self, pid: u64) {
2875        if let Some(removed) = self.remove(pid) {
2876            self.fire_unregistered(pid, &removed);
2877        }
2878    }
2879
2880    /// Records the one readiness token minted for this connection. A live
2881    /// connection never re-registers: later parked slices rearm this identity.
2882    pub(super) fn set_readiness_token_once(
2883        &self,
2884        pid: u64,
2885        token: ReadinessToken,
2886        fd: RawFd,
2887    ) -> Result<(), ServerError> {
2888        let mut records = lock(&self.records, "connection registry")?;
2889        let record = records
2890            .get_mut(&pid)
2891            .ok_or_else(|| ServerError::ListenerAccept {
2892                message: format!("connection {pid} has no host record for readiness registration"),
2893            })?;
2894        if record.readiness.is_some() {
2895            return Err(ServerError::ListenerAccept {
2896                message: format!("connection {pid} attempted to replace its readiness token"),
2897            });
2898        }
2899        record.readiness = Some(ReadinessRegistration { token, fd });
2900        drop(records);
2901        Ok(())
2902    }
2903
2904    /// Invokes `on_worker_unregistered` for a removed connection record that
2905    /// carried a worker registration. A record with no registration (a plain
2906    /// connection, or a worker connection that never registered) is a no-op, so
2907    /// only connections that actually registered deregister.
2908    fn fire_unregistered(&self, pid: u64, record: &ConnectionRecord) {
2909        if record.registration.is_some() {
2910            if let Some(notifier) = self.notifier.as_ref() {
2911                notifier.on_worker_unregistered(pid);
2912            }
2913        }
2914    }
2915
2916    fn reap_crashed(&self, scheduler: &Scheduler) -> usize {
2917        let pids = match self.records.lock() {
2918            Ok(records) => records.keys().copied().collect::<Vec<_>>(),
2919            Err(error) => {
2920                tracing::warn!(%error, "connection registry unavailable during crash reap");
2921                return 0;
2922            }
2923        };
2924        let mut reaped = 0;
2925        for pid in pids {
2926            if scheduler.process_table().get(pid).is_none() {
2927                let removed = self.remove(pid);
2928                if let Some(record) = removed.as_ref() {
2929                    self.fire_unregistered(pid, record);
2930                }
2931                let peer_addr = removed.and_then(|record| record.peer_addr);
2932                // This process exited without ever reaching `mark_crashed`/`finish`
2933                // (e.g. the beamr scheduler terminated it externally). W4 leg 1
2934                // retired this scan from the per-accept listener loop, and W4 leg 3
2935                // retired the shutdown-drain reconciliation that also drove it: the
2936                // reclaimer of these exits is now the TOLD exit-event reactor
2937                // ([`run_reclaim_reactor`]), which also composes drain completion
2938                // through the one `remove()` funnel. This scan now survives only as
2939                // that reactor's exit-event overflow (`Lagged`) recovery, driven a
2940                // bounded number of times, never periodically. beamr 0.15.4 exposes
2941                // a public, non-blocking
2942                // `peek_exit_reason` (and `take_exit_outcome`), so the real reason
2943                // IS recoverable here rather than logged as an opaque literal.
2944                let reason = scheduler.peek_exit_reason(pid);
2945                tracing::warn!(
2946                    connection_pid = pid,
2947                    ?peer_addr,
2948                    ?reason,
2949                    "connection process exited without a final slice; reclaimed by reconciliation"
2950                );
2951                reaped += 1;
2952            }
2953        }
2954        reaped
2955    }
2956
2957    fn contains(&self, pid: u64) -> bool {
2958        self.records
2959            .lock()
2960            .is_ok_and(|records| records.contains_key(&pid))
2961    }
2962
2963    #[cfg(test)]
2964    fn readiness_registration_count(&self) -> usize {
2965        self.records.lock().map_or(0, |records| {
2966            records
2967                .values()
2968                .filter(|record| record.readiness.is_some())
2969                .count()
2970        })
2971    }
2972
2973    #[cfg(test)]
2974    fn readiness_fd(&self, pid: u64) -> Option<RawFd> {
2975        self.records
2976            .lock()
2977            .ok()?
2978            .get(&pid)
2979            .and_then(|record| record.readiness.map(|registration| registration.fd))
2980    }
2981
2982    #[cfg(test)]
2983    fn readiness_token(&self, pid: u64) -> Option<ReadinessToken> {
2984        self.records
2985            .lock()
2986            .ok()?
2987            .get(&pid)
2988            .and_then(|record| record.readiness.map(|registration| registration.token))
2989    }
2990
2991    fn active_connections(&self) -> Vec<ActiveConnection> {
2992        self.records.lock().map_or_else(
2993            |_| Vec::new(),
2994            |records| {
2995                records
2996                    .iter()
2997                    .map(|(&pid, record)| ActiveConnection {
2998                        pid,
2999                        peer_addr: record.peer_addr,
3000                    })
3001                    .collect()
3002            },
3003        )
3004    }
3005
3006    /// Reads the complete active-connection incarnation set under the
3007    /// registry lock — the bounded defense-in-depth collision-skip input to
3008    /// incarnation allocation (uniqueness itself comes from allocator-log
3009    /// monotonicity; see `allocate_connection_incarnation`). Poisoning fails
3010    /// admission closed: treating an unreadable registry as empty would
3011    /// silently drop the defense layer.
3012    fn complete_active_incarnation_references(
3013        &self,
3014    ) -> Result<Vec<ConnectionIncarnation>, ServerError> {
3015        Ok(
3016            lock(&self.records, "connection incarnation reference registry")?
3017                .values()
3018                .filter_map(|record| record.connection_incarnation)
3019                .collect(),
3020        )
3021    }
3022
3023    fn push_control(&self, pid: u64, control: ConnectionControl) -> Result<(), ServerError> {
3024        lock(&self.controls, "connection control queue")?
3025            .push(QueuedConnectionControl { pid, control });
3026        Ok(())
3027    }
3028
3029    pub(super) fn pop_control(&self, pid: u64) -> Option<ConnectionControl> {
3030        let mut controls = self.controls.lock().ok()?;
3031        let index = controls.iter().position(|queued| queued.pid == pid)?;
3032        Some(controls.remove(index).control)
3033    }
3034
3035    /// Non-consuming final-probe query for controls enqueued after mailbox drain.
3036    pub(super) fn has_control(&self, pid: u64) -> bool {
3037        self.controls
3038            .lock()
3039            .is_ok_and(|controls| controls.iter().any(|queued| queued.pid == pid))
3040    }
3041
3042    /// Pulls a queued-but-unconsumed control back out of the queue. Returns
3043    /// whether THIS call removed it — `false` means the entry already left the
3044    /// queue, and since [`Self::pop_control`] is the only other remover, a
3045    /// consumer drain took it (S8's publication disambiguator). Matching is
3046    /// `pid` + full control equality; a `Push` control embeds its
3047    /// runtime-unique correlation id, so this can never remove a different
3048    /// push's entry and misreport.
3049    fn remove_control(&self, pid: u64, control: &ConnectionControl) -> bool {
3050        let Ok(mut controls) = self.controls.lock() else {
3051            return false;
3052        };
3053        let Some(index) = controls
3054            .iter()
3055            .position(|queued| queued.pid == pid && &queued.control == control)
3056        else {
3057            return false;
3058        };
3059        controls.remove(index);
3060        true
3061    }
3062
3063    fn active_count(&self) -> usize {
3064        self.records.lock().map_or(0, |records| records.len())
3065    }
3066
3067    /// Test read of the record's stamped mount (design §10).
3068    #[cfg(test)]
3069    fn connection_mount(&self, pid: u64) -> Option<MountKind> {
3070        self.records
3071            .lock()
3072            .ok()?
3073            .get(&pid)
3074            .map(|record| record.mount)
3075    }
3076
3077    /// Test read of whether the record holds an fd guard.
3078    #[cfg(test)]
3079    fn connection_has_fd_guard(&self, pid: u64) -> Option<bool> {
3080        self.records
3081            .lock()
3082            .ok()?
3083            .get(&pid)
3084            .map(|record| record.fd_guard.is_some())
3085    }
3086
3087    /// Removes the connection record for `pid` and, in the same close step, drops
3088    /// every push reply slot that connection still owns so each waiting
3089    /// [`PushReplyAwaiter`] wakes immediately with a disconnected error. This runs
3090    /// on every close route — `finish`, `mark_crashed`, and `reap_crashed` all
3091    /// remove through here — and fires regardless of whether the connection ever
3092    /// registered a worker, so a plain push target is covered too.
3093    ///
3094    /// ORDER MATTERS (S3/S7): the record is removed BEFORE the push sweep. A
3095    /// push registering concurrently runs INSERT -> CONFIRM -> PUBLISH
3096    /// (`confirm_push_registration` reads the record after inserting its slot
3097    /// and before publishing its control), so with this ordering exactly one
3098    /// side always observes a racing slot: a confirm that ran before this
3099    /// removal implies the slot was inserted before the sweep below (which then
3100    /// reaps it — a control published after that confirm is answered into a
3101    /// swept slot, read as the truthful disconnected outcome); a confirm after
3102    /// this removal sees the absence, rolls the slot back itself, and never
3103    /// publishes. Sweeping first (the original order) left a window — sweep,
3104    /// then insert+confirm, then record removal — where NEITHER side observed
3105    /// the slot and it leaked past connection close. The two locks are taken
3106    /// strictly sequentially (never nested), so no lock-order inversion.
3107    fn remove(&self, pid: u64) -> Option<ConnectionRecord> {
3108        let mut removed = self
3109            .records
3110            .lock()
3111            .ok()
3112            .and_then(|mut records| records.remove(&pid));
3113        self.cancel_pushes_for_connection(pid);
3114        if let Some(registration) = removed.as_mut().and_then(|record| record.readiness.take()) {
3115            if let Some(scheduler) = self.scheduler.upgrade() {
3116                // This call is ACK'd: it returns only after the poll owner has
3117                // removed the registration. `fd_guard` is still live here.
3118                scheduler.readiness_deregister(registration.token);
3119                tracing::debug!(
3120                    registered_fd = registration.fd,
3121                    "connection readiness deregistration acknowledged"
3122                );
3123            }
3124        }
3125        // Decrement only when a record was actually present so a double-remove
3126        // (e.g. `finish` after `reap_crashed`) cannot drive the gauge negative.
3127        // The §5 admission slot is released on the same guard: the reservation
3128        // acquired in `spawn_connection` converted into this record at
3129        // `register`, so its removal is exactly one release per reservation.
3130        if removed.is_some() {
3131            crate::metrics::connection_closed();
3132            self.release_admission();
3133        }
3134        if let Some(record) = removed.as_mut() {
3135            // Explicit after-deregister drop documents and enforces the fd wall.
3136            drop(record.fd_guard.take());
3137        }
3138        // TOLD drain-completion tell (W4 leg 3, §4.3): the record map above already
3139        // reflects this removal, so bump the removal generation and wake the
3140        // drain/settle waiter AFTER the observed state is updated. Ordering the
3141        // state update before the generation bump — paired with the waiter arming
3142        // its snapshot before observing `active_count` — is the arm-before-observe
3143        // barrier that makes a concurrently delivered exit un-losable (oracle 18).
3144        // Only a real removal tells, so a double-remove drives no spurious wake.
3145        if removed.is_some() {
3146            self.signal_connection_removed();
3147        }
3148        removed
3149    }
3150
3151    /// Bumps the drain-completion generation and wakes the drain/settle waiter.
3152    /// Called from [`Self::remove`] on every route that actually drops a record.
3153    fn signal_connection_removed(&self) {
3154        // Bump the generation under the lock, release it, THEN notify. A waiter
3155        // holds this lock continuously from its generation re-check through the
3156        // atomic release inside `wait_timeout`, so it can never miss a bump
3157        // published before the notify — the Condvar lost-wakeup contract holds
3158        // without notifying under the guard.
3159        {
3160            let mut generation = recover_lock(&self.drain_generation);
3161            *generation = generation.wrapping_add(1);
3162        }
3163        self.drain_removed.notify_all();
3164    }
3165
3166    /// Parks the calling thread until every tracked connection has been removed
3167    /// or `deadline` elapses, returning `true` when the drain completed and
3168    /// `false` when the single admitted deadline won. This is the TOLD
3169    /// replacement (W4 leg 3, §4.3) for the retired reap/count/sleep drain loop:
3170    /// it never samples completion on a timer. Completion is observed only on a
3171    /// delivered connection-removal wake (composed from the one `remove()` funnel,
3172    /// so a Died/Detached/crash exit and an orderly close decrement through the
3173    /// same path — oracle 15) or the one deadline. Force-close settle reuses this
3174    /// same waiter with its own deadline rather than a second poll loop (oracle
3175    /// 14).
3176    pub(super) fn wait_for_active_connections_drained(&self, deadline: Instant) -> bool {
3177        loop {
3178            // ARM before OBSERVE: snapshot the removal generation first, so an exit
3179            // delivered after the completion observation below and before the park
3180            // bumps a generation the park detects — it is never lost (oracle 18).
3181            let snapshot = self.drain_generation_snapshot();
3182            // OBSERVE completion first: a last exit that reaches zero wins a tie
3183            // with a simultaneously elapsed deadline (oracle 19).
3184            if self.active_count() == 0 {
3185                return true;
3186            }
3187            let Some(remaining) = deadline.checked_duration_since(Instant::now()) else {
3188                #[cfg(test)]
3189                self.drain_deadline_hits.fetch_add(1, Ordering::SeqCst);
3190                return false;
3191            };
3192            #[cfg(test)]
3193            self.run_drain_park_barrier();
3194            self.park_until_removed_or(snapshot, remaining);
3195        }
3196    }
3197
3198    /// Reads the current removal generation under its mutex.
3199    fn drain_generation_snapshot(&self) -> u64 {
3200        *recover_lock(&self.drain_generation)
3201    }
3202
3203    /// Parks on the removal `Condvar` for at most `timeout`, but only while no
3204    /// removal has bumped the generation since `snapshot` — the arm-before-observe
3205    /// barrier. `wait_timeout_while` evaluates the predicate under the lock BEFORE
3206    /// waiting, so a generation already advanced (an exit landed between the
3207    /// observation and here) returns immediately without sleeping; spurious wakes
3208    /// re-wait inside the call and never return early.
3209    fn park_until_removed_or(&self, snapshot: u64, timeout: Duration) {
3210        let outcome = self
3211            .drain_removed
3212            .wait_timeout_while(recover_lock(&self.drain_generation), timeout, |current| {
3213                *current == snapshot
3214            })
3215            .unwrap_or_else(PoisonError::into_inner);
3216        // A non-timed-out return means the predicate went false — a real removal
3217        // bumped the generation and woke this park (as opposed to the deadline).
3218        #[cfg(test)]
3219        if !outcome.1.timed_out() {
3220            self.drain_exit_wakes.fetch_add(1, Ordering::SeqCst);
3221        }
3222        // Release the guard promptly; the caller re-loops unlocked.
3223        drop(outcome);
3224    }
3225}
3226
3227/// One in-flight server-push reply slot, associating the awaiter's reply `sender`
3228/// with the `pid` of the connection that owns the push. The pid lets the close
3229/// path drop exactly that connection's slots; the correlation id (the map key)
3230/// still drives [`ConnectionRuntime::resolve_push`] and
3231/// [`ConnectionRuntime::cancel_push`].
3232#[derive(Debug)]
3233struct PendingPush {
3234    pid: u64,
3235    sender: Sender<Vec<u8>>,
3236    /// Absolute reply deadline for this push, when one was requested via
3237    /// [`ConnectionSupervisor::push_to_connection_with_deadline`]. `None` is the
3238    /// default 0.2.3 shape: the slot has no per-slot deadline and is reclaimed
3239    /// only by reply-consumed or connection-close. `Some` is evaluated host-side
3240    /// and lazily in [`ConnectionRuntime::expire_push_if_due`].
3241    deadline: Option<Instant>,
3242}
3243
3244/// Host-side disposition of a reply slot at an elapsed `receive` quantum.
3245enum PushSlotDisposition {
3246    /// The slot carried an explicit deadline that has passed; this call removed
3247    /// it (releasing its §5 cap admission).
3248    Expired,
3249    /// The slot is present with no deadline, or a deadline still in the future:
3250    /// the elapsed quantum is a benign re-arm and the slot is untouched.
3251    Live,
3252    /// No slot for this correlation id — a concurrent resolve or connection close
3253    /// already removed it.
3254    Absent,
3255}
3256
3257#[derive(Debug)]
3258struct ConnectionRecord {
3259    peer_addr: Option<SocketAddr>,
3260    /// Which door admitted this connection (design §10). Written once, by the
3261    /// spawn path, from the server's own knowledge of which path it is; never
3262    /// read from, or influenced by, anything the client sends.
3263    mount: MountKind,
3264    /// Durable pair allocated and flushed before the process was spawned.
3265    connection_incarnation: Option<ConnectionIncarnation>,
3266    /// Worker registration declared on this connection, set by `set_registration`
3267    /// when a `WorkerRegister` frame is accepted. `Some` marks a connection whose
3268    /// close must fire `on_worker_unregistered`.
3269    registration: Option<WorkerRegistration>,
3270    /// Host-reachable identity for ACK'd deregistration after external death.
3271    readiness: Option<ReadinessRegistration>,
3272    /// Shared edge set before READY enters beamr's process-table pending queue;
3273    /// the executing process reads it at its final probe.
3274    ready_pending: Arc<AtomicBool>,
3275    /// FIX A-ii: `true` while this connection is parked (its last slice returned
3276    /// `Wait`). A parked connection with no `ready_pending` edge has fanned out
3277    /// every accepted publish to its socket, so the shutdown flush barrier reads
3278    /// this with `ready_pending` to know delivery has quiesced before it lets the
3279    /// shutdown Disconnect be broadcast.
3280    parked: AtomicBool,
3281    /// Keeps the fd alive until deregistration has been acknowledged, preventing
3282    /// stale registration delivery to a subsequently reused descriptor number.
3283    fd_guard: Option<TcpStream>,
3284}
3285
3286#[derive(Debug, Clone, Copy)]
3287struct ReadinessRegistration {
3288    token: ReadinessToken,
3289    fd: RawFd,
3290}
3291
3292#[derive(Debug, Clone, PartialEq, Eq)]
3293struct QueuedConnectionControl {
3294    pid: u64,
3295    control: ConnectionControl,
3296}
3297
3298fn lock<'a, T>(mutex: &'a Mutex<T>, context: &str) -> Result<MutexGuard<'a, T>, ServerError> {
3299    mutex.lock().map_err(|error| ServerError::ListenerAccept {
3300        message: format!("{context} unavailable: {error}"),
3301    })
3302}
3303
3304/// Locks `mutex`, RECOVERING a poisoned guard instead of failing (S4). For
3305/// lifecycle-cleanup paths only (reply delivery, expiry, cancellation, the
3306/// close sweep): removal-style operations are sound on a recovered map, and a
3307/// cleanup that silently skipped its removal would strand slots and their §5
3308/// cap admissions forever. Admission paths keep the fail-closed [`lock`].
3309fn recover_lock<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
3310    mutex
3311        .lock()
3312        .unwrap_or_else(std::sync::PoisonError::into_inner)
3313}