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