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