Skip to main content

sozu_lib/
server.rs

1//! event loop management
2use std::{
3    cell::RefCell,
4    collections::{HashMap, HashSet, VecDeque, hash_map::Entry},
5    hash::{DefaultHasher, Hash, Hasher},
6    io::Error as IoError,
7    net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
8    os::unix::io::{AsRawFd, FromRawFd},
9    rc::Rc,
10    str::FromStr,
11    sync::LazyLock,
12    time::{Duration, Instant},
13};
14
15use mio::{
16    Events, Interest, Poll, Token,
17    net::{TcpListener as MioTcpListener, TcpStream, UdpSocket as MioUdpSocket},
18};
19use slab::Slab;
20use sozu_command::{
21    channel::Channel,
22    config::MetricDetailLevel,
23    logging,
24    proto::command::{
25        ActivateListener, AddBackend, CertificatesWithFingerprints, Cluster, ClusterHashes,
26        ClusterInformations, DeactivateListener, Event, EventKind, HttpListenerConfig,
27        HttpsListenerConfig, InitialState, ListenerType, LoadBalancingAlgorithms, LoadMetric,
28        MetricDetail, MetricsConfiguration, RemoveBackend, Request, ResponseContent,
29        ResponseStatus, ServerConfig, TcpListenerConfig as CommandTcpListener,
30        UdpListenerConfig as CommandUdpListener, UpdateHttpListenerConfig,
31        UpdateHttpsListenerConfig, UpdateTcpListenerConfig, UpdateUdpListenerConfig, WorkerRequest,
32        WorkerResponse, request::RequestType, response_content::ContentType,
33    },
34    ready::Ready,
35    scm_socket::{Listeners, ScmSocket, ScmSocketError},
36    state::ConfigState,
37};
38
39use crate::metrics::names;
40use crate::{
41    AcceptError, Protocol, ProxyConfiguration, ProxySession, SessionIsToBeClosed,
42    backends::{Backend, BackendMap},
43    features::FEATURES,
44    health_check::HealthChecker,
45    http, https,
46    metrics::METRICS,
47    pool::Pool,
48    tcp,
49    timer::Timer,
50    udp,
51};
52
53// Number of retries to perform on a server after a connection failure
54pub const CONN_RETRIES: u8 = 3;
55
56/// Number of bounded buckets for the per-source connect-rate counter.
57///
58/// `incr!` requires a `&'static str`, so per-IP labelling would either need
59/// runtime `Box::leak` per unique source (unbounded under SYN flood — direct
60/// OWASP A05 / NIST SP 800-92 cardinality-blow-up risk) or a fixed bucket
61/// table. We pick the bucket table: 256 static labels precomputed at startup,
62/// each masked subnet hashes into one of them.
63///
64/// Bucket-noise vs per-IP fidelity is a deliberate trade. Operators wanting
65/// per-IP attribution should pair these counters with structured access logs
66/// or a downstream rate-limiter; the metric here is for "is some /24 spamming
67/// us right now?", not "which IP exactly". 256 buckets keep the memory + UDP
68/// statsd cost flat regardless of attacker effort.
69pub const PER_SOURCE_BUCKETS: usize = 256;
70
71/// Pre-leaked `&'static str` table for per-source bucket counters.
72/// `incr!` requires `&'static str`; we leak once at first access (LazyLock)
73/// for `PER_SOURCE_BUCKETS` keys, totalling ~10 KB heap. The leak is bounded
74/// by `PER_SOURCE_BUCKETS` and never grows with traffic.
75static PER_SOURCE_BUCKET_KEYS: LazyLock<[&'static str; PER_SOURCE_BUCKETS]> = LazyLock::new(|| {
76    let mut keys: [&'static str; PER_SOURCE_BUCKETS] = [""; PER_SOURCE_BUCKETS];
77    for (i, slot) in keys.iter_mut().enumerate() {
78        // e.g. "client.connect.per_source.bucket_042"
79        let owned = format!("client.connect.per_source.bucket_{i:03}");
80        *slot = Box::leak(owned.into_boxed_str());
81    }
82    keys
83});
84
85/// Mask an IP address to its bounded prefix (/24 for IPv4, /48 for IPv6) and
86/// hash it into one of `PER_SOURCE_BUCKETS` slots. The hash is `DefaultHasher`,
87/// which is deterministic within a process but salted across runs — fine for
88/// telemetry, not suitable for cross-host correlation.
89fn per_source_bucket(peer: &SocketAddr) -> &'static str {
90    let mut hasher = DefaultHasher::new();
91    match peer.ip() {
92        IpAddr::V4(v4) => {
93            let octets = v4.octets();
94            // /24 mask: keep first three octets, zero the host portion.
95            let masked = Ipv4Addr::new(octets[0], octets[1], octets[2], 0);
96            masked.hash(&mut hasher);
97        }
98        IpAddr::V6(v6) => {
99            let octets = v6.octets();
100            // /48 mask: keep first 6 bytes, zero the rest.
101            let mut masked_octets = [0u8; 16];
102            masked_octets[..6].copy_from_slice(&octets[..6]);
103            Ipv6Addr::from(masked_octets).hash(&mut hasher);
104        }
105    }
106    let idx = (hasher.finish() as usize) % PER_SOURCE_BUCKETS;
107    PER_SOURCE_BUCKET_KEYS[idx]
108}
109
110/// Period between two `accept_queue.saturated_seconds` ticks. The counter is
111/// incremented once per period while [`SessionManager::can_accept`] is `false`,
112/// distinguishing "queue spent N seconds at max" from "queue briefly hit max"
113/// — the binary `accept_queue.backpressure` gauge collapses that duration.
114const ACCEPT_SATURATION_TICK: Duration = Duration::from_secs(1);
115
116pub type ProxyChannel = Channel<WorkerResponse, WorkerRequest>;
117
118thread_local! {
119  pub static QUEUE: RefCell<VecDeque<WorkerResponse>> = const { RefCell::new(VecDeque::new()) };
120}
121
122thread_local! {
123  pub static TIMER: RefCell<Timer<Token>> = RefCell::new(Timer::default());
124}
125
126pub fn push_queue(message: WorkerResponse) {
127    QUEUE.with(|queue| {
128        (*queue.borrow_mut()).push_back(message);
129    });
130}
131
132pub fn push_event(event: Event) {
133    QUEUE.with(|queue| {
134        (*queue.borrow_mut()).push_back(WorkerResponse {
135            id: "EVENT".to_string(),
136            message: String::new(),
137            status: ResponseStatus::Processing.into(),
138            content: Some(ContentType::Event(event).into()),
139        });
140    });
141}
142
143/// Build the `WorkerMetricDetailStatus` content payload returned in
144/// every successful `SetMetricDetail` worker response. The master
145/// collects these across the fan-out and assembles them into
146/// `MetricDetailStatus.workers[<worker_id>]` so the TUI sees each
147/// worker's actual aggregator state instead of the master's view.
148fn worker_metric_detail_status_content(
149    configured: MetricDetailLevel,
150    effective: MetricDetailLevel,
151    previous_effective: MetricDetailLevel,
152    active_lease_count: u32,
153) -> ResponseContent {
154    use sozu_command::proto::command::WorkerMetricDetailStatus;
155    ContentType::WorkerMetricDetailStatus(WorkerMetricDetailStatus {
156        configured: MetricDetail::from(configured) as i32,
157        effective: MetricDetail::from(effective) as i32,
158        previous_effective: MetricDetail::from(previous_effective) as i32,
159        active_lease_count,
160    })
161    .into()
162}
163
164/// Build a `METRIC_DETAIL_CHANGED` event carrying the worker-local
165/// transition payload (previous/effective levels + transition kind).
166/// `client_id` is `Some(_)` for explicit apply/clear, `None` for the
167/// polled janitor's bulk expiry. The master folds this Event into the
168/// audit log alongside operator-initiated transitions emitted at the
169/// dispatch site in `bin/src/command/requests.rs::worker_request`.
170fn push_metric_detail_transition(
171    previous: MetricDetailLevel,
172    effective: MetricDetailLevel,
173    transition_kind: &'static str,
174    client_id: Option<String>,
175) {
176    use sozu_command::proto::command::MetricDetailTransition;
177    // No-op when nothing actually changed. Defence-in-depth — every
178    // caller already gates on `previous != effective`, but
179    // double-checking here means future call sites can't accidentally
180    // emit a "ghost" transition.
181    if previous == effective {
182        return;
183    }
184    push_event(Event {
185        kind: EventKind::MetricDetailChanged as i32,
186        cluster_id: None,
187        backend_id: None,
188        address: None,
189        metric_detail: Some(MetricDetailTransition {
190            previous_effective: MetricDetail::from(previous) as i32,
191            effective: MetricDetail::from(effective) as i32,
192            transition_kind: transition_kind.to_owned(),
193            client_id,
194        }),
195    });
196}
197
198#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
199pub struct ListenToken(pub usize);
200#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
201pub struct SessionToken(pub usize);
202
203impl From<usize> for ListenToken {
204    fn from(val: usize) -> ListenToken {
205        ListenToken(val)
206    }
207}
208
209impl From<ListenToken> for usize {
210    fn from(val: ListenToken) -> usize {
211        val.0
212    }
213}
214
215impl From<usize> for SessionToken {
216    fn from(val: usize) -> SessionToken {
217        SessionToken(val)
218    }
219}
220
221impl From<SessionToken> for usize {
222    fn from(val: SessionToken) -> usize {
223        val.0
224    }
225}
226
227pub struct SessionManager {
228    pub max_connections: usize,
229    pub nb_connections: usize,
230    pub can_accept: bool,
231    pub slab: Slab<Rc<RefCell<dyn ProxySession>>>,
232    /// Default per-(cluster, source-IP) connection limit. `0` disables
233    /// the feature; cluster-level overrides take precedence at check
234    /// time.
235    pub max_connections_per_ip: u64,
236    /// Default `Retry-After` header value (seconds) for HTTP 429
237    /// responses emitted on per-(cluster, source-IP) limit hit. `0`
238    /// omits the header.
239    pub retry_after: u32,
240    /// Active **frontend connections** per `(cluster_id, source_ip)`.
241    /// Each frontend session contributes AT MOST 1 to the count for any
242    /// given `(cluster, ip)` pair, regardless of how many streams it
243    /// multiplexes to that cluster (an H2 connection serving 100
244    /// streams to cluster X from IP 1.2.3.4 still counts as 1). The
245    /// counter is incremented the first time a session's
246    /// `Router::connect` resolves to a fresh `(cluster, ip)` pair, and
247    /// decremented when the session closes. Empty when the feature is
248    /// unused.
249    ///
250    /// ── Why nested maps instead of `HashMap<(String, IpAddr), usize>` ──
251    ///
252    /// The per-request hot path (`cluster_ip_at_limit`, called from
253    /// `mux/router::connect` for every cluster-resolving request) used
254    /// to allocate a `String` to build the compound key on every
255    /// lookup. Splitting the storage so the outer key is `String`
256    /// lets the lookup take `&str` — `HashMap::get(cluster_id)` on a
257    /// `HashMap<String, _>` accepts a borrow via the `Borrow<str>`
258    /// impl. The hot path no longer allocates; the only `String` clone
259    /// is in `track_cluster_ip`, which runs at most once per
260    /// `(cluster, ip)` pair per session. Memory footprint is unchanged
261    /// in steady state — entries are still reaped to zero on session
262    /// close.
263    connections_per_cluster_ip: HashMap<String, HashMap<IpAddr, usize>>,
264    /// Reverse index: per-token map of `cluster_id` → set of source IPs
265    /// already counted against `connections_per_cluster_ip`. Used to
266    /// make `track_cluster_ip` idempotent within a session (so H2
267    /// streams to the same cluster from the same client only consume
268    /// one slot in the limit) and to drain a session's contributions on
269    /// close. Same nesting rationale as above.
270    cluster_ip_tracks: HashMap<Token, HashMap<String, HashSet<IpAddr>>>,
271}
272
273impl SessionManager {
274    pub fn new(
275        slab: Slab<Rc<RefCell<dyn ProxySession>>>,
276        max_connections: usize,
277        max_connections_per_ip: u64,
278        retry_after: u32,
279    ) -> Rc<RefCell<Self>> {
280        Rc::new(RefCell::new(SessionManager {
281            max_connections,
282            nb_connections: 0,
283            can_accept: true,
284            slab,
285            max_connections_per_ip,
286            retry_after,
287            connections_per_cluster_ip: HashMap::new(),
288            cluster_ip_tracks: HashMap::new(),
289        }))
290    }
291
292    /// Resolve the effective per-(cluster, source-IP) limit. `override_value`
293    /// is the cluster-level setting from the proto `Cluster` message:
294    /// `None` inherits the global default, `Some(0)` is explicit
295    /// "unlimited", `Some(n > 0)` overrides.
296    pub fn effective_max_connections_per_ip(&self, override_value: Option<u64>) -> u64 {
297        override_value.unwrap_or(self.max_connections_per_ip)
298    }
299
300    /// Resolve the effective `Retry-After` header value. `Some(0)` (or
301    /// the global default of 0) signals "omit the header" — caller
302    /// must skip emission rather than render `Retry-After: 0`.
303    pub fn effective_retry_after(&self, override_value: Option<u32>) -> u32 {
304        override_value.unwrap_or(self.retry_after)
305    }
306
307    /// Returns `true` when admitting `token` to one more connection for
308    /// `(cluster, ip)` would exceed the resolved limit. `0` is treated
309    /// as unlimited. A token that already holds a slot for this
310    /// `(cluster, ip)` is NEVER at the limit — H2 sessions multiplex
311    /// many streams to the same cluster on a single connection, and
312    /// the limit governs distinct frontend connections, not streams.
313    ///
314    /// Hot-path: called for every cluster-resolving request from
315    /// `mux/router::connect`. The nested-map storage lets both lookups
316    /// borrow `cluster_id` and `ip`; no per-call allocation runs here
317    /// in steady state.
318    pub fn cluster_ip_at_limit(
319        &self,
320        token: Token,
321        cluster_id: &str,
322        ip: &IpAddr,
323        override_value: Option<u64>,
324    ) -> bool {
325        let limit = self.effective_max_connections_per_ip(override_value);
326        if limit == 0 {
327            return false;
328        }
329        // Pure query: the limit==0 branch already returned, so any work below
330        // runs only with a positive cap.
331        debug_assert!(
332            limit > 0,
333            "limit==0 (unlimited) must have returned before reaching the bounded check"
334        );
335        let already_tracked = self
336            .cluster_ip_tracks
337            .get(&token)
338            .and_then(|by_cluster| by_cluster.get(cluster_id))
339            .is_some_and(|ips| ips.contains(ip));
340        if already_tracked {
341            // Reverse-index/forward-count coherence: if this token already
342            // holds a slot for (cluster, ip), the forward count must be > 0
343            // (decrement-to-zero reaps the inner entry in untrack_all).
344            debug_assert!(
345                self.connections_per_cluster_ip
346                    .get(cluster_id)
347                    .and_then(|by_ip| by_ip.get(ip))
348                    .is_some_and(|c| *c > 0),
349                "a tracked (token, cluster, ip) slot must have a positive forward count"
350            );
351            return false;
352        }
353        self.connections_per_cluster_ip
354            .get(cluster_id)
355            .and_then(|by_ip| by_ip.get(ip))
356            .is_some_and(|c| (*c as u64) >= limit)
357    }
358
359    /// Account `token`'s active connection against `(cluster, ip)`.
360    /// Idempotent within a token: a second call for the same
361    /// `(cluster, ip)` is a no-op so H2 retries / multi-stream opens
362    /// to the same cluster do not double-count.
363    ///
364    /// Allocates a single owned `String` per `(token, cluster)` pair on
365    /// first observation — `entry(cluster_id.clone())` materialises a
366    /// new outer-map slot. Subsequent IPs under the same `(token,
367    /// cluster)` reuse the existing slot.
368    pub fn track_cluster_ip(&mut self, token: Token, cluster_id: String, ip: IpAddr) {
369        // Snapshot the forward count for this (cluster, ip) before the insert
370        // so we can pair-assert the delta. Ungated `let`: read only inside the
371        // debug_assert! below → optimised out in release (no E0425).
372        let count_before = self
373            .connections_per_cluster_ip
374            .get(&cluster_id)
375            .and_then(|by_ip| by_ip.get(&ip))
376            .copied()
377            .unwrap_or(0);
378        let inserted = self
379            .cluster_ip_tracks
380            .entry(token)
381            .or_default()
382            .entry(cluster_id.clone())
383            .or_default()
384            .insert(ip);
385        if inserted {
386            *self
387                .connections_per_cluster_ip
388                .entry(cluster_id.clone())
389                .or_default()
390                .entry(ip)
391                .or_insert(0) += 1;
392        }
393        // Postconditions: the reverse index now records this (token, cluster,
394        // ip), and the forward count advanced by exactly `inserted as usize`
395        // (idempotent: a repeat call for the same triple is a no-op on both).
396        debug_assert!(
397            self.cluster_ip_tracks
398                .get(&token)
399                .and_then(|by_cluster| by_cluster.get(&cluster_id))
400                .is_some_and(|ips| ips.contains(&ip)),
401            "track must leave the (token, cluster, ip) recorded in the reverse index"
402        );
403        debug_assert_eq!(
404            self.connections_per_cluster_ip
405                .get(&cluster_id)
406                .and_then(|by_ip| by_ip.get(&ip))
407                .copied()
408                .unwrap_or(0),
409            count_before + inserted as usize,
410            "forward count must advance by exactly 1 on first track, 0 on a repeat"
411        );
412        #[cfg(debug_assertions)]
413        self.check_invariants();
414    }
415
416    /// Drain every `(cluster, ip)` slot held by `token` and apply the
417    /// matching decrements. Called on session teardown only — there is
418    /// no per-stream untrack because the limit is per-connection, not
419    /// per-stream. Removes empty inner maps so the outer
420    /// `connections_per_cluster_ip` does not retain `(cluster_id,
421    /// empty_map)` orphans across cluster lifetimes.
422    pub fn untrack_all_cluster_ip(&mut self, token: Token) {
423        let Some(by_cluster) = self.cluster_ip_tracks.remove(&token) else {
424            return;
425        };
426        // The reverse index for this token was just drained by `remove`; no
427        // other code path re-inserts it within this call.
428        debug_assert!(
429            !self.cluster_ip_tracks.contains_key(&token),
430            "untrack_all must evict the token from the reverse index"
431        );
432        for (cluster_id, ips) in by_cluster {
433            let Entry::Occupied(mut outer) = self.connections_per_cluster_ip.entry(cluster_id)
434            else {
435                continue;
436            };
437            for ip in ips {
438                if let Entry::Occupied(mut inner) = outer.get_mut().entry(ip) {
439                    let count = inner.get_mut();
440                    *count = count.saturating_sub(1);
441                    if *count == 0 {
442                        inner.remove();
443                    }
444                }
445            }
446            if outer.get().is_empty() {
447                outer.remove();
448            }
449        }
450        // No orphan bookkeeping survives: the forward map must not retain a
451        // cluster with an empty inner ip-map, nor an ip whose count is zero.
452        debug_assert!(
453            self.connections_per_cluster_ip
454                .values()
455                .all(|by_ip| !by_ip.is_empty() && by_ip.values().all(|&c| c > 0)),
456            "untrack_all must not leave empty inner maps or zero-count ips behind"
457        );
458        #[cfg(debug_assertions)]
459        self.check_invariants();
460    }
461
462    /// Wipe every per-(cluster, source-IP) accounting bucket. Called by
463    /// the runtime `SetMaxConnectionsPerIp(0)` path so disabling the
464    /// feature does not leave dead bookkeeping behind that a future
465    /// re-enable would consult.
466    pub fn clear_cluster_ip_tracking(&mut self) {
467        self.cluster_ip_tracks.clear();
468        self.connections_per_cluster_ip.clear();
469        // Both halves of the per-(cluster, ip) accounting are now empty; a
470        // future re-enable starts from a clean slate.
471        debug_assert!(
472            self.cluster_ip_tracks.is_empty() && self.connections_per_cluster_ip.is_empty(),
473            "clear must wipe both the reverse index and the forward count map"
474        );
475        #[cfg(debug_assertions)]
476        self.check_invariants();
477    }
478
479    /// The slab is considered at capacity if it contains more sessions than twice max_connections
480    pub fn at_capacity(&self) -> bool {
481        self.slab.len() >= self.accept_slab_threshold()
482    }
483
484    /// The slab fill level at which `at_capacity` flips to true and the
485    /// accept queue is flushed. Reported as `slab.accept_threshold` so the
486    /// per-iteration `slab.accept_threshold_percent` gauge in the run loop
487    /// can chart proximity to this gate, distinct from raw slab usage.
488    ///
489    /// The constant `10 + 2 * max_connections` is the historical pre-knob
490    /// budget; configured slab capacity is
491    /// `10 + slab_entries_per_connection * max_connections` (see
492    /// `command/src/config.rs`) and can be larger, so `slab.usage_percent`
493    /// (against `slab.capacity()`) and `slab.accept_threshold_percent`
494    /// (against this gate) are emitted as independent gauges.
495    pub fn accept_slab_threshold(&self) -> usize {
496        let threshold = 10 + 2 * self.max_connections;
497        // The gate must leave headroom above the connection cap so listener /
498        // system slots (Channel, Metrics, Timer, listeners) are never starved
499        // by frontend connections alone.
500        debug_assert!(
501            threshold > self.max_connections,
502            "accept gate must sit strictly above max_connections to reserve system slots"
503        );
504        threshold
505    }
506
507    /// Check the number of connections against max_connections, and the slab capacity.
508    /// Returns false if limits are reached.
509    pub fn check_limits(&mut self) -> bool {
510        // Live-count invariant: the accounted connection count never exceeds
511        // the configured cap (incr() enforces this, decr() never underflows).
512        debug_assert!(
513            self.nb_connections <= self.max_connections,
514            "nb_connections must never exceed max_connections"
515        );
516        if self.nb_connections >= self.max_connections {
517            error!("max number of session connection reached, flushing the accept queue");
518            gauge!(names::accept_queue::BACKPRESSURE, 1);
519            self.can_accept = false;
520            // A negative result must have closed the accept gate.
521            debug_assert!(
522                !self.can_accept,
523                "refusing at the cap must clear can_accept"
524            );
525            return false;
526        }
527
528        if self.at_capacity() {
529            error!("not enough memory to accept another session, flushing the accept queue");
530            error!(
531                "nb_connections: {}, max_connections: {}",
532                self.nb_connections, self.max_connections
533            );
534            gauge!(names::accept_queue::BACKPRESSURE, 1);
535            self.can_accept = false;
536
537            debug_assert!(
538                !self.can_accept,
539                "refusing at slab capacity must clear can_accept"
540            );
541            return false;
542        }
543
544        // A positive result means there is room under both gates.
545        debug_assert!(
546            self.nb_connections < self.max_connections && !self.at_capacity(),
547            "check_limits returned room while a gate was actually saturated"
548        );
549        true
550    }
551
552    pub fn to_session(token: Token) -> SessionToken {
553        SessionToken(token.0)
554    }
555
556    pub fn incr(&mut self) {
557        let before = self.nb_connections;
558        self.nb_connections += 1;
559        assert!(self.nb_connections <= self.max_connections);
560        // The counter advances by exactly one per accepted session.
561        debug_assert_eq!(
562            self.nb_connections,
563            before + 1,
564            "incr must raise nb_connections by exactly one"
565        );
566        // `client.connections_max` and `client.connections_percent` are
567        // emitted from the run loop alongside `process.uptime_seconds` /
568        // `server.live` so all proxy gauges advance in lock-step. Keeping
569        // `client.connections` per-event preserves the high-resolution
570        // signal scrapers expect.
571        gauge!(names::client::CONNECTIONS, self.nb_connections);
572    }
573
574    /// Decrements the number of sessions, start accepting new connections
575    /// if the capacity limit of 90% has not been reached.
576    pub fn decr(&mut self) {
577        assert!(self.nb_connections != 0);
578        let before = self.nb_connections;
579        self.nb_connections -= 1;
580        // Mirror of incr: exactly one connection is released, no underflow.
581        debug_assert_eq!(
582            self.nb_connections,
583            before - 1,
584            "decr must lower nb_connections by exactly one"
585        );
586        gauge!(names::client::CONNECTIONS, self.nb_connections);
587
588        // do not be ready to accept right away, wait until we get back to 10% capacity
589        if !self.can_accept && self.nb_connections < self.max_connections * 90 / 100 {
590            debug!(
591                "nb_connections = {}, max_connections = {}, starting to accept again",
592                self.nb_connections, self.max_connections
593            );
594            gauge!(names::accept_queue::BACKPRESSURE, 0);
595            self.can_accept = true;
596        }
597    }
598
599    /// Full cross-field invariant sweep for the session manager. Called as a
600    /// `debug_assert!`-guarded postcondition from the mutating cluster-IP
601    /// tracking methods. Asserts logic-bug conditions only — every clause
602    /// holds on any well-formed manager regardless of traffic.
603    #[cfg(debug_assertions)]
604    fn check_invariants(&self) {
605        // 1. Live connection count never exceeds the configured cap.
606        debug_assert!(
607            self.nb_connections <= self.max_connections,
608            "nb_connections {} exceeds max_connections {}",
609            self.nb_connections,
610            self.max_connections
611        );
612        // 2. The forward count map never retains an empty inner ip-map or a
613        //    zero count (both are reaped on the last untrack).
614        debug_assert!(
615            self.connections_per_cluster_ip
616                .values()
617                .all(|by_ip| !by_ip.is_empty() && by_ip.values().all(|&c| c > 0)),
618            "connections_per_cluster_ip holds an empty inner map or a zero count"
619        );
620        // 3. Reverse-index → forward-count coherence: every (token, cluster,
621        //    ip) recorded in the reverse index must have a positive forward
622        //    count. The forward count is the sum of contributing tokens, so a
623        //    tracked slot can never point at a missing/zero count.
624        debug_assert!(
625            self.cluster_ip_tracks.values().all(|by_cluster| {
626                by_cluster.iter().all(|(cluster_id, ips)| {
627                    ips.iter().all(|ip| {
628                        self.connections_per_cluster_ip
629                            .get(cluster_id)
630                            .and_then(|by_ip| by_ip.get(ip))
631                            .is_some_and(|&c| c > 0)
632                    })
633                })
634            }),
635            "a tracked (token, cluster, ip) slot has no positive forward count"
636        );
637        // 4. The reverse index never retains an empty inner structure.
638        debug_assert!(
639            self.cluster_ip_tracks.values().all(|by_cluster| {
640                !by_cluster.is_empty() && by_cluster.values().all(|ips| !ips.is_empty())
641            }),
642            "cluster_ip_tracks retains an empty per-token or per-cluster entry"
643        );
644    }
645}
646
647#[derive(thiserror::Error, Debug)]
648pub enum ServerError {
649    #[error("could not create event loop with MIO poll: {0}")]
650    CreatePoll(IoError),
651    #[error("could not clone the MIO registry: {0}")]
652    CloneRegistry(IoError),
653    #[error("could not register the channel: {0}")]
654    RegisterChannel(IoError),
655    #[error("{msg}:{scm_err}")]
656    ScmSocket {
657        msg: String,
658        scm_err: ScmSocketError,
659    },
660}
661
662/// `Server` handles the event loop, the listeners, the sessions and
663/// communication with the configuration channel.
664///
665/// A listener wraps a listen socket, the associated proxying protocols
666/// (HTTP, HTTPS and TCP) and the routing configuration for clusters.
667/// Listeners handle creating sessions from accepted sockets.
668///
669/// A session manages a "front" socket for a connected client, and all
670/// of the associated data (back socket, protocol state machine, buffers,
671/// metrics...).
672///
673/// `Server` gets configuration updates from the channel (domIN/path routes,
674/// backend server address...).
675///
676/// Listeners and sessions are all stored in a slab structure to index them
677/// by a [Token], they all have to implement the [ProxySession] trait.
678pub struct Server {
679    accept_queue_timeout: Duration,
680    /// Tuple layout: `(socket, listen token, protocol, accept time, peer
681    /// address)`. The peer is captured via `TcpStream::peer_addr()` at accept
682    /// time so the `client.connect.per_source.*` counter can be attributed
683    /// without the socket having to be alive at session-creation time. The
684    /// peer is `Option` because `peer_addr()` is best-effort: a peer that
685    /// races to close before we read it is rare but possible.
686    accept_queue: VecDeque<(
687        TcpStream,
688        ListenToken,
689        Protocol,
690        Instant,
691        Option<SocketAddr>,
692    )>,
693    /// When the accept queue saturates and `check_limits` refuses, evict the
694    /// oldest non-listener sessions to make room. Default off — see
695    /// `command::config::DEFAULT_EVICT_ON_QUEUE_FULL` for the rationale.
696    evict_on_queue_full: bool,
697    accept_ready: HashSet<ListenToken>,
698    backends: Rc<RefCell<BackendMap>>,
699    base_sessions_count: usize,
700    channel: ProxyChannel,
701    config_state: ConfigState,
702    current_poll_errors: i32,
703    health_checker: HealthChecker,
704    http: Rc<RefCell<http::HttpProxy>>,
705    https: Rc<RefCell<https::HttpsProxy>>,
706    last_sessions_len: usize,
707    last_shutting_down_message: Option<Instant>,
708    last_zombie_check: Instant,
709    loop_start: Instant,
710    /// Wall-clock anchor for the `process.uptime_seconds` gauge. Captured once
711    /// in [`Server::new`]; never reset on hot upgrades (the new worker that
712    /// inherits FDs is a fresh process and starts its own counter).
713    started_at: Instant,
714    /// Last time the 1Hz `accept_queue.saturated_seconds` ticker fired. The
715    /// counter is incremented once per [`ACCEPT_SATURATION_TICK`] while
716    /// `SessionManager::can_accept` is `false`, so dashboards can plot the
717    /// time spent saturated rather than just whether saturation occurred.
718    last_saturation_tick: Instant,
719    max_poll_errors: i32, // TODO: make this configurable? this defaults to 10000 for now
720    /// Shared reference to the buffer pool the protocol stacks check buffers
721    /// in and out of. Held here so the run loop can sample
722    /// `buffer.in_use` / `buffer.capacity` / `buffer.usage_percent` once per
723    /// iteration without requiring each protocol module to expose its own
724    /// snapshot.
725    pool: Rc<RefCell<Pool>>,
726    pub poll: Poll,
727    poll_timeout: Option<Duration>, // TODO: make this configurable? this defaults to 1000 milliseconds for now
728    scm_listeners: Option<Listeners>,
729    scm: ScmSocket,
730    sessions: Rc<RefCell<SessionManager>>,
731    should_poll_at: Option<Instant>,
732    shutting_down: Option<String>,
733    tcp: Rc<RefCell<tcp::TcpProxy>>,
734    udp: Rc<RefCell<udp::UdpProxy>>,
735    zombie_check_interval: Duration,
736}
737
738impl Server {
739    pub fn try_new_from_config(
740        worker_to_main_channel: ProxyChannel,
741        worker_to_main_scm: ScmSocket,
742        config: ServerConfig,
743        initial_state: InitialState,
744        expects_initial_status: bool,
745    ) -> Result<Self, ServerError> {
746        let event_loop = Poll::new().map_err(ServerError::CreatePoll)?;
747        // Commit the operator-configured Basic-auth credential cap (or
748        // keep the built-in default) once per worker process. The
749        // `OnceLock` rejects any later attempt to change the value, so
750        // calling here — before any L7 listener has accepted a request
751        // — guarantees the cap is in force the first time `mux::auth`
752        // runs.
753        if let Some(cap) = config.basic_auth_max_credential_bytes {
754            crate::protocol::mux::auth::set_max_decoded_credential_bytes(cap as usize);
755        }
756        // Same set-once-per-worker-boot pattern for the splice kernel-pipe
757        // capacity. The setter no-ops on `0` so an explicit zero in config
758        // does not collapse the pipe to PAGE_SIZE; the kernel still applies
759        // page-rounding and `/proc/sys/fs/pipe-max-size` clamping at
760        // SplicePipe::new time. Cfg-gated because the splice module only
761        // exists on Linux + `splice` feature.
762        #[cfg(all(target_os = "linux", feature = "splice"))]
763        if let Some(cap) = config.splice_pipe_capacity_bytes {
764            crate::splice::set_pipe_capacity(cap as usize);
765        }
766        let pool = Rc::new(RefCell::new(Pool::with_capacity(
767            config.min_buffers as usize,
768            config.max_buffers as usize,
769            config.buffer_size as usize,
770        )));
771        let backends = Rc::new(RefCell::new(BackendMap::new()));
772
773        // Note: slab_capacity uses 4x multiplier (up from 2x) to account for H2
774        // multiplexing where each session can have multiple backend connections.
775        // Newer `optional` proto fields fall through to the
776        // command-lib defaults when an older worker manager omits them.
777        let sessions: Rc<RefCell<SessionManager>> = SessionManager::new(
778            Slab::with_capacity(config.slab_capacity() as usize),
779            config.max_connections as usize,
780            config
781                .max_connections_per_ip
782                .unwrap_or(sozu_command::config::DEFAULT_MAX_CONNECTIONS_PER_IP),
783            config
784                .retry_after
785                .unwrap_or(sozu_command::config::DEFAULT_RETRY_AFTER),
786        );
787        {
788            let mut s = sessions.borrow_mut();
789            let entry = s.slab.vacant_entry();
790            trace!("taking token {:?} for channel", SessionToken(entry.key()));
791            entry.insert(Rc::new(RefCell::new(ListenSession {
792                protocol: Protocol::Channel,
793            })));
794        }
795        {
796            let mut s = sessions.borrow_mut();
797            let entry = s.slab.vacant_entry();
798            trace!("taking token {:?} for metrics", SessionToken(entry.key()));
799            entry.insert(Rc::new(RefCell::new(ListenSession {
800                protocol: Protocol::Timer,
801            })));
802        }
803        {
804            let mut s = sessions.borrow_mut();
805            let entry = s.slab.vacant_entry();
806            trace!("taking token {:?} for metrics", SessionToken(entry.key()));
807            entry.insert(Rc::new(RefCell::new(ListenSession {
808                protocol: Protocol::Metrics,
809            })));
810        }
811
812        Server::new(
813            event_loop,
814            worker_to_main_channel,
815            worker_to_main_scm,
816            sessions,
817            pool,
818            backends,
819            None,
820            None,
821            None,
822            config,
823            Some(initial_state),
824            expects_initial_status,
825        )
826    }
827
828    #[allow(clippy::too_many_arguments)]
829    pub fn new(
830        poll: Poll,
831        mut channel: ProxyChannel,
832        scm: ScmSocket,
833        sessions: Rc<RefCell<SessionManager>>,
834        pool: Rc<RefCell<Pool>>,
835        backends: Rc<RefCell<BackendMap>>,
836        http: Option<http::HttpProxy>,
837        https: Option<https::HttpsProxy>,
838        tcp: Option<tcp::TcpProxy>,
839        server_config: ServerConfig,
840        initial_state: Option<InitialState>,
841        expects_initial_status: bool,
842    ) -> Result<Self, ServerError> {
843        FEATURES.with(|_features| {
844            // initializing feature flags
845        });
846
847        poll.registry()
848            .register(
849                &mut channel,
850                Token(0),
851                Interest::READABLE | Interest::WRITABLE,
852            )
853            .map_err(ServerError::RegisterChannel)?;
854
855        METRICS.with(|metrics| {
856            if let Some(sock) = (*metrics.borrow_mut()).socket_mut() {
857                poll.registry()
858                    .register(sock, Token(2), Interest::WRITABLE)
859                    .expect("should register the metrics socket");
860            }
861        });
862
863        let base_sessions_count = sessions.borrow().slab.len();
864
865        let http = Rc::new(RefCell::new(match http {
866            Some(http) => http,
867            None => {
868                let registry = poll
869                    .registry()
870                    .try_clone()
871                    .map_err(ServerError::CloneRegistry)?;
872
873                http::HttpProxy::new(registry, sessions.clone(), pool.clone(), backends.clone())
874            }
875        }));
876
877        let https = Rc::new(RefCell::new(match https {
878            Some(https) => https,
879            None => {
880                let registry = poll
881                    .registry()
882                    .try_clone()
883                    .map_err(ServerError::CloneRegistry)?;
884
885                https::HttpsProxy::new(registry, sessions.clone(), pool.clone(), backends.clone())
886            }
887        }));
888
889        let tcp = Rc::new(RefCell::new(match tcp {
890            Some(tcp) => tcp,
891            None => {
892                let registry = poll
893                    .registry()
894                    .try_clone()
895                    .map_err(ServerError::CloneRegistry)?;
896
897                tcp::TcpProxy::new(registry, sessions.clone(), pool.clone(), backends.clone())
898            }
899        }));
900
901        // UDP proxy is constructed internally (no constructor parameter) so the
902        // public `Server::new` / `try_new_from_config` signatures stay
903        // unchanged — bin/ is not forced to change for construction.
904        let udp = Rc::new(RefCell::new({
905            let registry = poll
906                .registry()
907                .try_clone()
908                .map_err(ServerError::CloneRegistry)?;
909
910            udp::UdpProxy::new(
911                registry,
912                sessions.clone(),
913                pool.clone(),
914                backends.clone(),
915                server_config.max_connections as usize,
916                server_config.buffer_size as usize,
917            )
918        }));
919
920        let mut server = Server {
921            accept_queue_timeout: Duration::from_secs(u64::from(
922                server_config.accept_queue_timeout,
923            )),
924            accept_queue: VecDeque::new(),
925            evict_on_queue_full: server_config.evict_on_queue_full.unwrap_or(false),
926            accept_ready: HashSet::new(),
927            backends,
928            base_sessions_count,
929            channel,
930            config_state: ConfigState::new(),
931            current_poll_errors: 0,
932            health_checker: HealthChecker::new(),
933            http,
934            https,
935            last_sessions_len: 0, // to be reset on server run
936            last_shutting_down_message: None,
937            last_zombie_check: Instant::now(), // to be reset on server run
938            loop_start: Instant::now(),        // to be reset on server run
939            started_at: Instant::now(),        // captured once, never reset
940            last_saturation_tick: Instant::now(), // 1Hz saturation ticker anchor
941            max_poll_errors: 10000,            // TODO: make it configurable?
942            pool,
943            poll_timeout: Some(Duration::from_millis(1000)), // TODO: make it configurable?
944            poll,
945            scm_listeners: None,
946            scm,
947            sessions,
948            should_poll_at: None,
949            shutting_down: None,
950            tcp,
951            udp,
952            zombie_check_interval: Duration::from_secs(u64::from(
953                server_config.zombie_check_interval,
954            )),
955        };
956
957        // initialize the worker with the state we got from a file
958        if let Some(state) = initial_state {
959            for request in state.requests {
960                trace!("generating initial config request: {:#?}", request);
961                server.notify_proxys(request);
962            }
963
964            // do not send back answers to the initialization messages
965            QUEUE.with(|queue| {
966                (*queue.borrow_mut()).clear();
967            });
968        }
969
970        if expects_initial_status {
971            // the main process sends a Status message, so we can notify it
972            // when the initial state is loaded
973            server.block_channel();
974            let msg = server.channel.read_message();
975            debug!("got message: {:?}", msg);
976
977            if let Ok(WorkerRequest {
978                id,
979                content:
980                    Request {
981                        request_type: Some(RequestType::Status(_)),
982                    },
983            }) = msg
984            {
985                if let Err(e) = server.channel.write_message(&WorkerResponse::ok(id)) {
986                    error!("Could not send an ok to the main process: {}", e);
987                }
988            } else {
989                panic!(
990                    "plz give me a status request first when I start, you sent me this instead: {msg:?}"
991                );
992            }
993            server.unblock_channel();
994        }
995
996        info!("will try to receive listeners");
997        server
998            .scm
999            .set_blocking(true)
1000            .map_err(|scm_err| ServerError::ScmSocket {
1001                msg: "Could not set the scm socket to blocking".to_string(),
1002                scm_err,
1003            })?;
1004        let listeners =
1005            server
1006                .scm
1007                .receive_listeners()
1008                .map_err(|scm_err| ServerError::ScmSocket {
1009                    msg: "could not receive listeners from the scm socket".to_string(),
1010                    scm_err,
1011                })?;
1012        server
1013            .scm
1014            .set_blocking(false)
1015            .map_err(|scm_err| ServerError::ScmSocket {
1016                msg: "Could not set the scm socket to unblocking".to_string(),
1017                scm_err,
1018            })?;
1019        info!("received listeners: {:?}", listeners);
1020        server.scm_listeners = Some(listeners);
1021
1022        Ok(server)
1023    }
1024
1025    /// The server runs in a loop until a shutdown is ordered
1026    pub fn run(&mut self) {
1027        let mut events = Events::with_capacity(1024); // TODO: make event capacity configurable?
1028        self.last_sessions_len = self.sessions.borrow().slab.len();
1029
1030        self.last_zombie_check = Instant::now();
1031        self.loop_start = Instant::now();
1032
1033        loop {
1034            self.check_for_poll_errors();
1035
1036            let timeout = self.reset_loop_time_and_get_timeout();
1037
1038            match self.poll.poll(&mut events, timeout) {
1039                Ok(_) => self.current_poll_errors = 0,
1040                Err(error) => {
1041                    error!("Error while polling events: {:?}", error);
1042                    self.current_poll_errors += 1;
1043                    continue;
1044                }
1045            }
1046
1047            let after_epoll = Instant::now();
1048            time!(
1049                names::event_loop::EPOLL_TIME,
1050                (after_epoll - self.loop_start).as_millis()
1051            );
1052            self.loop_start = after_epoll;
1053
1054            self.send_queue();
1055
1056            for event in events.iter() {
1057                match event.token() {
1058                    // this is the command channel
1059                    Token(0) => {
1060                        if event.is_error() {
1061                            error!("error reading from command channel");
1062                            continue;
1063                        }
1064                        if event.is_read_closed() || event.is_write_closed() {
1065                            error!("command channel was closed");
1066                            return;
1067                        }
1068                        let ready = Ready::from(event);
1069                        self.channel.handle_events(ready);
1070
1071                        // loop here because iterations has borrow issues
1072                        loop {
1073                            QUEUE.with(|queue| {
1074                                if !(*queue.borrow()).is_empty() {
1075                                    self.channel.interest.insert(Ready::WRITABLE);
1076                                }
1077                            });
1078
1079                            //trace!("WORKER[{}] channel readiness={:?}, interest={:?}, queue={} elements",
1080                            //  line!(), self.channel.readiness, self.channel.interest, self.queue.len());
1081                            if self.channel.readiness() == Ready::EMPTY {
1082                                break;
1083                            }
1084
1085                            // exit the big loop if the message is HardStop
1086                            if self.read_channel_messages_and_notify() {
1087                                return;
1088                            }
1089
1090                            QUEUE.with(|queue| {
1091                                if !(*queue.borrow()).is_empty() {
1092                                    self.channel.interest.insert(Ready::WRITABLE);
1093                                }
1094                            });
1095
1096                            self.send_queue();
1097                        }
1098                    }
1099                    // timer tick
1100                    Token(1) => {
1101                        while let Some(t) = TIMER.with(|timer| timer.borrow_mut().poll()) {
1102                            self.timeout(t);
1103                        }
1104                    }
1105                    // metrics socket is writable
1106                    Token(2) => METRICS.with(|metrics| {
1107                        (*metrics.borrow_mut()).writable();
1108                    }),
1109                    // ListenToken: 1 listener <=> 1 token
1110                    // ProtocolToken (HTTP/HTTPS/TCP): 1 connection <=> 1 token
1111                    token if self.health_checker.owns_token(token) => {
1112                        self.health_checker.ready(token);
1113                    }
1114                    token if self.udp.borrow().health_owns_token(token) => {
1115                        self.udp.borrow_mut().health_ready(token);
1116                    }
1117                    token => self.ready(token, Ready::from(event)),
1118                }
1119            }
1120
1121            if let Some(t) = self.should_poll_at.as_ref()
1122                && *t <= Instant::now()
1123            {
1124                while let Some(t) = TIMER.with(|timer| timer.borrow_mut().poll()) {
1125                    //info!("polled for timeout: {:?}", t);
1126                    self.timeout(t);
1127                }
1128            }
1129            self.handle_remaining_readiness();
1130            self.create_sessions();
1131
1132            self.should_poll_at = TIMER.with(|timer| timer.borrow().next_poll_date());
1133
1134            self.zombie_check();
1135            self.health_checker
1136                .poll(&self.backends, self.poll.registry());
1137            // Drive the UDP endpoint health prober (TCP-probe + hysteresis +
1138            // fail-open). Non-blocking; no-op when no UDP cluster has health
1139            // configured.
1140            self.udp.borrow_mut().health_poll();
1141
1142            // Frontend session gauges. `client.connections` keeps the
1143            // per-event signal from `SessionManager::incr/decr`; the rest
1144            // follow the once-per-iteration batching contract this run loop
1145            // uses for `process.uptime_seconds` / `server.live`.
1146            //
1147            // `slab.usage_percent` charts pure slab utilisation against
1148            // `slab.capacity()`. `slab.accept_threshold_percent` charts how
1149            // close the slab is to the `at_capacity()` accept gate
1150            // (`10 + 2 * max_connections`, see
1151            // `SessionManager::accept_slab_threshold`). Configured slab
1152            // capacity can be larger than that gate (`slab_entries_per_connection`
1153            // > 2), so the two gauges are kept independent on purpose.
1154            {
1155                let sessions = self.sessions.borrow();
1156                let nb_connections = sessions.nb_connections;
1157                let max_connections = sessions.max_connections;
1158                let slab_len = sessions.slab.len();
1159                let slab_capacity = sessions.slab.capacity();
1160                let accept_threshold = sessions.accept_slab_threshold();
1161
1162                gauge!(names::client::CONNECTIONS, nb_connections);
1163                gauge!(names::client::CONNECTIONS_MAX, max_connections);
1164                if let Some(percent) = (nb_connections * 100).checked_div(max_connections) {
1165                    gauge!("client.connections_percent", percent);
1166                }
1167
1168                gauge!(names::slab::ENTRIES, slab_len);
1169                gauge!(names::slab::CAPACITY, slab_capacity);
1170                if let Some(percent) = (slab_len * 100).checked_div(slab_capacity) {
1171                    gauge!(names::slab::USAGE_PERCENT, percent);
1172                }
1173                if let Some(percent) = (slab_len * 100).checked_div(accept_threshold) {
1174                    gauge!("slab.accept_threshold_percent", percent);
1175                }
1176            }
1177            // Buffer pool gauges. `buffer.in_use` replaces the older
1178            // `buffer.number` (renamed in `lib/src/pool.rs` for naming
1179            // consistency with the surrounding `buffer.*` keys).
1180            // `buffer.usage_percent` is computed against the configured
1181            // `buffer.capacity` so dashboards can chart pool pressure.
1182            {
1183                let pool = self.pool.borrow();
1184                let used = pool.inner.used();
1185                let capacity = pool.inner.capacity();
1186                gauge!(names::buffer::IN_USE, used);
1187                gauge!(names::buffer::CAPACITY, capacity);
1188                if let Some(percent) = (used * 100).checked_div(capacity) {
1189                    gauge!(names::buffer::USAGE_PERCENT, percent);
1190                }
1191            }
1192            // 1Hz tick for `accept_queue.saturated_seconds`. Increments once
1193            // per `ACCEPT_SATURATION_TICK` while `SessionManager::can_accept`
1194            // is `false`. Distinguishes "queue spent N seconds at max" from
1195            // "queue briefly hit max" — the binary `accept_queue.backpressure`
1196            // gauge collapses that duration. Sampled here rather than via a
1197            // dedicated mio timer because the run loop ticks at least once
1198            // per `poll_timeout` (1s by default), which is granular enough.
1199            let now = Instant::now();
1200            if now.duration_since(self.last_saturation_tick) >= ACCEPT_SATURATION_TICK {
1201                if !self.sessions.borrow().can_accept {
1202                    incr!(names::accept_queue::SATURATED_SECONDS);
1203                }
1204                self.last_saturation_tick = now;
1205            }
1206            // Process / runtime gauges sampled once per loop iteration. Same
1207            // batch as `client.connections` so dashboards see them update in
1208            // lock-step.
1209            gauge!(
1210                "process.uptime_seconds",
1211                self.started_at.elapsed().as_secs() as usize
1212            );
1213            // `server.live` flips to 0 once a graceful shutdown is requested,
1214            // matching Envoy's `server.live` semantics. L4 health checks
1215            // (HAProxy / cloud LBs) can poll this gauge to drain a worker
1216            // before the OS-level termination signal lands.
1217            gauge!(
1218                "server.live",
1219                if self.shutting_down.is_some() { 0 } else { 1 }
1220            );
1221            METRICS.with(|metrics| {
1222                (*metrics.borrow_mut()).send_data();
1223            });
1224
1225            if self.shutting_down.is_some() && self.shut_down_sessions() {
1226                return;
1227            }
1228        }
1229    }
1230
1231    fn check_for_poll_errors(&mut self) {
1232        if self.current_poll_errors >= self.max_poll_errors {
1233            error!(
1234                "Something is going very wrong. Last {} poll() calls failed, crashing..",
1235                self.current_poll_errors
1236            );
1237            panic!(
1238                "poll() calls failed {} times in a row",
1239                self.current_poll_errors
1240            );
1241        }
1242    }
1243
1244    fn reset_loop_time_and_get_timeout(&mut self) -> Option<Duration> {
1245        let now = Instant::now();
1246        time!(
1247            names::event_loop::EVENT_LOOP_TIME,
1248            (now - self.loop_start).as_millis()
1249        );
1250
1251        let mut timeout = match self.should_poll_at.as_ref() {
1252            None => self.poll_timeout,
1253            Some(i) => {
1254                if *i <= now {
1255                    self.poll_timeout
1256                } else {
1257                    let dur = *i - now;
1258                    match self.poll_timeout {
1259                        None => Some(dur),
1260                        Some(t) => {
1261                            if t < dur {
1262                                Some(t)
1263                            } else {
1264                                Some(dur)
1265                            }
1266                        }
1267                    }
1268                }
1269            }
1270        };
1271
1272        if self.shutting_down.is_some() {
1273            let shutdown_tick = Duration::from_millis(100);
1274            timeout = match timeout {
1275                None => Some(shutdown_tick),
1276                Some(current) => Some(current.min(shutdown_tick)),
1277            };
1278        }
1279
1280        self.loop_start = now;
1281        timeout
1282    }
1283
1284    /// Returns true if hardstop
1285    fn read_channel_messages_and_notify(&mut self) -> bool {
1286        if !self.channel.readiness().is_readable() {
1287            return false;
1288        }
1289
1290        if let Err(e) = self.channel.readable() {
1291            error!("error reading from channel: {:?}", e);
1292        }
1293
1294        loop {
1295            let request = self.channel.read_message();
1296            debug!("Received request {:?}", request);
1297            match request {
1298                Ok(request) => match request.content.request_type {
1299                    Some(RequestType::HardStop(_)) => {
1300                        let req_id = request.id.clone();
1301                        self.notify(request);
1302                        if let Err(e) = self.channel.write_message(&WorkerResponse::ok(req_id)) {
1303                            error!("Could not send ok response to the main process: {}", e);
1304                        }
1305                        if let Err(e) = self.channel.run() {
1306                            error!("Error while running the server channel: {}", e);
1307                        }
1308                        return true;
1309                    }
1310                    Some(RequestType::SoftStop(_)) => {
1311                        self.shutting_down = Some(request.id.clone());
1312                        self.last_sessions_len = self.sessions.borrow().slab.len();
1313                        self.notify(request);
1314                    }
1315                    Some(RequestType::ReturnListenSockets(_)) => {
1316                        info!("received ReturnListenSockets order");
1317                        match self.return_listen_sockets() {
1318                            Ok(_) => push_queue(WorkerResponse::ok(request.id)),
1319                            Err(error) => push_queue(worker_response_error(
1320                                request.id,
1321                                format!("Could not send listeners on scm socket: {error:?}"),
1322                            )),
1323                        }
1324                    }
1325                    _ => self.notify(request),
1326                },
1327                // Not an error per se, occurs when there is nothing to read
1328                Err(_) => {
1329                    // if the message was too large, we grow the buffer and retry to read if possible
1330                    if (self.channel.interest & self.channel.readiness).is_readable() {
1331                        if let Err(e) = self.channel.readable() {
1332                            error!("error reading from channel: {:?}", e);
1333                        }
1334                        continue;
1335                    }
1336                    break;
1337                }
1338            }
1339        }
1340        false
1341    }
1342
1343    /// Scans all sessions that have been inactive for longer than the configured interval
1344    fn zombie_check(&mut self) {
1345        let now = Instant::now();
1346        if now - self.last_zombie_check < self.zombie_check_interval {
1347            return;
1348        }
1349        info!("zombie check");
1350        // `now` is sampled this iteration and we only get here past the
1351        // interval gate, so the check timestamp advances monotonically.
1352        debug_assert!(
1353            now >= self.last_zombie_check,
1354            "zombie-check timestamp must never move backwards"
1355        );
1356        self.last_zombie_check = now;
1357
1358        let mut zombie_tokens = HashSet::new();
1359
1360        // find the zombie sessions
1361        for (_index, session) in self
1362            .sessions
1363            .borrow_mut()
1364            .slab
1365            .iter_mut()
1366            .filter(|(_, c)| now - c.borrow().last_event() > self.zombie_check_interval)
1367        {
1368            let session_token = session.borrow().frontend_token();
1369            if !zombie_tokens.contains(&session_token) {
1370                session.borrow().print_session();
1371                zombie_tokens.insert(session_token);
1372            }
1373        }
1374
1375        // Listen/system sessions report `Instant::now()` as their last event,
1376        // so `now - last_event` is ~0 and never exceeds the interval: a
1377        // listener can never be collected as a zombie. Assert the set is free
1378        // of listen protocols before we reap it.
1379        debug_assert!(
1380            !self.sessions.borrow().slab.iter().any(|(_, session)| {
1381                let s = session.borrow();
1382                zombie_tokens.contains(&s.frontend_token())
1383                    && matches!(
1384                        s.protocol(),
1385                        Protocol::HTTPListen | Protocol::HTTPSListen | Protocol::TCPListen
1386                    )
1387            }),
1388            "zombie reaping must never target a listener session"
1389        );
1390
1391        let zombie_count = zombie_tokens.len() as i64;
1392        count!(names::misc::ZOMBIES, zombie_count);
1393
1394        let remaining_count = self.shut_down_sessions_by_frontend_tokens(zombie_tokens);
1395        info!(
1396            "removing {} zombies ({} remaining entries after close)",
1397            zombie_count, remaining_count
1398        );
1399    }
1400
1401    /// Calls close on targeted sessions, yields the number of entries in the slab
1402    /// that were not properly removed
1403    fn shut_down_sessions_by_frontend_tokens(&self, tokens: HashSet<Token>) -> usize {
1404        if tokens.is_empty() {
1405            return 0;
1406        }
1407
1408        // close the sessions associated with the tokens
1409        for token in &tokens {
1410            if self.sessions.borrow().slab.contains(token.0) {
1411                let slab_before = self.sessions.borrow().slab.len();
1412                let session = { self.sessions.borrow_mut().slab.remove(token.0) };
1413                session.borrow_mut().close();
1414                self.sessions.borrow_mut().decr();
1415                // The removed token is truly gone afterwards. The slab may shrink
1416                // by MORE than one: `close()` also frees the session's backend
1417                // slab slot(s) (the multi-token pattern), so assert it shrank by
1418                // at least the frontend slot we just removed, not exactly one.
1419                debug_assert!(
1420                    !self.sessions.borrow().slab.contains(token.0),
1421                    "removed token must be absent from the slab"
1422                );
1423                debug_assert!(
1424                    self.sessions.borrow().slab.len() < slab_before,
1425                    "removing a present session must free at least its own slab slot"
1426                );
1427            }
1428        }
1429
1430        // find the entries of closed sessions in the session manager (they should not be there)
1431        let mut dangling_entries = HashSet::new();
1432        for (entry_key, session) in &self.sessions.borrow().slab {
1433            if tokens.contains(&session.borrow().frontend_token()) {
1434                dangling_entries.insert(entry_key);
1435            }
1436        }
1437
1438        // remove these from the session manager
1439        let mut dangling_entries_count = 0;
1440        for entry_key in dangling_entries {
1441            let mut sessions = self.sessions.borrow_mut();
1442            if sessions.slab.contains(entry_key) {
1443                sessions.slab.remove(entry_key);
1444                dangling_entries_count += 1;
1445            }
1446        }
1447        // Postcondition: no surviving slab entry still references any of the
1448        // closed frontend tokens — both the direct remove and the dangling
1449        // sweep together leave the slab clean of these sessions.
1450        debug_assert!(
1451            !self
1452                .sessions
1453                .borrow()
1454                .slab
1455                .iter()
1456                .any(|(_, session)| tokens.contains(&session.borrow().frontend_token())),
1457            "no slab entry may reference a closed frontend token after teardown"
1458        );
1459        dangling_entries_count
1460    }
1461
1462    /// Order sessions to shut down, check that they are all down
1463    fn shut_down_sessions(&mut self) -> bool {
1464        let sessions_count = self.sessions.borrow().slab.len();
1465        let mut sessions_to_shut_down = HashSet::new();
1466
1467        for (_key, session) in &self.sessions.borrow().slab {
1468            let mut session = session.borrow_mut();
1469            if session.shutting_down() {
1470                debug!(
1471                    "Server killing session from shutting_down: token={:?}, protocol={:?}",
1472                    session.frontend_token(),
1473                    session.protocol()
1474                );
1475                sessions_to_shut_down.insert(Token(session.frontend_token().0));
1476            }
1477        }
1478        let _ = self.shut_down_sessions_by_frontend_tokens(sessions_to_shut_down);
1479
1480        let new_sessions_count = self.sessions.borrow().slab.len();
1481
1482        if new_sessions_count < sessions_count {
1483            let now = Instant::now();
1484            if let Some(last) = self.last_shutting_down_message
1485                && (now - last) > Duration::from_secs(5)
1486            {
1487                info!(
1488                    "closed {} sessions, {} sessions left, base_sessions_count = {}",
1489                    sessions_count - new_sessions_count,
1490                    new_sessions_count,
1491                    self.base_sessions_count
1492                );
1493            }
1494            self.last_shutting_down_message = Some(now);
1495        }
1496
1497        if new_sessions_count <= self.base_sessions_count {
1498            info!("last session stopped, shutting down!");
1499            if let Err(e) = self.channel.run() {
1500                error!("Error while running the server channel: {}", e);
1501            }
1502            // self.block_channel();
1503            let id = self
1504                .shutting_down
1505                .take()
1506                .expect("should have shut down correctly"); // panicking here makes sense actually
1507
1508            debug!("Responding OK to main process for request {}", id);
1509
1510            let proxy_response = WorkerResponse::ok(id);
1511            if let Err(e) = self.channel.write_message(&proxy_response) {
1512                error!("Could not write response to the main process: {}", e);
1513            }
1514            if let Err(e) = self.channel.run() {
1515                error!("Error while running the server channel: {}", e);
1516            }
1517            return true;
1518        }
1519
1520        if new_sessions_count < self.last_sessions_len {
1521            info!(
1522                "shutting down, {} slab elements remaining (base: {})",
1523                new_sessions_count - self.base_sessions_count,
1524                self.base_sessions_count
1525            );
1526            self.last_sessions_len = new_sessions_count;
1527        }
1528
1529        false
1530    }
1531
1532    fn kill_session(&self, session: Rc<RefCell<dyn ProxySession>>) {
1533        let token = session.borrow().frontend_token();
1534        let _ = self.shut_down_sessions_by_frontend_tokens(HashSet::from([token]));
1535    }
1536
1537    fn send_queue(&mut self) {
1538        if self.channel.readiness.is_writable() {
1539            QUEUE.with(|q| {
1540                let mut queue = q.borrow_mut();
1541                loop {
1542                    if let Some(resp) = queue.pop_front() {
1543                        debug!("Sending response {:?}", resp);
1544                        if let Err(e) = self.channel.write_message(&resp) {
1545                            error!("Could not write message {} on the channel: {}", resp, e);
1546                            queue.push_front(resp);
1547                        }
1548                    }
1549
1550                    if self.channel.back_buf.available_data() > 0
1551                        && let Err(e) = self.channel.writable()
1552                    {
1553                        error!("error writing to channel: {:?}", e);
1554                    }
1555
1556                    if !self.channel.readiness.is_writable() {
1557                        break;
1558                    }
1559
1560                    if self.channel.back_buf.available_data() == 0 && queue.is_empty() {
1561                        break;
1562                    }
1563                }
1564            });
1565        }
1566    }
1567
1568    fn notify(&mut self, message: WorkerRequest) {
1569        // Polled lease-expiry janitor: SetMetricDetail leases self-expire after
1570        // their TTL so a crashed `sozu top` cannot permanently elevate metrics
1571        // cardinality. The janitor runs at most every LEASE_TICK_INTERVAL,
1572        // gated by `lease_tick_due` so the hot path of `notify` doesn't pay
1573        // the HashMap walk on every iteration. Single-threaded worker, so
1574        // `borrow_mut` is safe here.
1575        let now = std::time::Instant::now();
1576        // Capture (previous, effective) before releasing the borrow so we
1577        // can emit an Event afterwards. Holding `METRICS.borrow_mut`
1578        // across `push_event` would re-enter the same thread-local from
1579        // inside `QUEUE.with` (safe but conceptually noisy); the
1580        // two-step split keeps the borrow scopes minimal.
1581        let lease_tick_transition = METRICS.with(|metrics| {
1582            let mut m = metrics.borrow_mut();
1583            if !m.lease_tick_due(now) {
1584                return None;
1585            }
1586            let previous = m.lease_tick(now)?;
1587            let effective = m.detail_effective();
1588            Some((previous, effective))
1589        });
1590        if let Some((previous, effective)) = lease_tick_transition {
1591            // The janitor retired one or more leases AND the effective
1592            // level moved. Surface the worker-local transition as an
1593            // Event so the master folds it into the audit log (closes
1594            // the gap where TUI-crashed lease expiry was previously
1595            // silent). `client_id` is `None` because the janitor may
1596            // have retired multiple leases at once.
1597            push_metric_detail_transition(previous, effective, "lease_tick_expired", None);
1598        }
1599        match &message.content.request_type {
1600            Some(RequestType::ConfigureMetrics(configuration)) => {
1601                match MetricsConfiguration::try_from(*configuration) {
1602                    Ok(metrics_config) => {
1603                        METRICS.with(|metrics| {
1604                            (*metrics.borrow_mut()).configure(&metrics_config);
1605                            push_queue(WorkerResponse::ok(message.id));
1606                        });
1607                    }
1608                    Err(e) => {
1609                        error!("Error configuring metrics: {}", e);
1610                        push_queue(WorkerResponse::error(message.id, e));
1611                    }
1612                }
1613                return;
1614            }
1615            Some(RequestType::QueryMetrics(query_metrics_options)) => {
1616                METRICS.with(|metrics| {
1617                    match (*metrics.borrow_mut()).query(query_metrics_options) {
1618                        Ok(c) => push_queue(WorkerResponse::ok_with_content(message.id, c)),
1619                        Err(e) => {
1620                            error!("Error querying metrics: {}", e);
1621                            push_queue(WorkerResponse::error(message.id, e))
1622                        }
1623                    }
1624                });
1625                return;
1626            }
1627            // Runtime cardinality lease verb — apply, renew, or clear a lease
1628            // on this worker's `Aggregator`. The lease bumps `effective` to
1629            // `max(configured, max(active leases))`; expiry runs on the polled
1630            // janitor below. Master-side aggregation into `MetricDetailStatus`
1631            // lands in a follow-up; for now the worker acks with a bare OK so
1632            // the existing `worker_request` fan-out path can collect.
1633            Some(RequestType::SetMetricDetail(req)) => {
1634                // Master populates the peer binding from the connecting
1635                // `ClientSession` before fan-out (`bin/src/command/
1636                // requests.rs::worker_request`). A pre-binding caller or a
1637                // platform without `SO_PEERCRED` yields `PeerBinding::default()`
1638                // — clears against that lease are accepted from anyone, per
1639                // the proto contract on `SetMetricDetail.peer_pid`.
1640                let presented_binding = crate::metrics::PeerBinding {
1641                    pid: req.peer_pid,
1642                    // Master sends Crockford-base32 ULIDs (`Ulid::to_string`);
1643                    // accept those, with a fallback hex parse for callers that
1644                    // happen to send `0x…` form. A failed parse degrades to
1645                    // `None` — the lease store treats that as "binding
1646                    // unknown" per the proto contract.
1647                    session_ulid: req.peer_session_ulid.as_deref().and_then(|s| {
1648                        rusty_ulid::Ulid::from_str(s)
1649                            .map(u128::from)
1650                            .ok()
1651                            .or_else(|| u128::from_str_radix(s.trim_start_matches("0x"), 16).ok())
1652                    }),
1653                };
1654                if req.clear.unwrap_or(false) {
1655                    // Defense-in-depth: the master pre-validates `client_id`
1656                    // length at the dispatch site, but worker IPC is not
1657                    // master-only — fuzz harnesses, serial_test-flagged
1658                    // integration tests, and future internal callers can
1659                    // issue an oversized clear directly. Mirror the apply
1660                    // path's `ClientIdTooLong` arm so an unbounded HashMap
1661                    // lookup is never driven by an operator-supplied string
1662                    // here either. The reason string echoes the byte length
1663                    // but not the operator bytes themselves (symmetric with
1664                    // the audit-column-smuggling guard on the apply path).
1665                    if req.client_id.len() > crate::metrics::LEASE_CLIENT_ID_MAX_BYTES {
1666                        let msg = format!(
1667                            "SetMetricDetail: clear client_id length {} exceeds {} bytes",
1668                            req.client_id.len(),
1669                            crate::metrics::LEASE_CLIENT_ID_MAX_BYTES,
1670                        );
1671                        error!("{}", msg);
1672                        push_queue(WorkerResponse::error(message.id.clone(), msg));
1673                        return;
1674                    }
1675                    // Capture transition fields + post-clear snapshot
1676                    // before releasing the borrow so we can emit an
1677                    // Event after AND build the WorkerMetricDetailStatus
1678                    // payload that the master folds into
1679                    // `MetricDetailStatus.workers[<worker_id>]`. Without
1680                    // this payload the master used its own view as a
1681                    // stand-in for the worker's per-aggregator state.
1682                    let (outcome, effective_after, configured_after, lease_count_after) = METRICS
1683                        .with(|metrics| {
1684                            let mut m = metrics.borrow_mut();
1685                            let outcome = m.lease_clear(&req.client_id, presented_binding);
1686                            (
1687                                outcome,
1688                                m.detail_effective(),
1689                                m.detail_configured(),
1690                                m.lease_count(),
1691                            )
1692                        });
1693                    match outcome {
1694                        crate::metrics::LeaseClearOutcome::Cleared { previous_effective } => {
1695                            push_metric_detail_transition(
1696                                previous_effective,
1697                                effective_after,
1698                                "lease_clear",
1699                                Some(req.client_id.clone()),
1700                            );
1701                            push_queue(WorkerResponse::ok_with_content(
1702                                message.id.clone(),
1703                                worker_metric_detail_status_content(
1704                                    configured_after,
1705                                    effective_after,
1706                                    previous_effective,
1707                                    lease_count_after,
1708                                ),
1709                            ));
1710                        }
1711                        crate::metrics::LeaseClearOutcome::NotFound => {
1712                            // Silent no-op: no lease existed for that
1713                            // id. The worker's state is unchanged so
1714                            // previous_effective == effective.
1715                            push_queue(WorkerResponse::ok_with_content(
1716                                message.id.clone(),
1717                                worker_metric_detail_status_content(
1718                                    configured_after,
1719                                    effective_after,
1720                                    effective_after,
1721                                    lease_count_after,
1722                                ),
1723                            ));
1724                        }
1725                        crate::metrics::LeaseClearOutcome::Unauthorized => {
1726                            // Do NOT echo `req.client_id` here: the operator-
1727                            // supplied bytes flow back through the master's
1728                            // worker→reason aggregation into the audit line's
1729                            // `reason=` column, which is sanitised for control
1730                            // bytes only. The dedicated `lease_id=` audit
1731                            // column already carries the operator string
1732                            // through `sanitize_for_audit_kv`, so re-embedding
1733                            // it here would let a value containing `,` or `=`
1734                            // forge a sibling KV pair against SIEM consumers
1735                            // that split on `, key=value`.
1736                            let msg = "SetMetricDetail: clear refused (peer \
1737                                 binding does not match the apply-time owner)"
1738                                .to_owned();
1739                            error!("{}", msg);
1740                            push_queue(WorkerResponse::error(message.id.clone(), msg));
1741                        }
1742                    }
1743                    return;
1744                }
1745                let detail_proto = match req.detail {
1746                    Some(d) => d,
1747                    None => {
1748                        // Operator-supplied `client_id` is intentionally
1749                        // omitted from the reason string: the dedicated
1750                        // `lease_id=` audit column carries it through the
1751                        // strict KV sanitiser. See the matching comment on
1752                        // the `Unauthorized` arm above for the column-
1753                        // smuggling rationale.
1754                        let msg = "SetMetricDetail without `detail` and without `clear`".to_owned();
1755                        error!("{}", msg);
1756                        push_queue(WorkerResponse::error(message.id.clone(), msg));
1757                        return;
1758                    }
1759                };
1760                let detail_enum = match MetricDetail::try_from(detail_proto) {
1761                    Ok(d) => d,
1762                    Err(e) => {
1763                        let msg =
1764                            format!("SetMetricDetail: invalid MetricDetail variant {detail_proto}");
1765                        error!("{}: {}", msg, e);
1766                        push_queue(WorkerResponse::error(message.id.clone(), msg));
1767                        return;
1768                    }
1769                };
1770                let level = MetricDetailLevel::from(detail_enum);
1771                // Bound the worst case BEFORE we touch the aggregator: the
1772                // proto contract on `SetMetricDetail.ttl_seconds` says the
1773                // worker rejects values larger than `LEASE_TTL_MAX` so a
1774                // stuck operator-side renewer (or a buggy third-party client)
1775                // cannot lock the worker into elevated cardinality. The
1776                // `Aggregator::lease_apply` clamp is still in place as a
1777                // defence-in-depth net for code paths that bypass this
1778                // dispatch (proto fuzzing, future internal callers).
1779                if let Some(t) = req.ttl_seconds
1780                    && u64::from(t) > crate::metrics::LEASE_TTL_MAX.as_secs()
1781                {
1782                    let msg = format!(
1783                        "SetMetricDetail: ttl_seconds={t} exceeds LEASE_TTL_MAX={}",
1784                        crate::metrics::LEASE_TTL_MAX.as_secs()
1785                    );
1786                    error!("{}", msg);
1787                    push_queue(WorkerResponse::error(message.id.clone(), msg));
1788                    return;
1789                }
1790                let ttl_seconds = req.ttl_seconds.filter(|&t| t > 0).unwrap_or_else(|| {
1791                    // The default fits in a u32 by construction
1792                    // (LEASE_TTL_DEFAULT = 60 s); the lossy `as u32` cast
1793                    // is replaced with a checked conversion so any
1794                    // future tweak past `u32::MAX` seconds (≈ 136 years)
1795                    // can't silently truncate. Falls through to 60 s on
1796                    // the theoretical overflow path.
1797                    u32::try_from(crate::metrics::LEASE_TTL_DEFAULT.as_secs()).unwrap_or(60)
1798                });
1799                let ttl = std::time::Duration::from_secs(ttl_seconds.into());
1800                let (outcome, configured_after, lease_count_after) = METRICS.with(|metrics| {
1801                    let mut m = metrics.borrow_mut();
1802                    let outcome =
1803                        m.lease_apply(req.client_id.clone(), level, ttl, presented_binding);
1804                    (outcome, m.detail_configured(), m.lease_count())
1805                });
1806                match outcome {
1807                    crate::metrics::LeaseApplyOutcome::Applied {
1808                        previous_effective,
1809                        new_effective,
1810                    } => {
1811                        push_metric_detail_transition(
1812                            previous_effective,
1813                            new_effective,
1814                            "lease_apply",
1815                            Some(req.client_id.clone()),
1816                        );
1817                        push_queue(WorkerResponse::ok_with_content(
1818                            message.id.clone(),
1819                            worker_metric_detail_status_content(
1820                                configured_after,
1821                                new_effective,
1822                                previous_effective,
1823                                lease_count_after,
1824                            ),
1825                        ));
1826                    }
1827                    crate::metrics::LeaseApplyOutcome::ClientIdTooLong => {
1828                        let msg = format!(
1829                            "SetMetricDetail: client_id length {} exceeds {} bytes",
1830                            req.client_id.len(),
1831                            crate::metrics::LEASE_CLIENT_ID_MAX_BYTES,
1832                        );
1833                        error!("{}", msg);
1834                        push_queue(WorkerResponse::error(message.id.clone(), msg));
1835                    }
1836                    crate::metrics::LeaseApplyOutcome::TableFull => {
1837                        // Same audit-column-smuggling guard as the
1838                        // `Unauthorized` and missing-detail arms: the
1839                        // operator-supplied `client_id` is already rendered
1840                        // safely through the strict KV sanitiser in the
1841                        // audit envelope's `lease_id=` column, so we keep
1842                        // it out of the reason string.
1843                        let msg = format!(
1844                            "SetMetricDetail: lease table at capacity ({} entries); reject new \
1845                             apply — operators must retry after an active lease expires or is \
1846                             cleared",
1847                            crate::metrics::LEASE_TABLE_CAP,
1848                        );
1849                        error!("{}", msg);
1850                        push_queue(WorkerResponse::error(message.id.clone(), msg));
1851                    }
1852                    crate::metrics::LeaseApplyOutcome::TtlOutOfRange => {
1853                        // Unreachable in the normal flow: the dispatch-time
1854                        // gate above already rejected ttl > LEASE_TTL_MAX.
1855                        // Surface explicitly so any future bypass (proto
1856                        // fuzzing, internal callers) fails loud rather
1857                        // than silently capping the lessor's intent.
1858                        let msg = format!(
1859                            "SetMetricDetail: ttl exceeds LEASE_TTL_MAX={} (internal contract \
1860                             violation: dispatch gate should have rejected)",
1861                            crate::metrics::LEASE_TTL_MAX.as_secs(),
1862                        );
1863                        error!("{}", msg);
1864                        push_queue(WorkerResponse::error(message.id.clone(), msg));
1865                    }
1866                    crate::metrics::LeaseApplyOutcome::Unauthorized => {
1867                        // A renewal arrived against an existing lease whose
1868                        // apply-time peer binding does not match the
1869                        // presented one. The `client_id` is intentionally
1870                        // omitted from the error string — the audit-log
1871                        // row already carries it in the dedicated
1872                        // `lease_id` column, and echoing it here would
1873                        // route operator-controlled bytes through the
1874                        // freeform reason field.
1875                        let msg = "SetMetricDetail: renewal refused (peer binding does not \
1876                                   match the apply-time owner)"
1877                            .to_owned();
1878                        error!("{}", msg);
1879                        push_queue(WorkerResponse::error(message.id.clone(), msg));
1880                    }
1881                }
1882                return;
1883            }
1884            Some(RequestType::Logging(logging_filter)) => {
1885                info!(
1886                    "{} changing logging filter to {}",
1887                    message.id, logging_filter
1888                );
1889                // there should not be any errors as it was already parsed by the main process
1890                let (directives, _errors) = logging::parse_logging_spec(logging_filter);
1891                logging::LOGGER.with(|logger| {
1892                    logger.borrow_mut().set_directives(directives);
1893                });
1894                push_queue(WorkerResponse::ok(message.id));
1895                return;
1896            }
1897            Some(RequestType::QueryClustersHashes(_)) => {
1898                push_queue(WorkerResponse::ok_with_content(
1899                    message.id.clone(),
1900                    ContentType::ClusterHashes(ClusterHashes {
1901                        map: self.config_state.hash_state(),
1902                    })
1903                    .into(),
1904                ));
1905                return;
1906            }
1907            Some(RequestType::QueryClusterById(cluster_id)) => {
1908                push_queue(WorkerResponse::ok_with_content(
1909                    message.id.clone(),
1910                    ContentType::Clusters(ClusterInformations {
1911                        vec: self
1912                            .config_state
1913                            .cluster_state(cluster_id)
1914                            .map_or(vec![], |ci| vec![ci]),
1915                    })
1916                    .into(),
1917                ));
1918            }
1919            Some(RequestType::SetMaxConnectionsPerIp(limit)) => {
1920                let mut sessions = self.sessions.borrow_mut();
1921                let previous = sessions.max_connections_per_ip;
1922                sessions.max_connections_per_ip = *limit;
1923                // Disabling the feature on the fly should not leave
1924                // stale `(cluster, ip)` entries behind: drain the
1925                // bookkeeping so a re-enable starts from a clean slate
1926                // and `cluster_ip_at_limit` does not consult dead state.
1927                if *limit == 0 {
1928                    sessions.clear_cluster_ip_tracking();
1929                }
1930                info!(
1931                    "{} updated global max_connections_per_ip from {} to {}",
1932                    message.id, previous, limit
1933                );
1934                push_queue(WorkerResponse::ok(message.id));
1935                return;
1936            }
1937            Some(RequestType::QueryMaxConnectionsPerIp(_)) => {
1938                let limit = self.sessions.borrow().max_connections_per_ip;
1939                push_queue(WorkerResponse::ok_with_content(
1940                    message.id,
1941                    ContentType::MaxConnectionsPerIpLimit(
1942                        sozu_command::proto::command::MaxConnectionsPerIpLimit { limit },
1943                    )
1944                    .into(),
1945                ));
1946                return;
1947            }
1948            Some(RequestType::QueryClustersByDomain(domain)) => {
1949                let cluster_ids = self
1950                    .config_state
1951                    .get_cluster_ids_by_domain(domain.hostname.clone(), domain.path.clone());
1952                let vec = cluster_ids
1953                    .iter()
1954                    .filter_map(|cluster_id| self.config_state.cluster_state(cluster_id))
1955                    .collect();
1956
1957                push_queue(WorkerResponse::ok_with_content(
1958                    message.id.clone(),
1959                    ContentType::Clusters(ClusterInformations { vec }).into(),
1960                ));
1961                return;
1962            }
1963            Some(RequestType::QueryCertificatesFromWorkers(filters))
1964                if filters.fingerprint.is_some() =>
1965            {
1966                let certs = self.config_state.get_certificates(filters.clone());
1967                let response = if !certs.is_empty() {
1968                    WorkerResponse::ok_with_content(
1969                        message.id.clone(),
1970                        ContentType::CertificatesWithFingerprints(CertificatesWithFingerprints {
1971                            certs,
1972                        })
1973                        .into(),
1974                    )
1975                } else {
1976                    worker_response_error(
1977                        message.id.clone(),
1978                        "Could not find certificate for this fingerprint",
1979                    )
1980                };
1981                push_queue(response);
1982                return;
1983            }
1984            // if all certificates are queried, or filtered by domain name,
1985            // the request will be handled by the https proxy
1986            _other_request => {}
1987        }
1988        self.notify_proxys(message);
1989    }
1990
1991    pub fn notify_proxys(&mut self, request: WorkerRequest) {
1992        if let Err(e) = self.config_state.dispatch(&request.content) {
1993            error!("Could not execute order on config state: {}", e);
1994        }
1995
1996        let req_id = request.id.clone();
1997
1998        match request.content.request_type {
1999            Some(RequestType::AddCluster(ref cluster)) => {
2000                // Mirror the master-side ConfigState::add_cluster check so
2001                // off-channel paths (TOML reload, SaveState/LoadState, direct
2002                // API) that smuggle a malformed `cluster.health_check` cannot
2003                // arm the worker's BackendList::set_health_check_config with
2004                // a CRLF/NUL/C0 URI or zero thresholds. The SetHealthCheck
2005                // handler below already runs the same validation; this is the
2006                // AddCluster mirror.
2007                if let Some(hc) = cluster.health_check.as_ref()
2008                    && let Err(reason) = sozu_command::config::validate_health_check_config(hc)
2009                {
2010                    push_queue(worker_response_error(req_id, reason));
2011                    return;
2012                }
2013                self.add_cluster(cluster);
2014                // Re-arm the metric drain tombstone in case this cluster id
2015                // was previously removed — without this the drain would
2016                // continue dropping every emission for the resurrected
2017                // cluster. Idempotent on a fresh id.
2018                METRICS.with(|metrics| {
2019                    (*metrics.borrow_mut()).add_cluster(&cluster.cluster_id);
2020                });
2021                //not returning because the message must still be handled by each proxy
2022            }
2023            Some(RequestType::RemoveCluster(ref cluster_id)) => {
2024                self.remove_health_check_state(cluster_id);
2025                METRICS.with(|metrics| {
2026                    (*metrics.borrow_mut()).remove_cluster(cluster_id);
2027                });
2028                //not returning because the message must still be handled by each proxy
2029            }
2030            Some(RequestType::SetHealthCheck(ref set)) => {
2031                if let Err(reason) = sozu_command::config::validate_health_check_config(&set.config)
2032                {
2033                    push_queue(worker_response_error(req_id, reason));
2034                    return;
2035                }
2036                self.backends
2037                    .borrow_mut()
2038                    .set_health_check_config(&set.cluster_id, Some(set.config.to_owned()));
2039                push_queue(WorkerResponse::ok(req_id));
2040                return;
2041            }
2042            Some(RequestType::RemoveHealthCheck(ref cluster_id)) => {
2043                self.remove_health_check_state(cluster_id);
2044                push_queue(WorkerResponse::ok(req_id));
2045                return;
2046            }
2047            Some(RequestType::AddBackend(ref backend)) => {
2048                push_queue(self.add_backend(&req_id, backend));
2049                return;
2050            }
2051            Some(RequestType::RemoveBackend(ref remove_backend)) => {
2052                push_queue(self.remove_backend(&req_id, remove_backend));
2053                return;
2054            }
2055            _ => {}
2056        };
2057
2058        let proxy_destinations = request.content.get_destinations();
2059        let mut notify_response = None;
2060        if proxy_destinations.to_http_proxy {
2061            notify_response = Some(self.http.borrow_mut().notify(request.clone()));
2062        }
2063        if proxy_destinations.to_https_proxy {
2064            let http_proxy_response = self.https.borrow_mut().notify(request.clone());
2065            if http_proxy_response.is_failure() || notify_response.is_none() {
2066                notify_response = Some(http_proxy_response);
2067            }
2068        }
2069        if proxy_destinations.to_tcp_proxy {
2070            let tcp_proxy_response = self.tcp.borrow_mut().notify(request.clone());
2071            if tcp_proxy_response.is_failure() || notify_response.is_none() {
2072                notify_response = Some(tcp_proxy_response);
2073            }
2074        }
2075        if proxy_destinations.to_udp_proxy {
2076            let udp_proxy_response = self.udp.borrow_mut().notify(request.clone());
2077            if udp_proxy_response.is_failure() || notify_response.is_none() {
2078                notify_response = Some(udp_proxy_response);
2079            }
2080        }
2081        if let Some(response) = notify_response {
2082            push_queue(response);
2083        }
2084
2085        match request.content.request_type {
2086            // special case for adding listeners, because we need to register a listener
2087            Some(RequestType::AddHttpListener(listener)) => {
2088                push_queue(self.notify_add_http_listener(&req_id, listener));
2089            }
2090            Some(RequestType::AddHttpsListener(listener)) => {
2091                push_queue(self.notify_add_https_listener(&req_id, listener));
2092            }
2093            Some(RequestType::AddTcpListener(listener)) => {
2094                push_queue(self.notify_add_tcp_listener(&req_id, listener));
2095            }
2096            Some(RequestType::AddUdpListener(listener)) => {
2097                push_queue(self.notify_add_udp_listener(&req_id, listener));
2098            }
2099            Some(RequestType::UpdateHttpListener(patch)) => {
2100                push_queue(self.notify_update_http_listener(&req_id, patch));
2101            }
2102            Some(RequestType::UpdateHttpsListener(patch)) => {
2103                push_queue(self.notify_update_https_listener(&req_id, patch));
2104            }
2105            Some(RequestType::UpdateTcpListener(patch)) => {
2106                push_queue(self.notify_update_tcp_listener(&req_id, patch));
2107            }
2108            Some(RequestType::UpdateUdpListener(patch)) => {
2109                push_queue(self.notify_update_udp_listener(&req_id, patch));
2110            }
2111            Some(RequestType::RemoveListener(ref remove)) => {
2112                debug!("{} remove {:?} listener {:?}", req_id, remove.proxy, remove);
2113                // We only remove a listener that was previously added, so the
2114                // base count is at least 1 — the subtraction cannot underflow.
2115                debug_assert!(
2116                    self.base_sessions_count > 0,
2117                    "removing a listener with base_sessions_count == 0 would underflow"
2118                );
2119                self.base_sessions_count -= 1;
2120                let response = match ListenerType::try_from(remove.proxy) {
2121                    Ok(ListenerType::Http) => self.http.borrow_mut().notify(request),
2122                    Ok(ListenerType::Https) => self.https.borrow_mut().notify(request),
2123                    Ok(ListenerType::Tcp) => self.tcp.borrow_mut().notify(request),
2124                    Ok(ListenerType::Udp) => self.udp.borrow_mut().notify(request),
2125                    Err(_) => WorkerResponse::error(req_id, "Wrong variant ListenerType"),
2126                };
2127                push_queue(response);
2128            }
2129            Some(RequestType::ActivateListener(ref activate)) => {
2130                push_queue(self.notify_activate_listener(&req_id, activate));
2131            }
2132            Some(RequestType::DeactivateListener(ref deactivate)) => {
2133                push_queue(self.notify_deactivate_listener(&req_id, deactivate));
2134            }
2135            _other_request => {}
2136        };
2137    }
2138
2139    fn add_cluster(&mut self, cluster: &Cluster) {
2140        let mut backends = self.backends.borrow_mut();
2141        backends.set_load_balancing_policy_for_cluster(
2142            &cluster.cluster_id,
2143            LoadBalancingAlgorithms::try_from(cluster.load_balancing).unwrap_or_default(),
2144            cluster
2145                .load_metric
2146                .and_then(|n| LoadMetric::try_from(n).ok()),
2147        );
2148        backends.set_health_check_config(&cluster.cluster_id, cluster.health_check.to_owned());
2149        backends.set_cluster_http2(&cluster.cluster_id, cluster.http2.unwrap_or(false));
2150    }
2151
2152    fn add_backend(&mut self, req_id: &str, add_backend: &AddBackend) -> WorkerResponse {
2153        let new_backend = Backend::new(
2154            &add_backend.backend_id,
2155            add_backend.address.into(),
2156            add_backend.sticky_id.clone(),
2157            add_backend.load_balancing_parameters,
2158            add_backend.backup,
2159        );
2160        self.backends
2161            .borrow_mut()
2162            .add_backend(&add_backend.cluster_id, new_backend);
2163
2164        WorkerResponse::ok(req_id)
2165    }
2166
2167    fn remove_health_check_state(&mut self, cluster_id: &str) {
2168        self.health_checker.remove_cluster(cluster_id);
2169        self.backends
2170            .borrow_mut()
2171            .health_check_configs
2172            .remove(cluster_id);
2173    }
2174
2175    fn remove_backend(&mut self, req_id: &str, backend: &RemoveBackend) -> WorkerResponse {
2176        let address = backend.address.into();
2177        // Runtime removal is address-keyed and drops every backend at this
2178        // address (A/B test, weighted variant, dedup race). The metrics
2179        // layer is id-keyed — fan out one `remove_backend` per actually-
2180        // removed id so the two identities stay in sync. Without this the
2181        // `backend_id` field on the IPC message could name "A" while the
2182        // runtime dropped both "A" and "B" at the same address, leaving
2183        // "B"'s metrics rows orphaned forever.
2184        let removed_ids = self
2185            .backends
2186            .borrow_mut()
2187            .remove_backend(&backend.cluster_id, &address);
2188        if removed_ids.is_empty() {
2189            // Edge case: BackendList returned nothing (address never
2190            // existed in this cluster). Honour the request's stated id
2191            // anyway so a no-op request still tidies any orphan metric
2192            // row from a prior identity-drift state.
2193            METRICS.with(|metrics| {
2194                (*metrics.borrow_mut()).remove_backend(&backend.cluster_id, &backend.backend_id);
2195            });
2196        } else {
2197            METRICS.with(|metrics| {
2198                let mut metrics = metrics.borrow_mut();
2199                for id in &removed_ids {
2200                    metrics.remove_backend(&backend.cluster_id, id);
2201                }
2202            });
2203        }
2204
2205        WorkerResponse::ok(req_id)
2206    }
2207
2208    fn notify_add_http_listener(
2209        &mut self,
2210        req_id: &str,
2211        listener: HttpListenerConfig,
2212    ) -> WorkerResponse {
2213        debug!("{} add http listener {:?}", req_id, listener);
2214
2215        if self.sessions.borrow().at_capacity() {
2216            return worker_response_error(req_id, "session list is full, cannot add a listener");
2217        }
2218
2219        let mut session_manager = self.sessions.borrow_mut();
2220        // The vacant entry's key is free now and becomes the listener's token.
2221        let slab_before = session_manager.slab.len();
2222        debug_assert!(
2223            !session_manager
2224                .slab
2225                .contains(session_manager.slab.vacant_key()),
2226            "the next vacant slab key must be free before insertion"
2227        );
2228        let entry = session_manager.slab.vacant_entry();
2229        let token = Token(entry.key());
2230
2231        match self.http.borrow_mut().add_listener(listener, token) {
2232            Ok(_token) => {
2233                entry.insert(Rc::new(RefCell::new(ListenSession {
2234                    protocol: Protocol::HTTPListen,
2235                })));
2236                // The listener session occupies exactly the token's slab key,
2237                // and the slab grew by exactly one slot.
2238                debug_assert!(
2239                    session_manager.slab.contains(token.0),
2240                    "listener insert must occupy the token's slab key"
2241                );
2242                debug_assert_eq!(
2243                    session_manager.slab.len(),
2244                    slab_before + 1,
2245                    "adding a listener must occupy exactly one slab slot"
2246                );
2247                self.base_sessions_count += 1;
2248                WorkerResponse::ok(req_id)
2249            }
2250            Err(e) => worker_response_error(req_id, format!("Could not add HTTP listener: {e}")),
2251        }
2252    }
2253
2254    fn notify_add_https_listener(
2255        &mut self,
2256        req_id: &str,
2257        listener: HttpsListenerConfig,
2258    ) -> WorkerResponse {
2259        debug!("{} add https listener {:?}", req_id, listener);
2260
2261        if self.sessions.borrow().at_capacity() {
2262            return worker_response_error(req_id, "session list is full, cannot add a listener");
2263        }
2264
2265        let mut session_manager = self.sessions.borrow_mut();
2266        let slab_before = session_manager.slab.len();
2267        debug_assert!(
2268            !session_manager
2269                .slab
2270                .contains(session_manager.slab.vacant_key()),
2271            "the next vacant slab key must be free before insertion"
2272        );
2273        let entry = session_manager.slab.vacant_entry();
2274        let token = Token(entry.key());
2275
2276        match self
2277            .https
2278            .borrow_mut()
2279            .add_listener(listener.clone(), token)
2280        {
2281            Ok(_token) => {
2282                entry.insert(Rc::new(RefCell::new(ListenSession {
2283                    protocol: Protocol::HTTPSListen,
2284                })));
2285                debug_assert!(
2286                    session_manager.slab.contains(token.0),
2287                    "listener insert must occupy the token's slab key"
2288                );
2289                debug_assert_eq!(
2290                    session_manager.slab.len(),
2291                    slab_before + 1,
2292                    "adding a listener must occupy exactly one slab slot"
2293                );
2294                self.base_sessions_count += 1;
2295                WorkerResponse::ok(req_id)
2296            }
2297            Err(e) => worker_response_error(req_id, format!("Could not add HTTPS listener: {e}")),
2298        }
2299    }
2300
2301    fn notify_add_tcp_listener(
2302        &mut self,
2303        req_id: &str,
2304        listener: CommandTcpListener,
2305    ) -> WorkerResponse {
2306        debug!("{} add tcp listener {:?}", req_id, listener);
2307
2308        if self.sessions.borrow().at_capacity() {
2309            return worker_response_error(req_id, "session list is full, cannot add a listener");
2310        }
2311
2312        let mut session_manager = self.sessions.borrow_mut();
2313        let slab_before = session_manager.slab.len();
2314        debug_assert!(
2315            !session_manager
2316                .slab
2317                .contains(session_manager.slab.vacant_key()),
2318            "the next vacant slab key must be free before insertion"
2319        );
2320        let entry = session_manager.slab.vacant_entry();
2321        let token = Token(entry.key());
2322
2323        match self.tcp.borrow_mut().add_listener(listener, token) {
2324            Ok(_token) => {
2325                entry.insert(Rc::new(RefCell::new(ListenSession {
2326                    protocol: Protocol::TCPListen,
2327                })));
2328                debug_assert!(
2329                    session_manager.slab.contains(token.0),
2330                    "listener insert must occupy the token's slab key"
2331                );
2332                debug_assert_eq!(
2333                    session_manager.slab.len(),
2334                    slab_before + 1,
2335                    "adding a listener must occupy exactly one slab slot"
2336                );
2337                self.base_sessions_count += 1;
2338                WorkerResponse::ok(req_id)
2339            }
2340            Err(e) => worker_response_error(req_id, format!("Could not add TCP listener: {e}")),
2341        }
2342    }
2343
2344    fn notify_add_udp_listener(
2345        &mut self,
2346        req_id: &str,
2347        listener: CommandUdpListener,
2348    ) -> WorkerResponse {
2349        debug!("{} add udp listener {:?}", req_id, listener);
2350
2351        if self.sessions.borrow().at_capacity() {
2352            return worker_response_error(req_id, "session list is full, cannot add a listener");
2353        }
2354
2355        let mut session_manager = self.sessions.borrow_mut();
2356        let entry = session_manager.slab.vacant_entry();
2357        let token = Token(entry.key());
2358
2359        match self.udp.borrow_mut().add_listener(listener, token) {
2360            Ok(_token) => {
2361                entry.insert(Rc::new(RefCell::new(ListenSession {
2362                    protocol: Protocol::UDPListen,
2363                })));
2364                self.base_sessions_count += 1;
2365                WorkerResponse::ok(req_id)
2366            }
2367            Err(e) => worker_response_error(req_id, format!("Could not add UDP listener: {e}")),
2368        }
2369    }
2370
2371    fn notify_update_udp_listener(
2372        &mut self,
2373        req_id: &str,
2374        patch: UpdateUdpListenerConfig,
2375    ) -> WorkerResponse {
2376        debug!("{} update udp listener {:?}", req_id, patch.address);
2377        match self.udp.borrow_mut().update_listener(patch) {
2378            Ok(()) => WorkerResponse::ok(req_id),
2379            Err(e) => worker_response_error(req_id, format!("Could not update UDP listener: {e}")),
2380        }
2381    }
2382
2383    fn notify_update_http_listener(
2384        &mut self,
2385        req_id: &str,
2386        patch: UpdateHttpListenerConfig,
2387    ) -> WorkerResponse {
2388        debug!("{} update http listener {:?}", req_id, patch.address);
2389        match self.http.borrow_mut().update_listener(patch) {
2390            Ok(()) => WorkerResponse::ok(req_id),
2391            Err(e) => worker_response_error(req_id, format!("Could not update HTTP listener: {e}")),
2392        }
2393    }
2394
2395    fn notify_update_https_listener(
2396        &mut self,
2397        req_id: &str,
2398        patch: UpdateHttpsListenerConfig,
2399    ) -> WorkerResponse {
2400        debug!("{} update https listener {:?}", req_id, patch.address);
2401        match self.https.borrow_mut().update_listener(patch) {
2402            Ok(()) => WorkerResponse::ok(req_id),
2403            Err(e) => {
2404                worker_response_error(req_id, format!("Could not update HTTPS listener: {e}"))
2405            }
2406        }
2407    }
2408
2409    fn notify_update_tcp_listener(
2410        &mut self,
2411        req_id: &str,
2412        patch: UpdateTcpListenerConfig,
2413    ) -> WorkerResponse {
2414        debug!("{} update tcp listener {:?}", req_id, patch.address);
2415        match self.tcp.borrow_mut().update_listener(patch) {
2416            Ok(()) => WorkerResponse::ok(req_id),
2417            Err(e) => worker_response_error(req_id, format!("Could not update TCP listener: {e}")),
2418        }
2419    }
2420
2421    fn notify_activate_listener(
2422        &mut self,
2423        req_id: &str,
2424        activate: &ActivateListener,
2425    ) -> WorkerResponse {
2426        debug!(
2427            "{} activate {:?} listener {:?}",
2428            req_id, activate.proxy, activate
2429        );
2430
2431        let address: std::net::SocketAddr = activate.address.into();
2432
2433        match ListenerType::try_from(activate.proxy) {
2434            Ok(ListenerType::Http) => {
2435                let listener = self
2436                    .scm_listeners
2437                    .as_mut()
2438                    .and_then(|s| s.get_http(&address))
2439                    // SAFETY: `fd` was just received from the supervisor via SCM_RIGHTS
2440                    // (see `command/src/scm_socket.rs`) and is not owned elsewhere — the
2441                    // `ScmListeners` map removes it on `get_http`. Ownership transfers to
2442                    // the mio wrapper, whose `Drop` closes the descriptor.
2443                    .map(|fd| unsafe { MioTcpListener::from_raw_fd(fd) });
2444
2445                let activated_token = self.http.borrow_mut().activate_listener(&address, listener);
2446                match activated_token {
2447                    Ok(token) => {
2448                        self.accept(ListenToken(token.0), Protocol::HTTPListen);
2449                        WorkerResponse::ok(req_id)
2450                    }
2451                    Err(activate_error) => worker_response_error(
2452                        req_id,
2453                        format!("Could not activate HTTP listener: {activate_error}"),
2454                    ),
2455                }
2456            }
2457            Ok(ListenerType::Https) => {
2458                let listener = self
2459                    .scm_listeners
2460                    .as_mut()
2461                    .and_then(|s| s.get_https(&address))
2462                    // SAFETY: `fd` was just received from the supervisor via SCM_RIGHTS
2463                    // (see `command/src/scm_socket.rs`) and is not owned elsewhere — the
2464                    // `ScmListeners` map removes it on `get_https`. Ownership transfers to
2465                    // the mio wrapper, whose `Drop` closes the descriptor.
2466                    .map(|fd| unsafe { MioTcpListener::from_raw_fd(fd) });
2467
2468                let activated_token = self
2469                    .https
2470                    .borrow_mut()
2471                    .activate_listener(&address, listener);
2472                match activated_token {
2473                    Ok(token) => {
2474                        self.accept(ListenToken(token.0), Protocol::HTTPSListen);
2475                        WorkerResponse::ok(req_id)
2476                    }
2477                    Err(activate_error) => worker_response_error(
2478                        req_id,
2479                        format!("Could not activate HTTPS listener: {activate_error}"),
2480                    ),
2481                }
2482            }
2483            Ok(ListenerType::Tcp) => {
2484                let listener = self
2485                    .scm_listeners
2486                    .as_mut()
2487                    .and_then(|s| s.get_tcp(&address))
2488                    // SAFETY: `fd` was just received from the supervisor via SCM_RIGHTS
2489                    // (see `command/src/scm_socket.rs`) and is not owned elsewhere — the
2490                    // `ScmListeners` map removes it on `get_tcp`. Ownership transfers to
2491                    // the mio wrapper, whose `Drop` closes the descriptor.
2492                    .map(|fd| unsafe { MioTcpListener::from_raw_fd(fd) });
2493
2494                let listener_token = self.tcp.borrow_mut().activate_listener(&address, listener);
2495                match listener_token {
2496                    Ok(token) => {
2497                        self.accept(ListenToken(token.0), Protocol::TCPListen);
2498                        WorkerResponse::ok(req_id)
2499                    }
2500                    Err(activate_error) => worker_response_error(
2501                        req_id,
2502                        format!("Could not activate TCP listener: {activate_error}"),
2503                    ),
2504                }
2505            }
2506            Ok(ListenerType::Udp) => {
2507                let socket = self
2508                    .scm_listeners
2509                    .as_mut()
2510                    .and_then(|s| s.get_udp(&address))
2511                    // SAFETY: `fd` was just received from the supervisor via
2512                    // SCM_RIGHTS (see `command/src/scm_socket.rs`) and is not
2513                    // owned elsewhere — `ScmListeners::get_udp` removes it from
2514                    // the map. Ownership transfers to the mio `UdpSocket`
2515                    // wrapper, whose `Drop` closes the descriptor. `O_NONBLOCK`
2516                    // + `SO_REUSE*` are file-description flags preserved across
2517                    // SCM + exec.
2518                    .map(|fd| unsafe { MioUdpSocket::from_raw_fd(fd) });
2519
2520                let activated_token = self.udp.borrow_mut().activate_listener(&address, socket);
2521                match activated_token {
2522                    Ok(token) => {
2523                        // UDP never uses accept()/create_session: replace the
2524                        // `ListenSession` placeholder at the listener token with
2525                        // the real `UdpListenerSession` so the READABLE
2526                        // registration drives `Server::ready`'s generic path
2527                        // into `UdpListenerSession::update_readiness`.
2528                        if let Some(session) = self.udp.borrow_mut().build_session(token) {
2529                            let mut sessions = self.sessions.borrow_mut();
2530                            if sessions.slab.contains(token.0) {
2531                                sessions.slab[token.0] = session;
2532                            }
2533                        }
2534                        WorkerResponse::ok(req_id)
2535                    }
2536                    Err(activate_error) => worker_response_error(
2537                        req_id,
2538                        format!("Could not activate UDP listener: {activate_error}"),
2539                    ),
2540                }
2541            }
2542            Err(_) => worker_response_error(req_id, "Wrong variant for ListenerType on request"),
2543        }
2544    }
2545
2546    fn notify_deactivate_listener(
2547        &mut self,
2548        req_id: &str,
2549        deactivate: &DeactivateListener,
2550    ) -> WorkerResponse {
2551        debug!(
2552            "{} deactivate {:?} listener {:?}",
2553            req_id, deactivate.proxy, deactivate
2554        );
2555
2556        let address: std::net::SocketAddr = deactivate.address.into();
2557
2558        match ListenerType::try_from(deactivate.proxy) {
2559            Ok(ListenerType::Http) => {
2560                let (token, mut listener) = match self.http.borrow_mut().give_back_listener(address)
2561                {
2562                    Ok((token, listener)) => (token, listener),
2563                    Err(e) => {
2564                        return worker_response_error(
2565                            req_id,
2566                            format!(
2567                                "Couldn't deactivate HTTP listener at address {address:?}: {e}"
2568                            ),
2569                        );
2570                    }
2571                };
2572
2573                if let Err(e) = self.poll.registry().deregister(&mut listener) {
2574                    error!(
2575                        "error deregistering HTTP listen socket({:?}): {:?}",
2576                        deactivate, e
2577                    );
2578                }
2579
2580                {
2581                    let mut sessions = self.sessions.borrow_mut();
2582                    if sessions.slab.contains(token.0) {
2583                        sessions.slab.remove(token.0);
2584                        info!("removed listen token {:?}", token);
2585                    }
2586                }
2587
2588                if deactivate.to_scm {
2589                    self.unblock_scm_socket();
2590                    let listeners = Listeners {
2591                        http: vec![(address, listener.as_raw_fd())],
2592                        tls: vec![],
2593                        tcp: vec![],
2594                        udp: vec![],
2595                    };
2596                    info!("sending HTTP listener: {:?}", listeners);
2597                    let res = self.scm.send_listeners(&listeners);
2598
2599                    self.block_scm_socket();
2600
2601                    info!("sent HTTP listener: {:?}", res);
2602                }
2603                WorkerResponse::ok(req_id)
2604            }
2605            Ok(ListenerType::Https) => {
2606                let (token, mut listener) = match self
2607                    .https
2608                    .borrow_mut()
2609                    .give_back_listener(address)
2610                {
2611                    Ok((token, listener)) => (token, listener),
2612                    Err(e) => {
2613                        return worker_response_error(
2614                            req_id,
2615                            format!(
2616                                "Couldn't deactivate HTTPS listener at address {address:?}: {e}",
2617                            ),
2618                        );
2619                    }
2620                };
2621                if let Err(e) = self.poll.registry().deregister(&mut listener) {
2622                    error!(
2623                        "error deregistering HTTPS listen socket({:?}): {:?}",
2624                        deactivate, e
2625                    );
2626                }
2627                if self.sessions.borrow().slab.contains(token.0) {
2628                    self.sessions.borrow_mut().slab.remove(token.0);
2629                    info!("removed listen token {:?}", token);
2630                }
2631
2632                if deactivate.to_scm {
2633                    self.unblock_scm_socket();
2634                    let listeners = Listeners {
2635                        http: vec![],
2636                        tls: vec![(address, listener.as_raw_fd())],
2637                        tcp: vec![],
2638                        udp: vec![],
2639                    };
2640                    info!("sending HTTPS listener: {:?}", listeners);
2641                    let res = self.scm.send_listeners(&listeners);
2642
2643                    self.block_scm_socket();
2644
2645                    info!("sent HTTPS listener: {:?}", res);
2646                }
2647                WorkerResponse::ok(req_id)
2648            }
2649            Ok(ListenerType::Tcp) => {
2650                let (token, mut listener) = match self.tcp.borrow_mut().give_back_listener(address)
2651                {
2652                    Ok((token, listener)) => (token, listener),
2653                    Err(e) => {
2654                        return worker_response_error(
2655                            req_id,
2656                            format!(
2657                                "Could not deactivate TCP listener at address {address:?}: {e}"
2658                            ),
2659                        );
2660                    }
2661                };
2662
2663                if let Err(e) = self.poll.registry().deregister(&mut listener) {
2664                    error!(
2665                        "error deregistering TCP listen socket({:?}): {:?}",
2666                        deactivate, e
2667                    );
2668                }
2669                if self.sessions.borrow().slab.contains(token.0) {
2670                    self.sessions.borrow_mut().slab.remove(token.0);
2671                    info!("removed listen token {:?}", token);
2672                }
2673
2674                if deactivate.to_scm {
2675                    self.unblock_scm_socket();
2676                    let listeners = Listeners {
2677                        http: vec![],
2678                        tls: vec![],
2679                        tcp: vec![(address, listener.as_raw_fd())],
2680                        udp: vec![],
2681                    };
2682                    info!("sending TCP listener: {:?}", listeners);
2683                    let res = self.scm.send_listeners(&listeners);
2684
2685                    self.block_scm_socket();
2686
2687                    info!("sent TCP listener: {:?}", res);
2688                }
2689                WorkerResponse::ok(req_id)
2690            }
2691            Ok(ListenerType::Udp) => {
2692                let (token, mut listener) = match self.udp.borrow_mut().give_back_listener(address)
2693                {
2694                    Ok((token, listener)) => (token, listener),
2695                    Err(e) => {
2696                        return worker_response_error(
2697                            req_id,
2698                            format!(
2699                                "Could not deactivate UDP listener at address {address:?}: {e}"
2700                            ),
2701                        );
2702                    }
2703                };
2704
2705                if let Err(e) = self.poll.registry().deregister(&mut listener) {
2706                    error!(
2707                        "error deregistering UDP listen socket({:?}): {:?}",
2708                        deactivate, e
2709                    );
2710                }
2711                if self.sessions.borrow().slab.contains(token.0) {
2712                    self.sessions.borrow_mut().slab.remove(token.0);
2713                    info!("removed listen token {:?}", token);
2714                }
2715
2716                if deactivate.to_scm {
2717                    self.unblock_scm_socket();
2718                    let listeners = Listeners {
2719                        http: vec![],
2720                        tls: vec![],
2721                        tcp: vec![],
2722                        udp: vec![(address, listener.as_raw_fd())],
2723                    };
2724                    info!("sending UDP listener: {:?}", listeners);
2725                    let res = self.scm.send_listeners(&listeners);
2726
2727                    self.block_scm_socket();
2728
2729                    info!("sent UDP listener: {:?}", res);
2730                }
2731                WorkerResponse::ok(req_id)
2732            }
2733            Err(_) => worker_response_error(req_id, "Wrong variant for ListenerType on request"),
2734        }
2735    }
2736
2737    /// Send all socket addresses and file descriptors of all proxies, via the scm socket
2738    pub fn return_listen_sockets(&mut self) -> Result<(), ScmSocketError> {
2739        self.unblock_scm_socket();
2740
2741        let mut http_listeners = self.http.borrow_mut().give_back_listeners();
2742        for &mut (_, ref mut sock) in http_listeners.iter_mut() {
2743            if let Err(e) = self.poll.registry().deregister(sock) {
2744                error!(
2745                    "error deregistering HTTP listen socket({:?}): {:?}",
2746                    sock, e
2747                );
2748            }
2749        }
2750
2751        let mut https_listeners = self.https.borrow_mut().give_back_listeners();
2752        for &mut (_, ref mut sock) in https_listeners.iter_mut() {
2753            if let Err(e) = self.poll.registry().deregister(sock) {
2754                error!(
2755                    "error deregistering HTTPS listen socket({:?}): {:?}",
2756                    sock, e
2757                );
2758            }
2759        }
2760
2761        let mut tcp_listeners = self.tcp.borrow_mut().give_back_listeners();
2762        for &mut (_, ref mut sock) in tcp_listeners.iter_mut() {
2763            if let Err(e) = self.poll.registry().deregister(sock) {
2764                error!("error deregistering TCP listen socket({:?}): {:?}", sock, e);
2765            }
2766        }
2767
2768        let mut udp_listeners = self.udp.borrow_mut().give_back_listeners();
2769        for &mut (_, ref mut sock) in udp_listeners.iter_mut() {
2770            if let Err(e) = self.poll.registry().deregister(sock) {
2771                error!("error deregistering UDP listen socket({:?}): {:?}", sock, e);
2772            }
2773        }
2774
2775        // use as_raw_fd because the listeners should be dropped after sending them
2776        let listeners = Listeners {
2777            http: http_listeners
2778                .iter()
2779                .map(|(addr, listener)| (*addr, listener.as_raw_fd()))
2780                .collect(),
2781            tls: https_listeners
2782                .iter()
2783                .map(|(addr, listener)| (*addr, listener.as_raw_fd()))
2784                .collect(),
2785            tcp: tcp_listeners
2786                .iter()
2787                .map(|(addr, listener)| (*addr, listener.as_raw_fd()))
2788                .collect(),
2789            udp: udp_listeners
2790                .iter()
2791                .map(|(addr, listener)| (*addr, listener.as_raw_fd()))
2792                .collect(),
2793        };
2794        // Each handed-back listener is collected exactly once: the assembled
2795        // fd lists mirror the give_back lists one-to-one (the maps above are
2796        // straight `.iter().map().collect()` with no filtering or dedup).
2797        debug_assert_eq!(
2798            listeners.http.len(),
2799            http_listeners.len(),
2800            "every HTTP listener must be collected exactly once"
2801        );
2802        debug_assert_eq!(
2803            listeners.tls.len(),
2804            https_listeners.len(),
2805            "every HTTPS listener must be collected exactly once"
2806        );
2807        debug_assert_eq!(
2808            listeners.tcp.len(),
2809            tcp_listeners.len(),
2810            "every TCP listener must be collected exactly once"
2811        );
2812        info!("sending default listeners: {:?}", listeners);
2813        let res = self.scm.send_listeners(&listeners);
2814
2815        self.block_scm_socket();
2816
2817        info!("sent default listeners: {:?}", res);
2818        res
2819    }
2820
2821    fn block_scm_socket(&mut self) {
2822        if let Err(e) = self.scm.set_blocking(true) {
2823            error!("Could not block scm socket: {}", e);
2824        }
2825    }
2826
2827    fn unblock_scm_socket(&mut self) {
2828        if let Err(e) = self.scm.set_blocking(false) {
2829            error!("Could not unblock scm socket: {}", e);
2830        }
2831    }
2832
2833    pub fn to_session(&self, token: Token) -> SessionToken {
2834        SessionToken(token.0)
2835    }
2836
2837    pub fn from_session(&self, token: SessionToken) -> Token {
2838        Token(token.0)
2839    }
2840
2841    pub fn accept(&mut self, token: ListenToken, protocol: Protocol) {
2842        // Per-protocol counter key. Keeping the namespace static (3 keys +
2843        // aggregate) is a deliberate cardinality cap: per-listener-address
2844        // labelling would require runtime `Box::leak` because `incr!` takes
2845        // `&'static str`, and listener addresses can be reconfigured at
2846        // runtime by the control plane. Operators wanting per-listener
2847        // attribution should correlate with the listener-protocol breakdown
2848        // below.
2849        //
2850        // Non-listen protocols reach this code only on an invariant break
2851        // upstream (`ready()` dispatched a non-listen `Protocol` to
2852        // `accept()`). Log and return rather than panicking — defense in
2853        // depth on the accept path, which is process-fatal if it aborts.
2854        let (proto_key, accepted_protocol) = match protocol {
2855            Protocol::TCPListen => ("listener.accepted.tcp", Protocol::TCPListen),
2856            Protocol::HTTPListen => ("listener.accepted.http", Protocol::HTTPListen),
2857            Protocol::HTTPSListen => ("listener.accepted.https", Protocol::HTTPSListen),
2858            other => {
2859                warn!(
2860                    "accept() called with non-listen protocol {:?} on token {:?}; skipping",
2861                    other, token
2862                );
2863                return;
2864            }
2865        };
2866
2867        // Past the guard, `accepted_protocol` is one of the three listen
2868        // variants — the inner dispatch's `unreachable!` arm relies on this.
2869        debug_assert!(
2870            matches!(
2871                accepted_protocol,
2872                Protocol::TCPListen | Protocol::HTTPListen | Protocol::HTTPSListen
2873            ),
2874            "accept dispatch must run with a listen protocol only"
2875        );
2876
2877        loop {
2878            let result = match accepted_protocol {
2879                Protocol::TCPListen => self.tcp.borrow_mut().accept(token),
2880                Protocol::HTTPListen => self.http.borrow_mut().accept(token),
2881                Protocol::HTTPSListen => self.https.borrow_mut().accept(token),
2882                // The outer match populates `accepted_protocol` only with the
2883                // three listen variants and returns early otherwise — this
2884                // arm is structurally unreachable.
2885                other => unreachable!(
2886                    "accept dispatch reached non-listen protocol {:?} after outer guard",
2887                    other
2888                ),
2889            };
2890            match result {
2891                Ok(sock) => {
2892                    // peer_addr() is one syscall (`getpeername(2)`) and runs
2893                    // exactly once per accepted socket. It can fail if the
2894                    // peer raced to close — recorded as `None` and silently
2895                    // skipped for the per-source counter.
2896                    let peer = sock.peer_addr().ok();
2897                    incr!(names::listener::ACCEPTED_TOTAL);
2898                    incr!(proto_key);
2899                    if let Some(peer_addr) = peer.as_ref() {
2900                        incr!(per_source_bucket(peer_addr));
2901                    }
2902                    let queue_before = self.accept_queue.len();
2903                    self.accept_queue.push_back((
2904                        sock,
2905                        token,
2906                        accepted_protocol,
2907                        Instant::now(),
2908                        peer,
2909                    ));
2910                    // One accepted socket enqueues exactly one entry.
2911                    debug_assert_eq!(
2912                        self.accept_queue.len(),
2913                        queue_before + 1,
2914                        "each accepted socket must enqueue exactly one entry"
2915                    );
2916                }
2917                Err(AcceptError::WouldBlock) => {
2918                    self.accept_ready.remove(&token);
2919                    break;
2920                }
2921                Err(other) => {
2922                    error!(
2923                        "error accepting {:?} sockets: {:?}",
2924                        accepted_protocol, other
2925                    );
2926                    self.accept_ready.remove(&token);
2927                    break;
2928                }
2929            }
2930        }
2931
2932        gauge!(names::accept_queue::CONNECTIONS, self.accept_queue.len());
2933    }
2934
2935    pub fn create_sessions(&mut self) {
2936        while let Some((sock, token, protocol, timestamp, _peer)) = self.accept_queue.pop_back() {
2937            let wait_time = Instant::now() - timestamp;
2938            time!(names::accept_queue::WAIT_TIME, wait_time.as_millis());
2939            if wait_time > self.accept_queue_timeout {
2940                incr!(names::accept_queue::TIMEOUT);
2941                continue;
2942            }
2943
2944            if !self.sessions.borrow_mut().check_limits() {
2945                // The socket we just popped will not be served, plus every
2946                // remaining queued socket below `break` will time out.
2947                // `listener.connection_capped` counts the popped socket so
2948                // the counter aligns with `check_limits` invocations rather
2949                // than with queue depth at the time of refusal.
2950                incr!(names::listener::CONNECTION_CAPPED);
2951
2952                if !self.evict_on_queue_full {
2953                    break;
2954                }
2955
2956                // Skip eviction during graceful shutdown — defeats the
2957                // shutting_down semantics and is wasted work since the
2958                // worker is winding down anyway.
2959                if self.shutting_down.is_some() {
2960                    break;
2961                }
2962
2963                // Evict 1% of `max_connections` per iteration. Conservative
2964                // ratio: large enough to make meaningful progress clearing
2965                // the accept queue, small enough to limit collateral damage
2966                // to active sessions. The cap loop re-checks limits after
2967                // each eviction round, so multiple rounds can run if the
2968                // queue has many pending connections. Decoupled from
2969                // `slab_entries_per_connection` (which only sizes the slab,
2970                // not `max_connections`).
2971                let to_evict = (self.sessions.borrow().max_connections / 100).max(1);
2972                let evicted = self.evict_least_active_sessions(to_evict);
2973                if evicted == 0 {
2974                    // Informational, not an invariant break: the worker may
2975                    // be at boot, or every active session is a system
2976                    // protocol (Channel/Metrics/Timer/listeners) and is
2977                    // ineligible. Stay at warn so operators see it in info-
2978                    // level production logs.
2979                    warn!("evict_on_queue_full enabled but no candidate sessions to evict");
2980                    break;
2981                }
2982
2983                count!(names::sessions::EVICTED, evicted as i64);
2984                warn!(
2985                    "evicted {} least recently active sessions to make room",
2986                    evicted
2987                );
2988
2989                if !self.sessions.borrow_mut().check_limits() {
2990                    break;
2991                }
2992            }
2993
2994            //FIXME: check the timestamp
2995            //TODO: create_session should return the session and
2996            // the server should insert it in the the SessionManager
2997            // The accept path only ever enqueues listen protocols, so the
2998            // `_ => panic!` arm below is genuinely unreachable. Assert the set
2999            // here so a future enqueue regression trips in debug, not prod.
3000            debug_assert!(
3001                matches!(
3002                    protocol,
3003                    Protocol::TCPListen | Protocol::HTTPListen | Protocol::HTTPSListen
3004                ),
3005                "accept queue must only hold listen protocols, got {protocol:?}"
3006            );
3007            match protocol {
3008                Protocol::TCPListen => {
3009                    let proxy = self.tcp.clone();
3010                    if self
3011                        .tcp
3012                        .borrow_mut()
3013                        .create_session(sock, token, wait_time, proxy)
3014                        .is_err()
3015                    {
3016                        break;
3017                    }
3018                }
3019                Protocol::HTTPListen => {
3020                    let proxy = self.http.clone();
3021                    if self
3022                        .http
3023                        .borrow_mut()
3024                        .create_session(sock, token, wait_time, proxy)
3025                        .is_err()
3026                    {
3027                        break;
3028                    }
3029                }
3030                Protocol::HTTPSListen => {
3031                    if self
3032                        .https
3033                        .borrow_mut()
3034                        .create_session(sock, token, wait_time, self.https.clone())
3035                        .is_err()
3036                    {
3037                        break;
3038                    }
3039                }
3040                _ => panic!("should not call accept() on a HTTP, HTTPS or TCP session"),
3041            };
3042            let nb_before = self.sessions.borrow().nb_connections;
3043            self.sessions.borrow_mut().incr();
3044            // A successfully created session bumps the live count by one.
3045            debug_assert_eq!(
3046                self.sessions.borrow().nb_connections,
3047                nb_before + 1,
3048                "create_sessions must account exactly one new connection per created session"
3049            );
3050        }
3051
3052        gauge!(names::accept_queue::CONNECTIONS, self.accept_queue.len());
3053    }
3054
3055    pub fn ready(&mut self, token: Token, events: Ready) {
3056        trace!("PROXY\t{:?} got events: {:?}", token, events);
3057
3058        let session_token = token.0;
3059        if self.sessions.borrow().slab.contains(session_token) {
3060            //info!("sessions contains {:?}", session_token);
3061            let protocol = self.sessions.borrow().slab[session_token]
3062                .borrow()
3063                .protocol();
3064            // NOTE: `token` is NOT necessarily the session's frontend token. A
3065            // session is registered under BOTH its frontend and its backend slab
3066            // slots (the multi-token pattern: `connect_to_backend` inserts the
3067            // same session Rc under a second vacant key), so `ready()` is also
3068            // dispatched with the backend token, where `frontend_token() !=
3069            // session_token`. There is therefore no `token == frontend_token`
3070            // identity to assert here.
3071            //info!("protocol: {:?}", protocol);
3072            match protocol {
3073                Protocol::HTTPListen | Protocol::HTTPSListen | Protocol::TCPListen => {
3074                    //info!("PROTOCOL IS LISTEN");
3075                    if events.is_readable() {
3076                        self.accept_ready.insert(ListenToken(token.0));
3077                        if self.sessions.borrow().can_accept {
3078                            self.accept(ListenToken(token.0), protocol);
3079                        }
3080                        return;
3081                    }
3082
3083                    if events.is_writable() {
3084                        error!(
3085                            "received writable for listener {:?}, this should not happen",
3086                            token
3087                        );
3088                        return;
3089                    }
3090
3091                    if events.is_hup() {
3092                        error!("should not happen: server {:?} closed", token);
3093                        return;
3094                    }
3095
3096                    unreachable!();
3097                }
3098                _ => {}
3099            }
3100
3101            let session = self.sessions.borrow_mut().slab[session_token].clone();
3102            session.borrow_mut().update_readiness(token, events);
3103            if session.borrow_mut().ready(session.clone()) {
3104                debug!(
3105                    "Server killing session from ready: token={:?}, protocol={:?}, events={:?}",
3106                    token, protocol, events
3107                );
3108                self.kill_session(session);
3109            }
3110        }
3111    }
3112
3113    pub fn timeout(&mut self, token: Token) {
3114        trace!("PROXY\t{:?} got timeout", token);
3115
3116        let session_token = token.0;
3117        if self.sessions.borrow().slab.contains(session_token) {
3118            let session = self.sessions.borrow_mut().slab[session_token].clone();
3119            if session.borrow_mut().timeout(token) {
3120                debug!(
3121                    "Server killing session from timeout: token={:?}, protocol={:?}",
3122                    token,
3123                    session.borrow().protocol()
3124                );
3125                self.kill_session(session);
3126            }
3127        }
3128    }
3129
3130    pub fn handle_remaining_readiness(&mut self) {
3131        // try to accept again after handling all session events,
3132        // since we might have released a few session slots
3133        if self.sessions.borrow().can_accept && !self.accept_ready.is_empty() {
3134            while let Some(token) = self
3135                .accept_ready
3136                .iter()
3137                .next()
3138                .map(|token| ListenToken(token.0))
3139            {
3140                let protocol = self.sessions.borrow().slab[token.0].borrow().protocol();
3141                self.accept(token, protocol);
3142                if !self.sessions.borrow().can_accept || self.accept_ready.is_empty() {
3143                    break;
3144                }
3145            }
3146        }
3147    }
3148    fn block_channel(&mut self) {
3149        if let Err(e) = self.channel.blocking() {
3150            error!("Could not block channel: {}", e);
3151        }
3152    }
3153    fn unblock_channel(&mut self) {
3154        if let Err(e) = self.channel.nonblocking() {
3155            error!("Could not block channel: {}", e);
3156        }
3157    }
3158
3159    /// Evict the `count` least-recently-active non-listener sessions and
3160    /// return how many tokens were enqueued for shutdown. Used by
3161    /// `create_sessions` when the accept queue is saturated and the
3162    /// `evict_on_queue_full` knob is set.
3163    ///
3164    /// Uses `select_nth_unstable_by_key` (introselect, O(n) average) to
3165    /// partition the oldest `count` sessions in-place rather than a full
3166    /// O(n log n) sort. The candidate `Vec` is unavoidable because of
3167    /// `RefCell` borrow rules — the immutable borrow on `self.sessions`
3168    /// must drop before `shut_down_sessions_by_frontend_tokens` can take
3169    /// its mutable borrow.
3170    fn evict_least_active_sessions(&self, count: usize) -> usize {
3171        if count == 0 {
3172            return 0;
3173        }
3174
3175        let tokens = {
3176            let sessions = self.sessions.borrow();
3177            let mut candidates: Vec<(Token, Instant)> = sessions
3178                .slab
3179                .iter()
3180                .filter(|(_, session)| {
3181                    !matches!(
3182                        session.borrow().protocol(),
3183                        Protocol::HTTPListen
3184                            | Protocol::HTTPSListen
3185                            | Protocol::TCPListen
3186                            | Protocol::UDPListen
3187                            | Protocol::Channel
3188                            | Protocol::Metrics
3189                            | Protocol::Timer
3190                    )
3191                })
3192                .map(|(_, session)| {
3193                    let s = session.borrow();
3194                    (s.frontend_token(), s.last_event())
3195                })
3196                .collect();
3197
3198            // Early return is load-bearing: the `pivot` computation below
3199            // does `count.min(len) - 1`, which underflows on empty input.
3200            if candidates.is_empty() {
3201                return 0;
3202            }
3203
3204            let pivot = count.min(candidates.len()) - 1;
3205            candidates.select_nth_unstable_by_key(pivot, |&(_, last_event)| last_event);
3206
3207            candidates[..=pivot]
3208                .iter()
3209                .map(|&(token, _)| token)
3210                .collect::<HashSet<Token>>()
3211        };
3212
3213        let evicted = tokens.len();
3214        self.shut_down_sessions_by_frontend_tokens(tokens);
3215        evicted
3216    }
3217}
3218
3219/// log the error together with the request id
3220/// create a WorkerResponse
3221fn worker_response_error<S: ToString, T: ToString>(request_id: S, error: T) -> WorkerResponse {
3222    error!(
3223        "error on request {}, {}",
3224        request_id.to_string(),
3225        error.to_string()
3226    );
3227    WorkerResponse::error(request_id, error)
3228}
3229
3230pub struct ListenSession {
3231    pub protocol: Protocol,
3232}
3233
3234impl ProxySession for ListenSession {
3235    fn last_event(&self) -> Instant {
3236        Instant::now()
3237    }
3238
3239    fn print_session(&self) {}
3240
3241    fn frontend_token(&self) -> Token {
3242        Token(0)
3243    }
3244
3245    fn protocol(&self) -> Protocol {
3246        self.protocol
3247    }
3248
3249    fn ready(&mut self, _session: Rc<RefCell<dyn ProxySession>>) -> SessionIsToBeClosed {
3250        false
3251    }
3252
3253    fn shutting_down(&mut self) -> SessionIsToBeClosed {
3254        false
3255    }
3256
3257    fn update_readiness(&mut self, _token: Token, _events: Ready) {}
3258
3259    fn close(&mut self) {}
3260
3261    fn timeout(&mut self, _token: Token) -> SessionIsToBeClosed {
3262        error!(
3263            "called ProxySession::timeout(token={:?}, time) on ListenSession {{ protocol: {:?} }}",
3264            _token, self.protocol
3265        );
3266        false
3267    }
3268}
3269
3270#[cfg(test)]
3271mod accept_telemetry_tests {
3272    use super::*;
3273
3274    /// Two IPv4 addresses sharing the same /24 must hash to the same bucket;
3275    /// the masking logic guarantees this regardless of the host octet.
3276    #[test]
3277    fn per_source_bucket_collapses_ipv4_slash24() {
3278        let a: SocketAddr = "203.0.113.5:1234".parse().unwrap();
3279        let b: SocketAddr = "203.0.113.250:9999".parse().unwrap();
3280        assert_eq!(
3281            per_source_bucket(&a),
3282            per_source_bucket(&b),
3283            "addresses in the same /24 must land in the same bucket"
3284        );
3285    }
3286
3287    /// Two IPv6 addresses sharing the same /48 must hash to the same bucket.
3288    #[test]
3289    fn per_source_bucket_collapses_ipv6_slash48() {
3290        let a: SocketAddr = "[2001:db8:1234::1]:443".parse().unwrap();
3291        let b: SocketAddr = "[2001:db8:1234:abcd::ffff]:8443".parse().unwrap();
3292        assert_eq!(
3293            per_source_bucket(&a),
3294            per_source_bucket(&b),
3295            "addresses in the same /48 must land in the same bucket"
3296        );
3297    }
3298
3299    /// Every bucket label must be one of `PER_SOURCE_BUCKETS` precomputed
3300    /// statics — the cardinality cap is the load-bearing property.
3301    #[test]
3302    fn per_source_bucket_keys_are_bounded() {
3303        assert_eq!(PER_SOURCE_BUCKET_KEYS.len(), PER_SOURCE_BUCKETS);
3304        for (i, key) in PER_SOURCE_BUCKET_KEYS.iter().enumerate() {
3305            let expected = format!("client.connect.per_source.bucket_{i:03}");
3306            assert_eq!(*key, expected.as_str());
3307        }
3308    }
3309
3310    /// A modest sample across distinct /24 prefixes should hit a healthy
3311    /// number of distinct buckets — guards against the hash collapsing.
3312    #[test]
3313    fn per_source_bucket_distributes_distinct_subnets() {
3314        let mut hits = std::collections::HashSet::new();
3315        for i in 0..200u8 {
3316            let addr: SocketAddr = format!("10.0.{i}.42:80").parse().unwrap();
3317            hits.insert(per_source_bucket(&addr));
3318        }
3319        // With 200 distinct /24 prefixes hashed into 256 buckets we expect
3320        // many distinct labels — assert a conservative lower bound that
3321        // tolerates birthday collisions.
3322        assert!(
3323            hits.len() >= 100,
3324            "expected at least 100 distinct buckets across 200 /24s, got {}",
3325            hits.len()
3326        );
3327    }
3328}
3329
3330#[cfg(test)]
3331mod state_error_log_tests {
3332    use sozu_command::state::StateError;
3333
3334    #[test]
3335    fn worker_state_dispatch_error_sink_bounds_the_reason() {
3336        const REASON_SECRET: &str = "WORKER_STATE_ERROR_REASON_SECRET_SENTINEL";
3337
3338        let reason = format!("{REASON_SECRET}{}", "x".repeat(4096));
3339        let reason_len = reason.len();
3340        let output = crate::capture_test_logs(move || {
3341            let error = StateError::InvalidTcpFrontend {
3342                address: "127.0.0.1:443"
3343                    .parse()
3344                    .expect("test TCP frontend address must parse"),
3345                reason,
3346            };
3347            error!("Could not execute order on config state: {}", error);
3348        });
3349
3350        assert!(
3351            !output.contains(REASON_SECRET),
3352            "worker state-error sink leaked the reason: {output}"
3353        );
3354        assert!(
3355            output.contains(&format!("reason_bytes={reason_len}")),
3356            "worker state-error sink omitted bounded reason metadata: {output}"
3357        );
3358        assert!(
3359            output.len() <= 512,
3360            "worker state-error sink is not bounded: {} bytes",
3361            output.len()
3362        );
3363    }
3364}
3365
3366#[cfg(test)]
3367mod eviction_tests {
3368    use std::collections::HashSet;
3369    use std::time::{Duration, Instant};
3370
3371    use mio::Token;
3372
3373    /// `select_nth_unstable_by_key` partitions in O(n) so that the first
3374    /// `pivot + 1` entries are the `pivot + 1` smallest by key. This guards
3375    /// against a future refactor swapping the comparator orientation.
3376    #[test]
3377    fn select_nth_finds_oldest_sessions() {
3378        let now = Instant::now();
3379        let mut candidates = [
3380            (Token(1), now - Duration::from_secs(10)), // 10s old
3381            (Token(2), now - Duration::from_secs(50)), // 50s old (oldest)
3382            (Token(3), now - Duration::from_secs(5)),  // 5s old (newest)
3383            (Token(4), now - Duration::from_secs(30)), // 30s old
3384            (Token(5), now - Duration::from_secs(20)), // 20s old
3385        ];
3386
3387        let count = 2;
3388        let pivot = count.min(candidates.len()) - 1;
3389        candidates.select_nth_unstable_by_key(pivot, |&(_, last_event)| last_event);
3390
3391        let selected: HashSet<Token> = candidates[..=pivot]
3392            .iter()
3393            .map(|&(token, _)| token)
3394            .collect();
3395
3396        assert_eq!(selected.len(), 2);
3397        assert!(
3398            selected.contains(&Token(2)),
3399            "should contain 50s-old session"
3400        );
3401        assert!(
3402            selected.contains(&Token(4)),
3403            "should contain 30s-old session"
3404        );
3405    }
3406
3407    /// When `count` exceeds available candidates, the pivot collapses to
3408    /// `len - 1` so we evict everything; this test pins that behaviour
3409    /// against a future refactor that might silently truncate.
3410    #[test]
3411    fn select_nth_with_count_exceeding_candidates() {
3412        let now = Instant::now();
3413        let mut candidates = [(Token(1), now - Duration::from_secs(10))];
3414
3415        let count = 5;
3416        let pivot = count.min(candidates.len()) - 1;
3417        candidates.select_nth_unstable_by_key(pivot, |&(_, last_event)| last_event);
3418
3419        let selected: HashSet<Token> = candidates[..=pivot]
3420            .iter()
3421            .map(|&(token, _)| token)
3422            .collect();
3423
3424        assert_eq!(selected.len(), 1);
3425        assert!(selected.contains(&Token(1)));
3426    }
3427}