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