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