Skip to main content

liminal_server/server/connection/
supervisor.rs

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