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