Skip to main content

subc_daemon/
router.rs

1use std::{
2    collections::HashMap,
3    error::Error,
4    fmt,
5    sync::{
6        atomic::{AtomicU64, AtomicUsize, Ordering},
7        Arc, Mutex,
8    },
9    time::{Duration, Instant},
10};
11
12use subc_protocol::{error_codes, ErrorBody, Flags, FrameType, Priority};
13use tokio::sync::{mpsc, Notify};
14use tracing::{debug, warn};
15
16use crate::{
17    control::ControlHandler,
18    forwarding::{
19        principal_label, CloseReason, ConnectionCloseReceiver, DataRoute, DataRouteState,
20        ForwardingError, ForwardingTable, RouteBinding, RouteRelease, UndeliveredFrame,
21    },
22    registry::ConnectionId,
23    DaemonCounters, Frame, FrameBuildError,
24};
25
26/// One queued outbound frame plus the instant it entered the writer queue.
27///
28/// The stamp exists for the reply-path half of slow-control diagnosis: a
29/// handler can finish in microseconds while the reply sits in this queue
30/// waiting for the writer task to be scheduled, and without a per-item stamp
31/// that wait is invisible to every other timing point (the client's round
32/// trip is the only witness, and it cannot say which side ate the time).
33/// Constructed exclusively inside [`FrameSink`] so no caller can forget it.
34#[derive(Debug)]
35pub struct OutboundFrame {
36    pub frame: Frame,
37    pub enqueued_at: std::time::Instant,
38    pub(crate) flushed: Option<tokio::sync::oneshot::Sender<()>>,
39    /// This frame's share of the connection's queued-byte count. Dropping the
40    /// frame (after the writer has written it, or when the queue itself is
41    /// dropped with the frame still in it) gives the bytes back. `None` only for
42    /// frames built outside a [`FrameSink`], which exist in tests alone.
43    pub(crate) charge: Option<EgressCharge>,
44}
45
46impl OutboundFrame {
47    fn charged(frame: Frame, charge: EgressCharge) -> Self {
48        Self {
49            frame,
50            enqueued_at: charge.enqueued_at,
51            flushed: None,
52            charge: Some(charge),
53        }
54    }
55}
56
57/// Bytes a frame occupies in the connection's egress queue for budget
58/// purposes: the fixed envelope header plus the body.
59fn queued_frame_bytes(frame: &Frame) -> usize {
60    subc_protocol::HEADER_LEN + frame.body.len()
61}
62
63/// A point-in-time view of one connection's egress queue, for diagnosing why a
64/// frame did not fit.
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
66pub(crate) struct EgressBacklog {
67    pub queued_bytes: usize,
68    pub queued_frames: usize,
69    /// Now minus the enqueue time of the frame the writer most recently took
70    /// off the queue (the frame it is writing, or has just written), or, if
71    /// the writer has taken nothing since the queue was last empty, of the
72    /// frame that made the queue non-empty. While the writer is blocked on a
73    /// frame this is exactly the oldest frame's age; between frames it can be
74    /// one frame older than the true head. `None` when nothing is queued.
75    pub oldest_age: Option<Duration>,
76}
77
78/// Queued-byte accounting shared by every clone of one connection's
79/// [`FrameSink`] and by every frame that sink has admitted. Everything here is
80/// an atomic: this is on the path of every frame a module sends to a client,
81/// so it takes no lock per frame.
82#[derive(Debug)]
83struct EgressAccounting {
84    byte_budget: usize,
85    queued_bytes: AtomicUsize,
86    queued_frames: AtomicUsize,
87    /// Origin for the nanosecond timestamps in `oldest_enqueued_nanos`.
88    time_base: Instant,
89    /// Enqueue time, as nanoseconds after `time_base` plus one, of the frame
90    /// described by [`EgressBacklog::oldest_age`]; 0 means none.
91    oldest_enqueued_nanos: AtomicU64,
92    /// Awaited senders currently parked on `freed`. A release only pays for a
93    /// notification when this is non-zero.
94    waiters: AtomicUsize,
95    /// Woken when a charged frame releases its bytes while a sender waits, so
96    /// the awaited send can re-check for room.
97    freed: Notify,
98}
99
100impl EgressAccounting {
101    fn new(byte_budget: usize) -> Self {
102        Self {
103            byte_budget,
104            queued_bytes: AtomicUsize::new(0),
105            queued_frames: AtomicUsize::new(0),
106            time_base: Instant::now(),
107            oldest_enqueued_nanos: AtomicU64::new(0),
108            waiters: AtomicUsize::new(0),
109            freed: Notify::new(),
110        }
111    }
112
113    fn stamp(&self, at: Instant) -> u64 {
114        (at.saturating_duration_since(self.time_base).as_nanos() as u64).saturating_add(1)
115    }
116
117    /// Charge `bytes` if they fit in the budget. A frame larger than the whole
118    /// budget (bodies may be up to 64 MiB) is still admitted into an EMPTY
119    /// queue, since it could otherwise never be sent at all; it simply has the
120    /// queue to itself until it is written.
121    fn try_charge(self: &Arc<Self>, bytes: usize) -> Option<EgressCharge> {
122        // SeqCst pairs with `release`: either this load sees bytes a release
123        // just freed, or that release sees this sender counted in `waiters`.
124        let mut current = self.queued_bytes.load(Ordering::SeqCst);
125        loop {
126            if current != 0 && current.saturating_add(bytes) > self.byte_budget {
127                return None;
128            }
129            match self.queued_bytes.compare_exchange_weak(
130                current,
131                current + bytes,
132                Ordering::SeqCst,
133                Ordering::SeqCst,
134            ) {
135                Ok(_) => return Some(self.record(bytes)),
136                Err(actual) => current = actual,
137            }
138        }
139    }
140
141    /// Charge `bytes` regardless of the budget. Used only for a frame whose
142    /// queue slot was reserved in advance, which must be sendable.
143    fn charge_unconditionally(self: &Arc<Self>, bytes: usize) -> EgressCharge {
144        self.queued_bytes.fetch_add(bytes, Ordering::SeqCst);
145        self.record(bytes)
146    }
147
148    fn record(self: &Arc<Self>, bytes: usize) -> EgressCharge {
149        let enqueued_at = Instant::now();
150        let stamp = self.stamp(enqueued_at);
151        if self.queued_frames.fetch_add(1, Ordering::AcqRel) == 0 {
152            // This frame made the queue non-empty, so it is the head until the
153            // writer takes something.
154            self.oldest_enqueued_nanos.store(stamp, Ordering::Release);
155        }
156        EgressCharge {
157            accounting: Arc::clone(self),
158            bytes,
159            stamp,
160            enqueued_at,
161        }
162    }
163
164    /// The writer has taken the frame stamped `stamp` off the queue.
165    fn taken_by_writer(&self, stamp: u64) {
166        self.oldest_enqueued_nanos.store(stamp, Ordering::Release);
167    }
168
169    fn release(&self, bytes: usize, stamp: u64) {
170        self.queued_bytes.fetch_sub(bytes, Ordering::SeqCst);
171        if self.queued_frames.fetch_sub(1, Ordering::AcqRel) == 1 {
172            // The queue drained. Clear the marker only if it still names this
173            // frame, so a frame admitted in the meantime keeps its stamp.
174            let _ = self.oldest_enqueued_nanos.compare_exchange(
175                stamp,
176                0,
177                Ordering::AcqRel,
178                Ordering::Acquire,
179            );
180        }
181        if self.waiters.load(Ordering::SeqCst) != 0 {
182            self.freed.notify_waiters();
183        }
184    }
185
186    fn backlog(&self) -> EgressBacklog {
187        let queued_frames = self.queued_frames.load(Ordering::Acquire);
188        let stamp = self.oldest_enqueued_nanos.load(Ordering::Acquire);
189        let oldest_age = (queued_frames != 0 && stamp != 0).then(|| {
190            let enqueued = self.time_base + Duration::from_nanos(stamp - 1);
191            enqueued.elapsed()
192        });
193        EgressBacklog {
194            queued_bytes: self.queued_bytes.load(Ordering::Acquire),
195            queued_frames,
196            oldest_age,
197        }
198    }
199}
200
201/// One admitted frame's claim on its connection's egress byte budget,
202/// returned when the frame is dropped.
203#[derive(Debug)]
204pub(crate) struct EgressCharge {
205    accounting: Arc<EgressAccounting>,
206    bytes: usize,
207    stamp: u64,
208    enqueued_at: Instant,
209}
210
211impl EgressCharge {
212    /// Called by the connection writer when it takes this frame off the queue,
213    /// so the backlog's oldest-age figure follows the writer.
214    pub(crate) fn taken_by_writer(&self) {
215        self.accounting.taken_by_writer(self.stamp);
216    }
217}
218
219impl Drop for EgressCharge {
220    fn drop(&mut self) {
221        self.accounting.release(self.bytes, self.stamp);
222    }
223}
224
225/// A queue slot reserved ahead of time (a pending `route.open` holds one until
226/// its module answers). Sending through it never waits and never fails for
227/// lack of room: the slot is already held, and its frame is charged to the
228/// byte count outside the budget check. At most
229/// `MAX_PENDING_ROUTE_OPENS_PER_CONNECTION` such frames exist per connection.
230#[derive(Debug)]
231pub(crate) struct EgressPermit {
232    permit: mpsc::OwnedPermit<OutboundFrame>,
233    accounting: Arc<EgressAccounting>,
234}
235
236impl EgressPermit {
237    /// Enqueue `frame` in the reserved slot. Returns true when the connection's
238    /// writer had already gone away, meaning the frame will never be written.
239    pub(crate) fn send(self, frame: Frame) -> bool {
240        let charge = self
241            .accounting
242            .charge_unconditionally(queued_frame_bytes(&frame));
243        let sender = self.permit.send(OutboundFrame::charged(frame, charge));
244        sender.is_closed()
245    }
246}
247
248/// An `OutboundFrame` is a stamped `Frame`; deref keeps every existing frame
249/// read (headers, bodies, assertions) working on queued items unchanged.
250impl std::ops::Deref for OutboundFrame {
251    type Target = Frame;
252
253    fn deref(&self) -> &Frame {
254        &self.frame
255    }
256}
257
258/// Shared tracing-capture helpers for timing-observability tests across
259/// modules (router dispatch, server reply path). Test-only.
260#[cfg(test)]
261pub(crate) mod test_log {
262    use std::{
263        io::Write,
264        sync::{Arc, Mutex, OnceLock},
265    };
266
267    #[derive(Clone)]
268    struct TestLogWriter(Arc<Mutex<Vec<u8>>>);
269
270    impl Write for TestLogWriter {
271        fn write(&mut self, buffer: &[u8]) -> std::io::Result<usize> {
272            self.0
273                .lock()
274                .expect("test log capture is not poisoned")
275                .extend(buffer);
276            Ok(buffer.len())
277        }
278
279        fn flush(&mut self) -> std::io::Result<()> {
280            Ok(())
281        }
282    }
283
284    pub(crate) fn log_capture(
285        level: tracing::Level,
286    ) -> (Arc<Mutex<Vec<u8>>>, tracing::dispatcher::DefaultGuard) {
287        // tracing-core's single-dispatch fast path registers callsites using
288        // the emitting thread's default subscriber. A parallel test with no
289        // subscriber can therefore cache `never` for a callsite this capture
290        // needs. Keep a second dispatch alive so registration considers all
291        // live subscribers instead. This registry is never installed as a
292        // default: each test still records only into its own thread's writer.
293        static INTEREST_ANCHOR: OnceLock<tracing::Dispatch> = OnceLock::new();
294        INTEREST_ANCHOR.get_or_init(|| tracing::Dispatch::new(tracing_subscriber::registry()));
295        let output = Arc::new(Mutex::new(Vec::new()));
296        let writer = Arc::clone(&output);
297        let subscriber = tracing_subscriber::fmt()
298            .with_max_level(level)
299            .with_ansi(false)
300            .without_time()
301            .with_target(false)
302            .with_writer(move || TestLogWriter(Arc::clone(&writer)))
303            .finish();
304        let guard = tracing::subscriber::set_default(subscriber);
305        (output, guard)
306    }
307
308    pub(crate) fn captured_logs(output: &Arc<Mutex<Vec<u8>>>) -> String {
309        String::from_utf8(
310            output
311                .lock()
312                .expect("test log capture is not poisoned")
313                .clone(),
314        )
315        .expect("tracing output is UTF-8")
316    }
317
318    #[cfg(test)]
319    mod tests {
320        use super::*;
321
322        fn emit_capture_probe(source: &str) {
323            tracing::error!(source, "parallel capture probe");
324        }
325
326        #[test]
327        fn parallel_callsite_registration_keeps_the_test_capture_enabled() {
328            let (output, _guard) = log_capture(tracing::Level::ERROR);
329            // First register the very same callsite on a thread without a
330            // default subscriber, while the test's capture remains live.
331            std::thread::spawn(|| emit_capture_probe("other-thread"))
332                .join()
333                .expect("unsubscribed thread exits");
334            emit_capture_probe("captured-thread");
335
336            let logs = captured_logs(&output);
337            assert!(
338                logs.contains("captured-thread"),
339                "capture was disabled: {logs}"
340            );
341            assert!(!logs.contains("other-thread"), "capture leaked: {logs}");
342        }
343    }
344}
345
346/// Cheaply cloneable handle to one connection's bounded outbound frame queue.
347///
348/// Backends emit responses, streaming frames, and future PUSH frames through this
349/// single path. The queue is bounded twice: by queued BYTES (the budget that
350/// matters for memory and for how long a slow reader may pause), and by the
351/// `mpsc` channel's frame count (a backstop). The socket layer owns the sole
352/// receiver/writer.
353#[derive(Debug, Clone)]
354pub struct FrameSink {
355    tx: mpsc::Sender<OutboundFrame>,
356    accounting: Arc<EgressAccounting>,
357}
358
359impl FrameSink {
360    /// A sink with the standard per-connection byte budget
361    /// ([`crate::server::CONNECTION_EGRESS_BYTE_BUDGET`]); the frame-count bound
362    /// is whatever capacity `tx`'s channel was created with.
363    pub fn new(tx: mpsc::Sender<OutboundFrame>) -> Self {
364        Self::with_byte_budget(tx, crate::server::CONNECTION_EGRESS_BYTE_BUDGET)
365    }
366
367    pub(crate) fn with_byte_budget(tx: mpsc::Sender<OutboundFrame>, byte_budget: usize) -> Self {
368        Self {
369            tx,
370            accounting: Arc::new(EgressAccounting::new(byte_budget)),
371        }
372    }
373
374    /// Wait until the frame's bytes fit the budget, then charge them. Awaited
375    /// senders (control replies, error replies, shutdown notices) are never
376    /// refused by the byte budget: they wait for the writer to free bytes, just
377    /// as they wait for a free slot, which is the same backpressure they had
378    /// when the queue was bounded by frame count alone. Returns `None` if the
379    /// writer goes away while waiting.
380    async fn charge_waiting(&self, bytes: usize) -> Option<EgressCharge> {
381        if let Some(charge) = self.accounting.try_charge(bytes) {
382            return Some(charge);
383        }
384        // Counted as a waiter for as long as this future is parked, including
385        // when it is cancelled, so releases notify only while someone waits.
386        struct Waiting<'a>(&'a AtomicUsize);
387        impl Drop for Waiting<'_> {
388            fn drop(&mut self) {
389                self.0.fetch_sub(1, Ordering::SeqCst);
390            }
391        }
392        self.accounting.waiters.fetch_add(1, Ordering::SeqCst);
393        let _waiting = Waiting(&self.accounting.waiters);
394        loop {
395            let freed = self.accounting.freed.notified();
396            tokio::pin!(freed);
397            // Register interest before checking, so a release that happens
398            // between the check and the await still wakes this sender.
399            freed.as_mut().enable();
400            if let Some(charge) = self.accounting.try_charge(bytes) {
401                return Some(charge);
402            }
403            tokio::select! {
404                _ = &mut freed => {}
405                _ = self.tx.closed() => return None,
406            }
407        }
408    }
409
410    pub async fn send(&self, frame: Frame) -> Result<(), RouterError> {
411        let channel = frame.header.channel;
412        let epoch = frame.header.epoch;
413        let corr = frame.header.corr;
414        let closed =
415            || RouterError::backend_with_epoch(channel, epoch, corr, "connection writer closed");
416        let charge = self
417            .charge_waiting(queued_frame_bytes(&frame))
418            .await
419            .ok_or_else(closed)?;
420        self.tx
421            .send(OutboundFrame::charged(frame, charge))
422            .await
423            .map_err(|_| closed())
424    }
425
426    /// Shutdown notices must leave the socket writer before an idle daemon exits.
427    /// Queue admission alone does not prove this; the writer acknowledges flush.
428    #[cfg(unix)]
429    pub(crate) async fn send_flushed(&self, frame: Frame) -> Result<(), RouterError> {
430        let (tx, rx) = tokio::sync::oneshot::channel();
431        let charge = self
432            .charge_waiting(queued_frame_bytes(&frame))
433            .await
434            .ok_or_else(|| RouterError::backend(0, 0, "connection writer closed"))?;
435        let mut outbound = OutboundFrame::charged(frame, charge);
436        outbound.flushed = Some(tx);
437        self.tx
438            .send(outbound)
439            .await
440            .map_err(|_| RouterError::backend(0, 0, "connection writer closed"))?;
441        rx.await
442            .map_err(|_| RouterError::backend(0, 0, "connection flush failed"))
443    }
444
445    pub(crate) async fn reserve_owned(&self) -> Result<EgressPermit, RouterError> {
446        let permit = self
447            .tx
448            .clone()
449            .reserve_owned()
450            .await
451            .map_err(|_| RouterError::backend(0, 0, "connection writer closed"))?;
452        Ok(EgressPermit {
453            permit,
454            accounting: Arc::clone(&self.accounting),
455        })
456    }
457
458    #[cfg(test)]
459    pub(crate) fn try_reserve_owned(&self) -> Result<EgressPermit, RouterError> {
460        let permit = self
461            .tx
462            .clone()
463            .try_reserve_owned()
464            .map_err(|err| RouterError::backend(0, 0, err.to_string()))?;
465        Ok(EgressPermit {
466            permit,
467            accounting: Arc::clone(&self.accounting),
468        })
469    }
470
471    pub(crate) fn is_closed(&self) -> bool {
472        self.tx.is_closed()
473    }
474
475    /// What the connection's egress queue holds right now.
476    pub(crate) fn backlog(&self) -> EgressBacklog {
477        self.accounting.backlog()
478    }
479
480    /// Enqueue without waiting. Fails when the frame's bytes would take the
481    /// queue past its byte budget, when the channel's frame-count backstop is
482    /// full, or when the writer is gone.
483    pub(crate) fn try_send(&self, frame: Frame) -> Result<(), RouterError> {
484        let channel = frame.header.channel;
485        let epoch = frame.header.epoch;
486        let corr = frame.header.corr;
487        let unavailable = |why: String| {
488            RouterError::backend_with_epoch(
489                channel,
490                epoch,
491                corr,
492                format!("connection writer unavailable: {why}"),
493            )
494        };
495        let bytes = queued_frame_bytes(&frame);
496        let Some(charge) = self.accounting.try_charge(bytes) else {
497            if self.tx.is_closed() {
498                return Err(unavailable("channel closed".to_string()));
499            }
500            return Err(unavailable(format!(
501                "egress byte budget exhausted ({} queued bytes, frame of {bytes} bytes, budget {})",
502                self.accounting.queued_bytes.load(Ordering::Acquire),
503                self.accounting.byte_budget
504            )));
505        };
506        // A refused frame is dropped inside the error, which returns its charge.
507        self.tx
508            .try_send(OutboundFrame::charged(frame, charge))
509            .map_err(|err| unavailable(err.to_string()))
510    }
511}
512
513/// Minimum time between two route GOODBYEs the daemon sends one module
514/// connection for one channel in answer to the module's frames on a (channel,
515/// epoch) the daemon holds no route for.
516///
517/// A module that missed a GOODBYE keeps sending on that route (a streaming
518/// module, many frames a second), and every one of those frames lands here. One
519/// answer is enough when it arrives, so answering each frame would only flood
520/// the module's egress queue at the moment it is already behind. Five seconds
521/// is long enough for an answer queued behind a deep backlog to reach the module
522/// and take effect before a second is sent, and short enough that an answer
523/// the queue refused is retried by the next orphan frame well within a minute.
524const ORPHAN_ROUTE_GOODBYE_INTERVAL: Duration = Duration::from_secs(5);
525
526/// Once one connection has this many remembered channels, entries older than
527/// [`ORPHAN_ROUTE_GOODBYE_INTERVAL`] are pruned before another is added. An
528/// expired entry has no effect on rate limiting, so pruning never changes a
529/// decision; it keeps a module that orphaned many channels long ago from
530/// pinning memory for all of them. The map is bounded in any case by the
531/// 16-bit channel space and is dropped whole when the connection ends.
532const ORPHAN_ROUTE_GOODBYE_PRUNE_AT: usize = 256;
533
534/// When each module connection was last sent an orphan-route GOODBYE, per
535/// channel. Shared by the [`Router`] (which consults it) and every
536/// [`RouterConnection`] (which removes its own entry when the connection ends).
537#[derive(Debug, Default)]
538struct OrphanGoodbyeLimiter {
539    last_sent: Mutex<HashMap<ConnectionId, HashMap<u16, tokio::time::Instant>>>,
540}
541
542impl OrphanGoodbyeLimiter {
543    /// True, and the send recorded, when `channel` on `connection_id` has not
544    /// been answered within the interval.
545    fn claim(&self, connection_id: ConnectionId, channel: u16) -> bool {
546        let now = tokio::time::Instant::now();
547        let mut last_sent = self
548            .last_sent
549            .lock()
550            .unwrap_or_else(std::sync::PoisonError::into_inner);
551        let channels = last_sent.entry(connection_id).or_default();
552        if channels.get(&channel).is_some_and(|sent| {
553            now.saturating_duration_since(*sent) < ORPHAN_ROUTE_GOODBYE_INTERVAL
554        }) {
555            return false;
556        }
557        if channels.len() >= ORPHAN_ROUTE_GOODBYE_PRUNE_AT {
558            channels.retain(|_, sent| {
559                now.saturating_duration_since(*sent) < ORPHAN_ROUTE_GOODBYE_INTERVAL
560            });
561        }
562        channels.insert(channel, now);
563        true
564    }
565
566    fn forget_connection(&self, connection_id: ConnectionId) {
567        self.last_sent
568            .lock()
569            .unwrap_or_else(std::sync::PoisonError::into_inner)
570            .remove(&connection_id);
571    }
572}
573
574/// Per-route context shared with backends besides the frame itself.
575#[derive(Debug, Clone)]
576pub struct RouteCtx {
577    pub connection_id: ConnectionId,
578    pub egress: FrameSink,
579}
580
581/// Closed set of data-plane backends for non-zero channels.
582///
583/// Channel 0 is structurally special and remains [`Router::control`], not an
584/// enum variant. Static/test backends live in [`Router::backends`]; the forwarding
585/// backend is selected dynamically only when a per-connection forwarding binding exists.
586#[derive(Debug, Clone)]
587pub enum Backend {
588    Echo(EchoBackend),
589    Forward(ForwardBackend),
590}
591
592impl From<EchoBackend> for Backend {
593    fn from(backend: EchoBackend) -> Self {
594        Self::Echo(backend)
595    }
596}
597
598impl From<ForwardBackend> for Backend {
599    fn from(backend: ForwardBackend) -> Self {
600        Self::Forward(backend)
601    }
602}
603
604impl Backend {
605    pub async fn handle(&self, ctx: RouteCtx, frame: Frame) -> Result<(), RouterError> {
606        match self {
607            Self::Echo(backend) => backend.handle(ctx, frame).await,
608            Self::Forward(backend) => backend.handle(ctx, frame).await,
609        }
610    }
611}
612
613/// I/O-agnostic splice router keyed by envelope `channel`.
614///
615/// Channel 0 is reserved for subc itself and is always dispatched to the
616/// dedicated control handler. Other channels must be explicitly registered.
617/// Unknown client-originated non-zero channels are translated to canonical JSON `ERROR` frames on
618/// the connection sink so the peer can continue using the same socket. Module-originated frames for
619/// released route channels are logged and dropped as the channel-gone race backstop.
620///
621/// DATA-PLANE BODIES ARE NEVER DECODED HERE, and the consequence is worth stating
622/// because it looks like a guarantee and is not. Additive fields in a request or
623/// response body reach the far side untouched — not because anything permits them,
624/// but because routing reads only the 21-byte header and treats the body as opaque
625/// bytes. That is a performance property, so **nothing prevents it from changing**:
626/// a future reason to inspect a body would convert a wire-transparent path into a
627/// filtering one, and nobody would think of it as a contract change.
628///
629/// So when a peer asks whether subc sees an additive field, the answer is per-path
630/// and this path's zero means WE NEVER LOOK rather than WE LOOK AT EVERYTHING. The
631/// control plane (typed enums at the frame boundary) and the MCP gateway (envelope
632/// unwrap plus a named struct) both narrow; only this one does not.
633pub struct Router {
634    backends: HashMap<u16, Backend>,
635    control: Arc<ControlHandler>,
636    forwarding: Arc<ForwardingTable>,
637    forward_backend: ForwardBackend,
638    counters: DaemonCounters,
639    next_connection_id: AtomicU64,
640    orphan_goodbyes: Arc<OrphanGoodbyeLimiter>,
641}
642
643impl Router {
644    pub fn with_control_handler(control: Arc<ControlHandler>) -> Self {
645        // The handler the router serves is the one a swap must tell when it
646        // promotes a candidate; this is where it first sits behind an `Arc`.
647        control.install_swap_promotion_observer();
648        let forwarding = control.forwarding();
649        let counters = control.counters();
650        Self {
651            backends: HashMap::new(),
652            control,
653            forwarding: Arc::clone(&forwarding),
654            forward_backend: ForwardBackend::new(forwarding),
655            counters,
656            // ConnectionId::LOCAL is 0; real socket ids start at 1 and never collide.
657            next_connection_id: AtomicU64::new(1),
658            orphan_goodbyes: Arc::default(),
659        }
660    }
661
662    pub fn with_default_self_handler() -> Self {
663        Self::with_control_handler(Arc::new(ControlHandler::default()))
664    }
665
666    pub fn forwarding(&self) -> Arc<ForwardingTable> {
667        Arc::clone(&self.forwarding)
668    }
669
670    pub fn register_backend(
671        &mut self,
672        channel: u16,
673        backend: impl Into<Backend>,
674    ) -> Result<(), RouterError> {
675        self.register_backend_arc(channel, Arc::new(backend.into()))
676    }
677
678    pub(crate) fn register_backend_arc(
679        &mut self,
680        channel: u16,
681        backend: Arc<Backend>,
682    ) -> Result<(), RouterError> {
683        if channel == 0 {
684            return Err(RouterError::ReservedChannelZero);
685        }
686        if self.backends.contains_key(&channel) {
687            return Err(RouterError::DuplicateChannel { channel });
688        }
689        self.backends.insert(channel, backend.as_ref().clone());
690        Ok(())
691    }
692
693    /// Start a connection-scoped routing context. Dropping the guard releases
694    /// any control-plane registrations owned by the connection.
695    fn record_module_frame_drop(&self, connection_id: ConnectionId) -> Result<(), RouterError> {
696        let module_id = self
697            .forwarding
698            .module_id_for_connection(connection_id)
699            .map_err(RouterError::Forwarding)?;
700        self.counters
701            .increment_module_frames_dropped_no_route(module_id.as_deref());
702        Ok(())
703    }
704
705    /// A module sent a non-request frame on a (channel, epoch) the daemon holds
706    /// no route for: most often a route the daemon released whose GOODBYE the
707    /// module never received, so the module still believes it is open and keeps
708    /// sending. Count the drop, and tell the module to let go of exactly that
709    /// (channel, epoch) with a route GOODBYE, at most once per
710    /// [`ORPHAN_ROUTE_GOODBYE_INTERVAL`] per connection and channel.
711    ///
712    /// The answer uses `try_send`: it is a best-effort nudge, and if the queue
713    /// refuses it, the module's next frame on that route after the interval
714    /// asks again. A GOODBYE from the module is not answered, since the module
715    /// is already letting go of the route.
716    fn handle_orphan_module_frame(&self, ctx: &RouteCtx, frame: &Frame) -> Result<(), RouterError> {
717        let channel = frame.header.channel;
718        let epoch = frame.header.epoch;
719        let module_id = self
720            .forwarding
721            .module_id_for_connection(ctx.connection_id)
722            .map_err(RouterError::Forwarding)?;
723        self.counters
724            .increment_module_frames_dropped_no_route(module_id.as_deref());
725        if self
726            .forwarding
727            .module_route_epoch_was_allocated(ctx.connection_id, channel, epoch)
728            .map_err(RouterError::Forwarding)?
729        {
730            self.counters
731                .increment_module_frames_dropped_released_route(module_id.as_deref());
732        }
733        if frame.header.ty == FrameType::Goodbye
734            || !self.orphan_goodbyes.claim(ctx.connection_id, channel)
735        {
736            return Ok(());
737        }
738        let goodbye = Frame::build_with_version(
739            frame.header.ver,
740            FrameType::Goodbye,
741            Flags::new(false, Priority::Passive, false),
742            channel,
743            epoch,
744            0,
745            Vec::new(),
746        )
747        .map_err(RouterError::FrameBuild)?;
748        match ctx.egress.try_send(goodbye) {
749            Ok(()) => {
750                self.counters.increment_module_orphan_route_goodbyes_sent();
751                debug!(
752                    connection_id = ctx.connection_id.get(),
753                    module_id = module_id.as_deref().unwrap_or("unknown"),
754                    channel,
755                    epoch,
756                    "answered module frame on a route the daemon does not hold with a route GOODBYE"
757                );
758            }
759            Err(err) => debug!(
760                connection_id = ctx.connection_id.get(),
761                module_id = module_id.as_deref().unwrap_or("unknown"),
762                channel,
763                epoch,
764                error = %err,
765                "could not enqueue route GOODBYE for module frame on a route the daemon does not hold; the next such frame retries"
766            ),
767        }
768        Ok(())
769    }
770
771    pub fn begin_connection(&self) -> RouterConnection {
772        let raw = self.next_connection_id.fetch_add(1, Ordering::Relaxed);
773        let id = ConnectionId::new(raw);
774        let close_receiver = self.forwarding.register_connection_close(id);
775        RouterConnection {
776            id,
777            control_handler: Arc::clone(&self.control),
778            forwarding: Arc::clone(&self.forwarding),
779            close_receiver: Some(close_receiver),
780            orphan_goodbyes: Arc::clone(&self.orphan_goodbyes),
781        }
782    }
783
784    pub(crate) fn route_open_target(&self, frame: &Frame) -> Option<String> {
785        self.control.route_open_target(frame)
786    }
787
788    pub(crate) fn record_caller_endpoints(
789        &self,
790        connection: ConnectionId,
791        endpoints: Option<(std::net::SocketAddr, std::net::SocketAddr)>,
792    ) {
793        self.control.record_caller_endpoints(connection, endpoints);
794    }
795
796    pub(crate) fn defers_privileged_control(&self, ctx: &RouteCtx, frame: &Frame) -> bool {
797        self.control.defers_privileged_control(ctx, frame)
798    }
799
800    pub(crate) fn route_open_capacity_refusal(
801        &self,
802        ctx: &RouteCtx,
803        frame: &Frame,
804        target_module_id: &str,
805        in_flight: usize,
806        limit: usize,
807    ) -> Result<Frame, RouterError> {
808        self.control
809            .route_open_capacity_refusal(ctx, frame, target_module_id, in_flight, limit)
810    }
811
812    pub async fn route_for_connection(
813        &self,
814        ctx: &RouteCtx,
815        frame: Frame,
816    ) -> Result<(), RouterError> {
817        self.route_for_connection_started(ctx, frame, None).await
818    }
819
820    pub(crate) async fn route_for_connection_started(
821        &self,
822        ctx: &RouteCtx,
823        frame: Frame,
824        dispatch_started_at: Option<Instant>,
825    ) -> Result<(), RouterError> {
826        let channel = frame.header.channel;
827        let epoch = frame.header.epoch;
828        let corr = frame.header.corr;
829        if channel == 0 {
830            debug!(
831                connection_id = ctx.connection_id.get(),
832                corr,
833                frame_type = ?frame.header.ty,
834                "routing control frame"
835            );
836            // The connection loop dispatches every control operation directly
837            // except `route.open`, activity writes and privileged requests. Each
838            // task passes a timestamp captured before
839            // spawn, so slow-dispatch timing includes scheduler delay but no
840            // wait in an application-owned queue.
841            let dispatch_started_at = (frame.header.ty == FrameType::Request)
842                .then(|| dispatch_started_at.unwrap_or_else(Instant::now));
843            let responses = self
844                .control
845                .handle_control_frame_timed(ctx, frame, dispatch_started_at)
846                .await?;
847            for response in responses {
848                ctx.egress.send(response).await?;
849            }
850            return Ok(());
851        }
852
853        let data_route = self
854            .forwarding
855            .lookup_data_route(ctx.connection_id, channel, epoch)
856            .map_err(RouterError::Forwarding)?;
857
858        match data_route {
859            DataRoute::Module(DataRouteState::EpochMismatch) => {
860                if frame.header.ty == FrameType::Request {
861                    self.counters
862                        .increment_module_requests_dropped_stale_route();
863                    let err = RouterError::StaleRouteEpoch {
864                        channel,
865                        epoch,
866                        corr,
867                    };
868                    if let Some(error_frame) = err.to_error_frame() {
869                        ctx.egress.send(error_frame).await?;
870                    }
871                } else {
872                    self.handle_orphan_module_frame(ctx, &frame)?;
873                }
874                debug!(
875                    connection_id = ctx.connection_id.get(),
876                    channel, epoch, corr, "dropping module frame for stale route epoch"
877                );
878                return Ok(());
879            }
880            DataRoute::Module(DataRouteState::Reserved) => {
881                if frame.header.ty == FrameType::Request {
882                    self.counters
883                        .increment_module_requests_dropped_stale_route();
884                    let err = RouterError::UnknownChannel {
885                        channel,
886                        epoch,
887                        corr,
888                    };
889                    if let Some(error_frame) = err.to_error_frame() {
890                        ctx.egress.send(error_frame).await?;
891                    }
892                } else {
893                    self.record_module_frame_drop(ctx.connection_id)?;
894                }
895                debug!(
896                    connection_id = ctx.connection_id.get(),
897                    channel, epoch, corr, "dropping module frame for reserved route handle"
898                );
899                return Ok(());
900            }
901            DataRoute::Module(DataRouteState::Absent) => {
902                if frame.header.ty == FrameType::Request {
903                    self.counters
904                        .increment_module_requests_dropped_stale_route();
905                    let err = RouterError::UnknownChannel {
906                        channel,
907                        epoch,
908                        corr,
909                    };
910                    if let Some(error_frame) = err.to_error_frame() {
911                        ctx.egress.send(error_frame).await?;
912                    }
913                } else {
914                    self.handle_orphan_module_frame(ctx, &frame)?;
915                }
916                debug!(
917                    connection_id = ctx.connection_id.get(),
918                    channel, epoch, corr, "dropping module frame for absent route handle"
919                );
920                return Ok(());
921            }
922            DataRoute::Module(DataRouteState::Bound(route)) => {
923                if frame.header.ty == FrameType::Goodbye {
924                    if let RouteRelease::Removed(target) = self
925                        .forwarding
926                        .release_module_route(ctx.connection_id, channel, epoch)
927                        .map_err(RouterError::Forwarding)?
928                    {
929                        let mut goodbye = frame;
930                        goodbye.header.channel = target.channel;
931                        goodbye.header.epoch = target.epoch;
932                        if let Err(err) = target.sink.try_send(goodbye) {
933                            if target.close_on_delivery_failure()
934                                && self
935                                    .forwarding
936                                    .escalate_client_delivery_failure(
937                                        target.connection_id,
938                                        target.channel,
939                                        target.epoch,
940                                        CloseReason::new(
941                                            "route_goodbye_delivery_failed",
942                                            format!(
943                                                "failed to enqueue route GOODBYE for client channel {}: {err}",
944                                                target.channel
945                                            ),
946                                        ),
947                                        UndeliveredFrame {
948                                            module_id: Some(&route.module_id),
949                                            sink: &target.sink,
950                                        },
951                                    )
952                                    .map_err(RouterError::Forwarding)?
953                            {
954                                self.counters.increment_goodbye_relay_client_failed();
955                            }
956                        }
957                    }
958                    return Ok(());
959                }
960
961                // Accounting allows an oversized frame to occupy an empty queue
962                // on other send paths. Refuse it at this splice before admission,
963                // so the next ordinary reply cannot close a multiplexed client.
964                let bytes = queued_frame_bytes(&frame);
965                let limit = route.client_sink.accounting.byte_budget;
966                if bytes > limit {
967                    let ty = frame.header.ty;
968                    drop(frame);
969                    return self
970                        .refuse_oversized_module_frame(ctx, &route, ty, corr, bytes)
971                        .await;
972                }
973
974                // A terminal frame ends the request at the module whether or not
975                // the client can still take it, so its credit is released before
976                // delivery is attempted. Releasing only after a successful
977                // enqueue would leave a drain counting a finished request until
978                // the client connection's cleanup removes the route.
979                let releases_credit = is_terminal_frame(frame.header.ty);
980                if releases_credit {
981                    route.flow.release_corr(corr);
982                }
983                let mut frame = frame;
984                frame.header.channel = route.client_channel;
985                frame.header.epoch = route.client_epoch;
986                if let Err(err) = route.client_sink.try_send(frame) {
987                    if self
988                        .forwarding
989                        .escalate_client_delivery_failure(
990                            route.client_connection_id,
991                            route.client_channel,
992                            route.client_epoch,
993                            CloseReason::new(
994                                "module_to_client_delivery_failed",
995                                format!(
996                                    "failed to enqueue module frame for client channel {} corr {corr}: {err}",
997                                    route.client_channel
998                                ),
999                            ),
1000                            UndeliveredFrame {
1001                                module_id: Some(&route.module_id),
1002                                sink: &route.client_sink,
1003                            },
1004                        )
1005                        .map_err(RouterError::Forwarding)?
1006                    {
1007                        self.counters
1008                            .increment_client_egress_close_delivery_failed();
1009                    }
1010                    return Ok(());
1011                }
1012                return Ok(());
1013            }
1014            DataRoute::Client(DataRouteState::EpochMismatch) => {
1015                if frame.header.ty == FrameType::Request {
1016                    self.counters.increment_client_frames_dropped_stale_route();
1017                    // Dropped before forwarding; a re-bind retry cannot double-execute this request.
1018                    let err = RouterError::StaleRouteEpoch {
1019                        channel,
1020                        epoch,
1021                        corr,
1022                    };
1023                    if let Some(error_frame) = err.to_error_frame() {
1024                        ctx.egress.send(error_frame).await?;
1025                    }
1026                }
1027                debug!(
1028                    connection_id = ctx.connection_id.get(),
1029                    channel, epoch, corr, "dropping client frame for stale route epoch"
1030                );
1031                return Ok(());
1032            }
1033            DataRoute::Client(DataRouteState::Reserved) => {
1034                if frame.header.ty == FrameType::Request {
1035                    let err = RouterError::UnknownChannel {
1036                        channel,
1037                        epoch,
1038                        corr,
1039                    };
1040                    if let Some(error_frame) = err.to_error_frame() {
1041                        ctx.egress.send(error_frame).await?;
1042                    }
1043                }
1044                return Ok(());
1045            }
1046            DataRoute::Client(DataRouteState::Bound(route)) => {
1047                if frame.header.ty == FrameType::Goodbye {
1048                    let _ = self
1049                        .control
1050                        .handle_route_goodbye(ctx.connection_id, channel, epoch)?;
1051                    return Ok(());
1052                }
1053                return self.forward_backend.handle_bound(frame, route).await;
1054            }
1055            DataRoute::Client(DataRouteState::Absent) => {}
1056        }
1057
1058        if let Some(backend) = self.backends.get(&channel) {
1059            return backend.handle(ctx.clone(), frame).await;
1060        }
1061        if frame.header.ty == FrameType::Request {
1062            let err = RouterError::UnknownChannel {
1063                channel,
1064                epoch,
1065                corr,
1066            };
1067            if let Some(error_frame) = err.to_error_frame() {
1068                ctx.egress.send(error_frame).await?;
1069            }
1070        }
1071        Ok(())
1072    }
1073
1074    async fn refuse_oversized_module_frame(
1075        &self,
1076        ctx: &RouteCtx,
1077        route: &RouteBinding,
1078        ty: FrameType,
1079        corr: u64,
1080        bytes: usize,
1081    ) -> Result<(), RouterError> {
1082        let limit = route.client_sink.accounting.byte_budget;
1083        if ty == FrameType::Push {
1084            // Deployed Rust clients cannot decode a new route.closed reason.
1085            // Until that vocabulary can be extended compatibly, an oversized
1086            // unsolicited push still closes the connection, with its cause named.
1087            if self
1088                .forwarding
1089                .escalate_client_delivery_failure(
1090                    route.client_connection_id,
1091                    route.client_channel,
1092                    route.client_epoch,
1093                    CloseReason::new(
1094                        error_codes::FRAME_TOO_LARGE,
1095                        format!(
1096                            "oversized push: size={bytes} limit={limit} (header plus body bytes)"
1097                        ),
1098                    ),
1099                    UndeliveredFrame {
1100                        module_id: Some(&route.module_id),
1101                        sink: &route.client_sink,
1102                    },
1103                )
1104                .map_err(RouterError::Forwarding)?
1105            {
1106                self.counters
1107                    .increment_client_egress_close_delivery_failed();
1108            }
1109            return Ok(());
1110        }
1111
1112        warn!(
1113            connection_id = route.client_connection_id.get(),
1114            module_id = %route.module_id,
1115            client_principal = %principal_label(&route.principal),
1116            client_channel = route.client_channel,
1117            frame_type = ?ty,
1118            frame_bytes = bytes,
1119            byte_limit = limit,
1120            "refusing oversized module-to-client frame with ERROR frame_too_large"
1121        );
1122        if ty == FrameType::Request {
1123            // A reverse request belongs to the module, not to a waiting client.
1124            // Answer its sender without forwarding any part of it to the client.
1125            ctx.egress
1126                .send(frame_too_large_error(
1127                    route.module_channel,
1128                    route.module_epoch,
1129                    corr,
1130                    bytes,
1131                    limit,
1132                )?)
1133                .await?;
1134        } else {
1135            // A refused stream chunk also ends the waiting request. Releasing
1136            // its credit prevents a finished request from keeping a drain alive.
1137            route.flow.release_corr(corr);
1138            let error = frame_too_large_error(
1139                route.client_channel,
1140                route.client_epoch,
1141                corr,
1142                bytes,
1143                limit,
1144            )?;
1145            // This runs on the module connection's frame reader, so it must not
1146            // wait for client egress: a stuck client would stall every route of
1147            // the module. Enqueue it like the reply it replaces, and treat a full
1148            // queue exactly as ordinary backpressure.
1149            if let Err(err) = route.client_sink.try_send(error) {
1150                if self
1151                    .forwarding
1152                    .escalate_client_delivery_failure(
1153                        route.client_connection_id,
1154                        route.client_channel,
1155                        route.client_epoch,
1156                        CloseReason::new(
1157                            "module_to_client_delivery_failed",
1158                            format!(
1159                                "failed to enqueue frame_too_large for client channel {} corr {corr}: {err}",
1160                                route.client_channel
1161                            ),
1162                        ),
1163                        UndeliveredFrame {
1164                            module_id: Some(&route.module_id),
1165                            sink: &route.client_sink,
1166                        },
1167                    )
1168                    .map_err(RouterError::Forwarding)?
1169                {
1170                    self.counters
1171                        .increment_client_egress_close_delivery_failed();
1172                }
1173            }
1174        }
1175        Ok(())
1176    }
1177}
1178
1179impl Default for Router {
1180    fn default() -> Self {
1181        Self::with_default_self_handler()
1182    }
1183}
1184
1185/// Connection-scoped cleanup guard returned by [`Router::begin_connection`].
1186#[must_use]
1187pub struct RouterConnection {
1188    id: ConnectionId,
1189    control_handler: Arc<ControlHandler>,
1190    forwarding: Arc<ForwardingTable>,
1191    close_receiver: Option<ConnectionCloseReceiver>,
1192    orphan_goodbyes: Arc<OrphanGoodbyeLimiter>,
1193}
1194
1195impl RouterConnection {
1196    pub fn id(&self) -> ConnectionId {
1197        self.id
1198    }
1199
1200    pub(crate) fn take_close_receiver(&mut self) -> ConnectionCloseReceiver {
1201        self.close_receiver
1202            .take()
1203            .expect("connection close receiver can only be taken once")
1204    }
1205}
1206
1207impl Drop for RouterConnection {
1208    fn drop(&mut self) {
1209        self.forwarding.unregister_connection_close(self.id);
1210        self.orphan_goodbyes.forget_connection(self.id);
1211        // GOODBYE (explicit) and connection-drop cleanup both call the same
1212        // idempotent deregistration path.
1213        let _ = self.control_handler.cleanup_connection(self.id);
1214    }
1215}
1216
1217/// Minimal in-memory backend used by tests and early wiring: it emits a
1218/// `RESPONSE` on the same channel/correlation id with the exact same body bytes.
1219#[derive(Debug, Default, Clone, Copy)]
1220pub struct EchoBackend;
1221
1222impl EchoBackend {
1223    pub async fn handle(&self, ctx: RouteCtx, frame: Frame) -> Result<(), RouterError> {
1224        let response = Frame::build_with_version(
1225            frame.header.ver,
1226            FrameType::Response,
1227            frame.header.flags,
1228            frame.header.channel,
1229            frame.header.epoch,
1230            frame.header.corr,
1231            frame.body,
1232        )
1233        .map_err(RouterError::FrameBuild)?;
1234        ctx.egress.send(response).await
1235    }
1236}
1237
1238/// Data-plane backend that splices client frames to the module connection bound at attach time.
1239#[derive(Debug, Clone)]
1240pub struct ForwardBackend {
1241    forwarding: Arc<ForwardingTable>,
1242}
1243
1244impl ForwardBackend {
1245    pub fn new(forwarding: Arc<ForwardingTable>) -> Self {
1246        Self { forwarding }
1247    }
1248
1249    pub async fn handle(&self, ctx: RouteCtx, frame: Frame) -> Result<(), RouterError> {
1250        let channel = frame.header.channel;
1251        let corr = frame.header.corr;
1252        let route = match self
1253            .forwarding
1254            .lookup_data_route(ctx.connection_id, channel, frame.header.epoch)
1255            .map_err(RouterError::Forwarding)?
1256        {
1257            DataRoute::Client(DataRouteState::Bound(route)) => route,
1258            DataRoute::Client(_) | DataRoute::Module(_) => {
1259                return Err(RouterError::UnknownChannel {
1260                    channel,
1261                    epoch: frame.header.epoch,
1262                    corr,
1263                });
1264            }
1265        };
1266        self.handle_bound(frame, route).await
1267    }
1268
1269    pub(crate) async fn handle_bound(
1270        &self,
1271        frame: Frame,
1272        route: Arc<RouteBinding>,
1273    ) -> Result<(), RouterError> {
1274        let channel = frame.header.channel;
1275        let corr = frame.header.corr;
1276        let frame_type = frame.header.ty;
1277
1278        // CANCEL and other non-REQUEST frames bypass the request-credit window;
1279        // the original request's credit is freed only by the module's terminal frame.
1280        let acquired_credit = frame_type == FrameType::Request;
1281        if acquired_credit {
1282            if let Err(err) = route
1283                .flow
1284                .acquire_tagged(corr, frame.header.flags.is_subscription())
1285                .await
1286            {
1287                // `module_reloading` here is answered BEFORE the frame is
1288                // forwarded, so the module never sees the request and callers
1289                // may re-dispatch it after reopening the route. That is a wire
1290                // guarantee documented on `error_codes::MODULE_RELOADING`; do
1291                // not emit this code for a request that already reached
1292                // `module_sink.send` below.
1293                if self
1294                    .forwarding
1295                    .endpoint_is_draining(route.module_endpoint)
1296                    .map_err(RouterError::Forwarding)?
1297                {
1298                    return Err(RouterError::route_error_with_epoch(
1299                        channel,
1300                        frame.header.epoch,
1301                        corr,
1302                        "module_reloading",
1303                        format!("module endpoint for route channel {channel} is reloading"),
1304                    ));
1305                }
1306                return Err(RouterError::backend_with_epoch(
1307                    channel,
1308                    frame.header.epoch,
1309                    corr,
1310                    format!("{err} for route channel {channel}"),
1311                ));
1312            }
1313        }
1314
1315        let mut frame = frame;
1316        frame.header.channel = route.module_channel;
1317        frame.header.epoch = route.module_epoch;
1318        let result = route.module_sink.send(frame).await.map_err(|err| {
1319            RouterError::backend_with_epoch(channel, route.client_epoch, corr, err.to_string())
1320        });
1321        if acquired_credit && result.is_err() {
1322            route.flow.release_corr(corr);
1323        }
1324        result
1325    }
1326}
1327
1328fn is_terminal_frame(frame_type: FrameType) -> bool {
1329    matches!(
1330        frame_type,
1331        FrameType::Response | FrameType::Error | FrameType::StreamEnd
1332    )
1333}
1334
1335/// Typed router errors. Routable failures can be translated to canonical JSON
1336/// `ERROR` frames with [`RouterError::to_error_frame`].
1337#[derive(Debug, Clone, PartialEq, Eq)]
1338pub enum RouterError {
1339    ReservedChannelZero,
1340    DuplicateChannel {
1341        channel: u16,
1342    },
1343    UnknownChannel {
1344        channel: u16,
1345        epoch: u32,
1346        corr: u64,
1347    },
1348    StaleRouteEpoch {
1349        channel: u16,
1350        epoch: u32,
1351        corr: u64,
1352    },
1353    Backend {
1354        channel: u16,
1355        epoch: u32,
1356        corr: u64,
1357        message: String,
1358    },
1359    RouteError {
1360        channel: u16,
1361        epoch: u32,
1362        corr: u64,
1363        code: String,
1364        message: String,
1365    },
1366    FrameBuild(FrameBuildError),
1367    Forwarding(ForwardingError),
1368}
1369
1370impl RouterError {
1371    pub fn backend(channel: u16, corr: u64, message: impl Into<String>) -> Self {
1372        Self::backend_with_epoch(channel, 0, corr, message)
1373    }
1374
1375    pub fn backend_with_epoch(
1376        channel: u16,
1377        epoch: u32,
1378        corr: u64,
1379        message: impl Into<String>,
1380    ) -> Self {
1381        Self::Backend {
1382            channel,
1383            epoch,
1384            corr,
1385            message: message.into(),
1386        }
1387    }
1388
1389    pub fn route_error(
1390        channel: u16,
1391        corr: u64,
1392        code: impl Into<String>,
1393        message: impl Into<String>,
1394    ) -> Self {
1395        Self::route_error_with_epoch(channel, 0, corr, code, message)
1396    }
1397
1398    pub fn route_error_with_epoch(
1399        channel: u16,
1400        epoch: u32,
1401        corr: u64,
1402        code: impl Into<String>,
1403        message: impl Into<String>,
1404    ) -> Self {
1405        Self::RouteError {
1406            channel,
1407            epoch,
1408            corr,
1409            code: code.into(),
1410            message: message.into(),
1411        }
1412    }
1413
1414    /// Translate route failures that belong on the wire into an `ERROR` frame.
1415    pub fn to_error_frame(&self) -> Option<Frame> {
1416        match self {
1417            Self::UnknownChannel {
1418                channel,
1419                epoch,
1420                corr,
1421            } => error_frame(
1422                *channel,
1423                *epoch,
1424                *corr,
1425                error_codes::UNKNOWN_CHANNEL,
1426                format!("unknown channel {channel}"),
1427            ),
1428            Self::StaleRouteEpoch {
1429                channel,
1430                epoch,
1431                corr,
1432            } => error_frame(
1433                *channel,
1434                *epoch,
1435                *corr,
1436                error_codes::STALE_ROUTE_EPOCH,
1437                format!("stale route epoch for channel {channel}"),
1438            ),
1439            Self::Backend {
1440                channel,
1441                epoch,
1442                corr,
1443                message,
1444            } => error_frame(*channel, *epoch, *corr, "backend_error", message.clone()),
1445            Self::RouteError {
1446                channel,
1447                epoch,
1448                corr,
1449                code,
1450                message,
1451            } => error_frame(*channel, *epoch, *corr, code, message.clone()),
1452            Self::ReservedChannelZero
1453            | Self::DuplicateChannel { .. }
1454            | Self::FrameBuild(_)
1455            | Self::Forwarding(_) => None,
1456        }
1457    }
1458}
1459
1460fn error_frame(channel: u16, epoch: u32, corr: u64, code: &str, message: String) -> Option<Frame> {
1461    let body = serde_json::to_vec(&ErrorBody {
1462        code: code.to_string(),
1463        message,
1464        detail: None,
1465    })
1466    .ok()?;
1467
1468    Frame::build(
1469        FrameType::Error,
1470        Flags::new(false, Priority::Passive, false),
1471        channel,
1472        epoch,
1473        corr,
1474        body,
1475    )
1476    .ok()
1477}
1478
1479fn frame_too_large_error(
1480    channel: u16,
1481    epoch: u32,
1482    corr: u64,
1483    bytes: usize,
1484    limit: usize,
1485) -> Result<Frame, RouterError> {
1486    let body = serde_json::to_vec(&ErrorBody {
1487        code: error_codes::FRAME_TOO_LARGE.to_string(),
1488        message: format!(
1489            "module frame of {bytes} bytes exceeds client egress limit of {limit} bytes"
1490        ),
1491        detail: Some(serde_json::json!({ "frame_bytes": bytes, "byte_limit": limit })),
1492    })
1493    .expect("serializing a frame size error");
1494    Frame::build(
1495        FrameType::Error,
1496        Flags::new(false, Priority::Passive, false).with_daemon_origin(),
1497        channel,
1498        epoch,
1499        corr,
1500        body,
1501    )
1502    .map_err(RouterError::FrameBuild)
1503}
1504
1505impl fmt::Display for RouterError {
1506    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1507        match self {
1508            Self::ReservedChannelZero => write!(f, "channel 0 is reserved for subc"),
1509            Self::DuplicateChannel { channel } => {
1510                write!(f, "backend already registered for channel {channel}")
1511            }
1512            Self::UnknownChannel { channel, corr, .. } => {
1513                write!(f, "unknown channel {channel} for corr {corr}")
1514            }
1515            Self::StaleRouteEpoch { channel, corr, .. } => {
1516                write!(f, "stale route epoch for channel {channel} corr {corr}")
1517            }
1518            Self::Backend {
1519                channel,
1520                corr,
1521                message,
1522                ..
1523            } => write!(
1524                f,
1525                "backend error on channel {channel} corr {corr}: {message}"
1526            ),
1527            Self::RouteError {
1528                channel,
1529                corr,
1530                code,
1531                message,
1532                ..
1533            } => write!(
1534                f,
1535                "route error {code} on channel {channel} corr {corr}: {message}"
1536            ),
1537            Self::FrameBuild(err) => write!(f, "failed to build routed frame: {err}"),
1538            Self::Forwarding(err) => write!(f, "forwarding error: {err}"),
1539        }
1540    }
1541}
1542
1543impl Error for RouterError {
1544    fn source(&self) -> Option<&(dyn Error + 'static)> {
1545        match self {
1546            Self::FrameBuild(err) => Some(err),
1547            Self::Forwarding(err) => Some(err),
1548            Self::ReservedChannelZero
1549            | Self::DuplicateChannel { .. }
1550            | Self::UnknownChannel { .. }
1551            | Self::StaleRouteEpoch { .. }
1552            | Self::Backend { .. }
1553            | Self::RouteError { .. } => None,
1554        }
1555    }
1556}
1557
1558#[cfg(test)]
1559mod tests {
1560    use super::*;
1561    use crate::{
1562        forwarding::RouteBindRelayOutcome,
1563        supervise::{ModuleSpec, RestartPolicy, Supervisor, SupervisorHandle},
1564        ControlHandler, Registry,
1565    };
1566    use std::{
1567        sync::{mpsc as std_mpsc, Arc},
1568        time::Duration,
1569    };
1570    use subc_control::ModuleProtocol;
1571    use subc_protocol::{manifest::Concurrency, ErrorBody, Flags, FrameType, Priority};
1572    use tokio::sync::mpsc;
1573
1574    pub(crate) use crate::router::test_log::{captured_logs, log_capture};
1575
1576    fn logged_millis(logs: &str, field: &str) -> u64 {
1577        logs.split_whitespace()
1578            .find_map(|part| part.strip_prefix(field))
1579            .and_then(|value| value.parse().ok())
1580            .unwrap_or_else(|| panic!("missing numeric {field} in logs: {logs}"))
1581    }
1582
1583    fn request(channel: u16, corr: u64, body: &[u8]) -> Frame {
1584        Frame::build(
1585            FrameType::Request,
1586            Flags::new(true, Priority::Interactive, false),
1587            channel,
1588            0,
1589            corr,
1590            body.to_vec(),
1591        )
1592        .unwrap()
1593    }
1594
1595    fn ping(corr: u64) -> Frame {
1596        Frame::build(
1597            FrameType::Ping,
1598            Flags::new(false, Priority::Passive, false),
1599            0,
1600            0,
1601            corr,
1602            Vec::new(),
1603        )
1604        .unwrap()
1605    }
1606
1607    fn route_ctx() -> (RouteCtx, mpsc::Receiver<crate::router::OutboundFrame>) {
1608        let (tx, rx) = mpsc::channel(8);
1609        (
1610            RouteCtx {
1611                connection_id: ConnectionId::LOCAL,
1612                egress: FrameSink::new(tx),
1613            },
1614            rx,
1615        )
1616    }
1617
1618    #[tokio::test]
1619    async fn echo_backend_returns_response_with_byte_identical_body() {
1620        let mut router = Router::with_default_self_handler();
1621        router.register_backend(7, EchoBackend).unwrap();
1622        let (ctx, mut rx) = route_ctx();
1623        let body = b"{not parsed}\0\xff";
1624
1625        router
1626            .route_for_connection(&ctx, request(7, 123, body))
1627            .await
1628            .unwrap();
1629        let response = rx.recv().await.unwrap();
1630
1631        assert_eq!(response.header.ty, FrameType::Response);
1632        assert_eq!(response.header.channel, 7);
1633        assert_eq!(response.header.corr, 123);
1634        assert_eq!(response.body, body);
1635        assert!(rx.try_recv().is_err());
1636    }
1637
1638    #[tokio::test]
1639    async fn unknown_channel_emits_canonical_error_frame() {
1640        let router = Router::with_default_self_handler();
1641        let (ctx, mut rx) = route_ctx();
1642
1643        router
1644            .route_for_connection(&ctx, request(99, 5, b"payload"))
1645            .await
1646            .unwrap();
1647        let error_frame = rx.recv().await.unwrap();
1648
1649        assert_eq!(error_frame.header.ty, FrameType::Error);
1650        assert_eq!(error_frame.header.channel, 99);
1651        assert_eq!(error_frame.header.corr, 5);
1652        let body: ErrorBody = serde_json::from_slice(&error_frame.body).unwrap();
1653        assert_eq!(body.code, "unknown_channel");
1654        assert_eq!(body.message, "unknown channel 99");
1655    }
1656
1657    #[tokio::test]
1658    async fn channel_zero_uses_control_handler_not_backend_registry() {
1659        let mut router = Router::with_default_self_handler();
1660        router.register_backend(1, EchoBackend).unwrap();
1661        let (ctx, mut rx) = route_ctx();
1662
1663        router.route_for_connection(&ctx, ping(77)).await.unwrap();
1664        let response = rx.recv().await.unwrap();
1665
1666        assert_eq!(response.header.ty, FrameType::Pong);
1667        assert_eq!(response.header.channel, 0);
1668        assert_eq!(response.header.corr, 77);
1669        assert!(response.body.is_empty());
1670    }
1671
1672    #[tokio::test]
1673    async fn slow_control_dispatch_logs_decoded_op_and_elapsed_time() {
1674        let control = Arc::new(
1675            ControlHandler::new(Arc::new(Registry::default()))
1676                .with_control_dispatch_delay(Duration::from_millis(1050)),
1677        );
1678        let router = Router::with_control_handler(control);
1679        let (ctx, mut rx) = route_ctx();
1680        let (output, guard) = log_capture(tracing::Level::WARN);
1681
1682        router
1683            .route_for_connection(&ctx, request(0, 41, br#"{"op":"server.describe"}"#))
1684            .await
1685            .expect("slow request routes");
1686        assert!(rx.recv().await.is_some(), "request receives a response");
1687        drop(guard);
1688
1689        let logs = captured_logs(&output);
1690        assert!(logs.contains("slow control dispatch"));
1691        assert!(logs.contains("op=server.describe"));
1692        assert!(logs.contains("connection_id=0"));
1693        assert!(logs.contains("corr=41"));
1694        assert!(
1695            logged_millis(&logs, "elapsed_ms=") >= 1050,
1696            "elapsed must include the injected handler delay: {logs}"
1697        );
1698    }
1699
1700    #[tokio::test]
1701    async fn fast_control_dispatch_emits_arrival_without_slow_warning() {
1702        let router = Router::with_default_self_handler();
1703        let (ctx, mut rx) = route_ctx();
1704        let (output, guard) = log_capture(tracing::Level::DEBUG);
1705
1706        router
1707            .route_for_connection(&ctx, request(0, 42, br#"{"op":"server.describe"}"#))
1708            .await
1709            .expect("fast request routes");
1710        assert!(rx.recv().await.is_some(), "request receives a response");
1711        drop(guard);
1712
1713        let logs = captured_logs(&output);
1714        assert!(logs.contains("control dispatch op=server.describe connection_id=0 corr=42"));
1715        assert!(!logs.contains("slow control dispatch"));
1716    }
1717
1718    #[tokio::test]
1719    async fn control_dispatch_arrival_is_hidden_at_info() {
1720        let router = Router::with_default_self_handler();
1721        let (ctx, mut rx) = route_ctx();
1722        let (output, guard) = log_capture(tracing::Level::INFO);
1723
1724        router
1725            .route_for_connection(&ctx, request(0, 43, br#"{"op":"server.describe"}"#))
1726            .await
1727            .expect("fast request routes");
1728        assert!(rx.recv().await.is_some(), "request receives a response");
1729        drop(guard);
1730
1731        assert!(
1732            !captured_logs(&output).contains("control dispatch"),
1733            "arrival logging must stay hidden at INFO"
1734        );
1735    }
1736
1737    #[tokio::test]
1738    async fn supervisor_list_logs_contended_snapshot_lock_only() {
1739        let registry = Arc::new(Registry::default());
1740        let handle = SupervisorHandle::new();
1741        let supervisor = Supervisor::new_for_test(Arc::clone(&registry), RestartPolicy::default())
1742            .with_handle(handle.clone());
1743        let module = supervisor
1744            .supervise_configured(
1745                ModuleSpec {
1746                    module_id: "held-module".to_string(),
1747                    program: "test-module".into(),
1748                    args: Vec::new(),
1749                    env: Vec::new(),
1750                    reserved: false,
1751                    reserved_prefixes: Vec::new(),
1752                    protocol: ModuleProtocol::Subc,
1753                    overlap: Default::default(),
1754                },
1755                false,
1756            )
1757            .expect("disabled test module is supervised");
1758        let router = Router::with_control_handler(Arc::new(
1759            ControlHandler::new(Arc::clone(&registry)).with_supervisor(handle),
1760        ));
1761        let (ctx, mut rx) = route_ctx();
1762        let (acquired, ready) = std_mpsc::channel();
1763        let holder = module.hold_snapshot_for_test(acquired, Duration::from_millis(400));
1764        ready.recv().expect("holder acquired snapshot lock");
1765        let (output, guard) = log_capture(tracing::Level::WARN);
1766
1767        router
1768            .route_for_connection(&ctx, request(0, 44, br#"{"op":"supervisor.list"}"#))
1769            .await
1770            .expect("list request routes after the lock releases");
1771        assert!(
1772            rx.recv().await.is_some(),
1773            "list request receives a response"
1774        );
1775        holder.join().expect("snapshot holder exits cleanly");
1776        drop(guard);
1777
1778        let logs = captured_logs(&output);
1779        assert!(logs.contains("slow snapshot lock"));
1780        assert!(logs.contains("module_id=held-module"));
1781        assert!(logs.contains("caller=list"));
1782        assert!(
1783            logged_millis(&logs, "waited_ms=") >= 250,
1784            "wait must exceed the slow-lock threshold: {logs}"
1785        );
1786
1787        let (output, guard) = log_capture(tracing::Level::WARN);
1788        router
1789            .route_for_connection(&ctx, request(0, 45, br#"{"op":"supervisor.list"}"#))
1790            .await
1791            .expect("uncontended list request routes");
1792        assert!(
1793            rx.recv().await.is_some(),
1794            "uncontended list receives a response"
1795        );
1796        drop(guard);
1797        assert!(
1798            !captured_logs(&output).contains("slow snapshot lock"),
1799            "uncontended list acquisition must not warn"
1800        );
1801    }
1802
1803    #[tokio::test]
1804    async fn full_module_to_client_sink_requests_client_close_without_erroring_module() {
1805        let forwarding = Arc::new(ForwardingTable::default());
1806        let control = Arc::new(ControlHandler::with_forwarding(
1807            Arc::new(crate::Registry::default()),
1808            Arc::clone(&forwarding),
1809        ));
1810        let router = Router::with_control_handler(control);
1811        let module_connection = ConnectionId::new(10);
1812        let client_connection = ConnectionId::new(20);
1813        let mut close_receiver = forwarding.register_connection_close(client_connection);
1814        let (module_tx, _module_rx) = mpsc::channel(1);
1815        forwarding
1816            .register_module_connection(
1817                module_connection,
1818                "full-sink-provider".to_string(),
1819                1,
1820                Concurrency::ModuleManaged,
1821                FrameSink::new(module_tx),
1822            )
1823            .unwrap();
1824        let (client_tx, mut client_rx) = mpsc::channel(1);
1825        let pending = forwarding
1826            .begin_route_bind_relay_for_test(
1827                client_connection,
1828                FrameSink::new(client_tx),
1829                700,
1830                "full-sink-provider",
1831            )
1832            .unwrap();
1833        forwarding
1834            .complete_pending_relay(
1835                module_connection,
1836                pending.corr,
1837                RouteBindRelayOutcome::Accepted,
1838            )
1839            .unwrap();
1840
1841        let (module_egress_tx, _module_egress_rx) = mpsc::channel(1);
1842        let module_ctx = RouteCtx {
1843            connection_id: module_connection,
1844            egress: FrameSink::new(module_egress_tx),
1845        };
1846        let terminal = Frame::build(
1847            FrameType::Response,
1848            Flags::new(false, Priority::Interactive, true),
1849            pending.module_channel,
1850            pending.module_epoch,
1851            701,
1852            b"terminal".to_vec(),
1853        )
1854        .unwrap();
1855
1856        router
1857            .route_for_connection(&module_ctx, terminal)
1858            .await
1859            .unwrap();
1860        let reason = tokio::time::timeout(Duration::from_secs(1), &mut close_receiver)
1861            .await
1862            .expect("close request should be sent for the full client sink")
1863            .expect("close sender should include a reason");
1864        assert!(
1865            reason
1866                .to_string()
1867                .contains("module_to_client_delivery_failed"),
1868            "unexpected close reason: {reason}"
1869        );
1870        assert_eq!(client_rx.try_recv().unwrap().header.corr, 700);
1871        assert!(client_rx.try_recv().is_err());
1872        assert_eq!(
1873            router.counters.snapshot()["client_egress_close_delivery_failed"],
1874            1
1875        );
1876    }
1877
1878    /// A terminal frame ends the request at the module even when the client
1879    /// cannot take it, so the drain must stop counting it at once rather than
1880    /// when the client connection's cleanup later removes the route.
1881    #[tokio::test]
1882    async fn terminal_frame_releases_its_credit_even_when_client_delivery_fails() {
1883        let forwarding = Arc::new(ForwardingTable::default());
1884        let control = Arc::new(ControlHandler::with_forwarding(
1885            Arc::new(crate::Registry::default()),
1886            Arc::clone(&forwarding),
1887        ));
1888        let router = Router::with_control_handler(control);
1889        let module_connection = ConnectionId::new(11);
1890        let client_connection = ConnectionId::new(21);
1891        let _close_receiver = forwarding.register_connection_close(client_connection);
1892        let (module_tx, _module_rx) = mpsc::channel(1);
1893        forwarding
1894            .register_module_connection(
1895                module_connection,
1896                "credit-provider".to_string(),
1897                1,
1898                Concurrency::ModuleManaged,
1899                FrameSink::new(module_tx),
1900            )
1901            .unwrap();
1902        // Capacity one, filled by the route.open response, so the terminal
1903        // frame below cannot be enqueued for the client.
1904        let (client_tx, _client_rx) = mpsc::channel(1);
1905        let pending = forwarding
1906            .begin_route_bind_relay_for_test(
1907                client_connection,
1908                FrameSink::new(client_tx),
1909                800,
1910                "credit-provider",
1911            )
1912            .unwrap();
1913        forwarding
1914            .complete_pending_relay(
1915                module_connection,
1916                pending.corr,
1917                RouteBindRelayOutcome::Accepted,
1918            )
1919            .unwrap();
1920        let DataRoute::Client(DataRouteState::Bound(route)) = forwarding
1921            .lookup_data_route(
1922                client_connection,
1923                pending.client_channel,
1924                pending.client_epoch,
1925            )
1926            .unwrap()
1927        else {
1928            panic!("expected a bound client route");
1929        };
1930        route.flow.acquire_tagged(801, false).await.unwrap();
1931        assert_eq!(route.flow.drain_in_flight(), 1);
1932
1933        let (module_egress_tx, _module_egress_rx) = mpsc::channel(1);
1934        let module_ctx = RouteCtx {
1935            connection_id: module_connection,
1936            egress: FrameSink::new(module_egress_tx),
1937        };
1938        let terminal = Frame::build(
1939            FrameType::Response,
1940            Flags::new(false, Priority::Interactive, true),
1941            pending.module_channel,
1942            pending.module_epoch,
1943            801,
1944            b"terminal".to_vec(),
1945        )
1946        .unwrap();
1947        router
1948            .route_for_connection(&module_ctx, terminal)
1949            .await
1950            .unwrap();
1951
1952        assert_eq!(
1953            router.counters.snapshot()["client_egress_close_delivery_failed"],
1954            1,
1955            "the client delivery must have failed for this test to mean anything"
1956        );
1957        assert_eq!(
1958            route.flow.drain_in_flight(),
1959            0,
1960            "the module's terminal frame must release its credit even though the client could not take it"
1961        );
1962    }
1963
1964    #[tokio::test]
1965    async fn full_route_goodbye_sink_requests_target_close_without_erroring_module() {
1966        let forwarding = Arc::new(ForwardingTable::default());
1967        let control = Arc::new(ControlHandler::with_forwarding(
1968            Arc::new(crate::Registry::default()),
1969            Arc::clone(&forwarding),
1970        ));
1971        let router = Router::with_control_handler(control);
1972        let module_connection = ConnectionId::new(30);
1973        let client_connection = ConnectionId::new(40);
1974        let mut close_receiver = forwarding.register_connection_close(client_connection);
1975        let (module_tx, _module_rx) = mpsc::channel(1);
1976        forwarding
1977            .register_module_connection(
1978                module_connection,
1979                "goodbye-full-provider".to_string(),
1980                1,
1981                Concurrency::ModuleManaged,
1982                FrameSink::new(module_tx),
1983            )
1984            .unwrap();
1985        let (client_tx, mut client_rx) = mpsc::channel(1);
1986        let pending = forwarding
1987            .begin_route_bind_relay_for_test(
1988                client_connection,
1989                FrameSink::new(client_tx),
1990                800,
1991                "goodbye-full-provider",
1992            )
1993            .unwrap();
1994        forwarding
1995            .complete_pending_relay(
1996                module_connection,
1997                pending.corr,
1998                RouteBindRelayOutcome::Accepted,
1999            )
2000            .unwrap();
2001
2002        let (module_egress_tx, _module_egress_rx) = mpsc::channel(1);
2003        let module_ctx = RouteCtx {
2004            connection_id: module_connection,
2005            egress: FrameSink::new(module_egress_tx),
2006        };
2007        let goodbye = Frame::build(
2008            FrameType::Goodbye,
2009            Flags::new(false, Priority::Passive, true),
2010            pending.module_channel,
2011            pending.module_epoch,
2012            801,
2013            Vec::new(),
2014        )
2015        .unwrap();
2016
2017        router
2018            .route_for_connection(&module_ctx, goodbye)
2019            .await
2020            .unwrap();
2021        let reason = tokio::time::timeout(Duration::from_secs(1), &mut close_receiver)
2022            .await
2023            .expect("close request should be sent for the full GOODBYE sink")
2024            .expect("close sender should include a reason");
2025        assert!(
2026            reason.to_string().contains("route_goodbye_delivery_failed"),
2027            "unexpected close reason: {reason}"
2028        );
2029        assert_eq!(client_rx.try_recv().unwrap().header.corr, 800);
2030        assert!(client_rx.try_recv().is_err());
2031        assert_eq!(router.counters.snapshot()["goodbye_relay_client_failed"], 1);
2032        assert_eq!(router.counters.snapshot()["route_released_epoch_fenced"], 1);
2033    }
2034
2035    /// One module and one client connection with `routes` routes bound between
2036    /// them. The client uses a real connection egress queue (the same byte
2037    /// budget and frame-count backstop as a live connection), and its route.open
2038    /// responses are drained so the queue starts empty.
2039    async fn multi_route_client(
2040        module_id: &str,
2041        module_connection: ConnectionId,
2042        client_connection: ConnectionId,
2043        routes: usize,
2044    ) -> (
2045        Router,
2046        FrameSink,
2047        mpsc::Receiver<OutboundFrame>,
2048        RouteCtx,
2049        Vec<crate::forwarding::PendingRouteBindRelay>,
2050        ConnectionCloseReceiver,
2051    ) {
2052        let forwarding = Arc::new(ForwardingTable::default());
2053        let control = Arc::new(ControlHandler::with_forwarding(
2054            Arc::new(crate::Registry::default()),
2055            Arc::clone(&forwarding),
2056        ));
2057        let router = Router::with_control_handler(control);
2058        let close_receiver = forwarding.register_connection_close(client_connection);
2059        let (module_tx, _module_rx) = mpsc::channel(8);
2060        forwarding
2061            .register_module_connection(
2062                module_connection,
2063                module_id.to_string(),
2064                1,
2065                Concurrency::ModuleManaged,
2066                FrameSink::new(module_tx),
2067            )
2068            .unwrap();
2069        let (client_sink, mut client_rx) = crate::server::connection_egress();
2070        let mut bound = Vec::with_capacity(routes);
2071        for index in 0..routes {
2072            let pending = forwarding
2073                .begin_route_bind_relay_for_test(
2074                    client_connection,
2075                    client_sink.clone(),
2076                    900 + index as u64,
2077                    module_id,
2078                )
2079                .unwrap();
2080            forwarding
2081                .complete_pending_relay(
2082                    module_connection,
2083                    pending.corr,
2084                    RouteBindRelayOutcome::Accepted,
2085                )
2086                .unwrap();
2087            assert_eq!(
2088                client_rx.recv().await.unwrap().header.corr,
2089                900 + index as u64
2090            );
2091            bound.push(pending);
2092        }
2093        let (module_egress_tx, _module_egress_rx) = mpsc::channel(8);
2094        let module_ctx = RouteCtx {
2095            connection_id: module_connection,
2096            egress: FrameSink::new(module_egress_tx),
2097        };
2098        (
2099            router,
2100            client_sink,
2101            client_rx,
2102            module_ctx,
2103            bound,
2104            close_receiver,
2105        )
2106    }
2107
2108    /// Per-frame cost of the egress sink's admission and release accounting:
2109    /// one million 200-byte frames enqueued with `try_send` and taken off the
2110    /// queue the way the connection writer does, in batches of 1,000 so the
2111    /// queue stays well inside its budget. Prints nanoseconds per frame; it is
2112    /// a measurement, not a gate. Run with
2113    /// `cargo test --release -p subc-daemon --lib egress_sink_per_frame_cost -- --ignored --nocapture`.
2114    #[test]
2115    #[ignore = "timing measurement, run on demand"]
2116    fn egress_sink_per_frame_cost() {
2117        const FRAMES: usize = 1_000_000;
2118        const BATCH: usize = 1_000;
2119        let (sink, mut rx) = crate::server::connection_egress();
2120        let template = stream_frame(9, 1, 0, vec![b't'; 200]);
2121        let started = Instant::now();
2122        for _ in 0..FRAMES / BATCH {
2123            for _ in 0..BATCH {
2124                sink.try_send(template.clone()).unwrap();
2125            }
2126            for _ in 0..BATCH {
2127                let outbound = rx.try_recv().unwrap();
2128                if let Some(charge) = &outbound.charge {
2129                    charge.taken_by_writer();
2130                }
2131                drop(std::hint::black_box(outbound));
2132            }
2133        }
2134        let elapsed = started.elapsed();
2135        assert_eq!(sink.backlog().queued_bytes, 0);
2136        println!(
2137            "egress sink: {FRAMES} frames in {elapsed:?}, {:.1} ns/frame",
2138            elapsed.as_nanos() as f64 / FRAMES as f64
2139        );
2140    }
2141
2142    fn stream_frame(channel: u16, epoch: u32, corr: u64, body: Vec<u8>) -> Frame {
2143        Frame::build(
2144            FrameType::StreamData,
2145            Flags::new(false, Priority::Interactive, false),
2146            channel,
2147            epoch,
2148            corr,
2149            body,
2150        )
2151        .unwrap()
2152    }
2153
2154    fn module_route_for_test(
2155        router: &Router,
2156        ctx: &RouteCtx,
2157        route: &crate::forwarding::PendingRouteBindRelay,
2158    ) -> Arc<RouteBinding> {
2159        match router
2160            .forwarding
2161            .lookup_data_route(ctx.connection_id, route.module_channel, route.module_epoch)
2162            .unwrap()
2163        {
2164            DataRoute::Module(DataRouteState::Bound(binding)) => binding,
2165            other => panic!("expected a bound module route, got {other:?}"),
2166        }
2167    }
2168
2169    fn assert_frame_size_error(frame: &Frame, channel: u16, epoch: u32, corr: u64, bytes: usize) {
2170        assert_eq!(frame.header.ty, FrameType::Error);
2171        assert_eq!(frame.header.channel, channel);
2172        assert_eq!(frame.header.epoch, epoch);
2173        assert_eq!(frame.header.corr, corr);
2174        assert!(frame.header.flags.is_daemon_origin());
2175        let body: ErrorBody = serde_json::from_slice(&frame.body).unwrap();
2176        assert_eq!(body.code, error_codes::FRAME_TOO_LARGE);
2177        let limit = crate::server::CONNECTION_EGRESS_BYTE_BUDGET;
2178        assert!(body.message.contains(&bytes.to_string()));
2179        assert!(body.message.contains(&limit.to_string()));
2180        assert_eq!(
2181            body.detail,
2182            Some(serde_json::json!({ "frame_bytes": bytes, "byte_limit": limit }))
2183        );
2184    }
2185
2186    #[tokio::test]
2187    async fn module_egress_response_oversized_reply_keeps_connection_and_other_route() {
2188        let (logs, _guard) = test_log::log_capture(tracing::Level::WARN);
2189        let (router, sink, mut rx, ctx, routes, mut close) = multi_route_client(
2190            "oversize-provider",
2191            ConnectionId::new(80),
2192            ConnectionId::new(81),
2193            2,
2194        )
2195        .await;
2196        let binding = module_route_for_test(&router, &ctx, &routes[0]);
2197        binding.flow.acquire_tagged(101, false).await.unwrap();
2198        module_route_for_test(&router, &ctx, &routes[1])
2199            .flow
2200            .acquire_tagged(102, false)
2201            .await
2202            .unwrap();
2203        let limit = crate::server::CONNECTION_EGRESS_BYTE_BUDGET;
2204        let oversized = Frame::build(
2205            FrameType::Response,
2206            Flags::new(false, Priority::Interactive, true),
2207            routes[0].module_channel,
2208            routes[0].module_epoch,
2209            101,
2210            vec![b'x'; limit - subc_protocol::HEADER_LEN + 1],
2211        )
2212        .unwrap();
2213        assert_eq!(sink.backlog().queued_bytes, 0);
2214        router.route_for_connection(&ctx, oversized).await.unwrap();
2215
2216        // Reproduce a large reply followed by ordinary traffic on another route
2217        // before the client reads anything. The large reply must not monopolize
2218        // the queue and make that second delivery close the shared connection.
2219        router
2220            .route_for_connection(
2221                &ctx,
2222                route_frame(
2223                    FrameType::Response,
2224                    routes[1].module_channel,
2225                    routes[1].module_epoch,
2226                    102,
2227                ),
2228            )
2229            .await
2230            .unwrap();
2231        assert!(
2232            close.try_recv().is_err(),
2233            "an oversized reply must not close the multiplexed client"
2234        );
2235        let error = rx.try_recv().expect("the waiting client gets an ERROR");
2236        assert_frame_size_error(
2237            &error,
2238            routes[0].client_channel,
2239            routes[0].client_epoch,
2240            101,
2241            limit + 1,
2242        );
2243        let second = rx.try_recv().expect("the other route's reply arrives");
2244        assert_eq!(second.header.ty, FrameType::Response);
2245        assert_eq!(second.header.channel, routes[1].client_channel);
2246        assert_eq!(second.header.epoch, routes[1].client_epoch);
2247        assert_eq!(second.header.corr, 102);
2248        assert_eq!(second.body, b"route-body");
2249        assert_eq!(binding.flow.drain_in_flight(), 0);
2250        for route in &routes {
2251            module_route_for_test(&router, &ctx, route);
2252        }
2253        assert!(rx.try_recv().is_err());
2254        drop((error, second));
2255        assert_eq!(sink.backlog().queued_bytes, 0);
2256        assert_eq!(
2257            router.counters.snapshot()["client_egress_close_delivery_failed"],
2258            0
2259        );
2260
2261        let captured = test_log::captured_logs(&logs);
2262        let warnings: Vec<_> = captured
2263            .lines()
2264            .filter(|line| line.contains("WARN"))
2265            .collect();
2266        assert_eq!(warnings.len(), 1, "{captured}");
2267        let warning = warnings[0];
2268        for expected in [
2269            "module_id=oversize-provider".to_string(),
2270            "client_principal=direct".to_string(),
2271            format!("client_channel={}", routes[0].client_channel),
2272            format!("frame_bytes={}", limit + 1),
2273            format!("byte_limit={limit}"),
2274        ] {
2275            assert!(warning.contains(&expected), "missing {expected}: {warning}");
2276        }
2277    }
2278
2279    #[tokio::test]
2280    async fn module_egress_response_that_fits_empty_queue_still_closes_full_client() {
2281        let (router, sink, mut rx, ctx, routes, mut close) = multi_route_client(
2282            "backpressure-provider",
2283            ConnectionId::new(82),
2284            ConnectionId::new(83),
2285            2,
2286        )
2287        .await;
2288        let limit = crate::server::CONNECTION_EGRESS_BYTE_BUDGET;
2289        // Exactly the limit, including its header, must be admitted to an empty
2290        // queue. Leave it unread to deterministically exhaust only byte capacity.
2291        router
2292            .route_for_connection(
2293                &ctx,
2294                Frame::build(
2295                    FrameType::Response,
2296                    Flags::new(false, Priority::Interactive, true),
2297                    routes[0].module_channel,
2298                    routes[0].module_epoch,
2299                    201,
2300                    vec![b'x'; limit - subc_protocol::HEADER_LEN],
2301                )
2302                .unwrap(),
2303            )
2304            .await
2305            .unwrap();
2306        assert_eq!(sink.backlog().queued_bytes, limit);
2307        assert_eq!(sink.backlog().queued_frames, 1);
2308        let ordinary = route_frame(
2309            FrameType::Response,
2310            routes[1].module_channel,
2311            routes[1].module_epoch,
2312            202,
2313        );
2314        assert!(queued_frame_bytes(&ordinary) < limit);
2315        let mut delivery = Box::pin(router.route_for_connection(&ctx, ordinary));
2316        // Backpressure closes immediately rather than parking a replacement
2317        // ERROR behind a full queue. Poll once; no clock or timeout is involved.
2318        let result = std::future::poll_fn(|cx| {
2319            std::task::Poll::Ready(std::future::Future::poll(delivery.as_mut(), cx))
2320        })
2321        .await;
2322        assert!(
2323            matches!(result, std::task::Poll::Ready(Ok(()))),
2324            "full-queue delivery must close, not wait for an oversized-frame ERROR"
2325        );
2326        drop(delivery);
2327        let reason = close.try_recv().expect("the full client must close");
2328        assert!(reason
2329            .to_string()
2330            .contains("module_to_client_delivery_failed"));
2331        assert!(!reason.to_string().contains("frame_too_large"));
2332        assert_eq!(
2333            router.counters.snapshot()["client_egress_close_delivery_failed"],
2334            1
2335        );
2336        let admitted = rx.try_recv().expect("the budget-sized frame was admitted");
2337        assert_eq!(admitted.header.ty, FrameType::Response);
2338        assert_eq!(admitted.header.corr, 201);
2339        assert!(
2340            rx.try_recv().is_err(),
2341            "no oversized-frame ERROR is substituted for backpressure"
2342        );
2343    }
2344
2345    #[tokio::test]
2346    async fn oversized_reply_to_a_full_client_closes_it_without_waiting() {
2347        let (router, sink, _rx, ctx, routes, mut close) = multi_route_client(
2348            "full-oversize-provider",
2349            ConnectionId::new(90),
2350            ConnectionId::new(91),
2351            2,
2352        )
2353        .await;
2354        let limit = crate::server::CONNECTION_EGRESS_BYTE_BUDGET;
2355        // Fill the client's queue to its budget and leave it unread.
2356        router
2357            .route_for_connection(
2358                &ctx,
2359                Frame::build(
2360                    FrameType::Response,
2361                    Flags::new(false, Priority::Interactive, true),
2362                    routes[0].module_channel,
2363                    routes[0].module_epoch,
2364                    501,
2365                    vec![b'x'; limit - subc_protocol::HEADER_LEN],
2366                )
2367                .unwrap(),
2368            )
2369            .await
2370            .unwrap();
2371        assert_eq!(sink.backlog().queued_bytes, limit);
2372        let oversized = Frame::build(
2373            FrameType::Response,
2374            Flags::new(false, Priority::Interactive, true),
2375            routes[1].module_channel,
2376            routes[1].module_epoch,
2377            502,
2378            vec![b'y'; limit - subc_protocol::HEADER_LEN + 1],
2379        )
2380        .unwrap();
2381        let mut delivery = Box::pin(router.route_for_connection(&ctx, oversized));
2382        // The module's frame reader must never park on a stuck client: one poll
2383        // completes, and the full client is closed as ordinary backpressure.
2384        let result = std::future::poll_fn(|cx| {
2385            std::task::Poll::Ready(std::future::Future::poll(delivery.as_mut(), cx))
2386        })
2387        .await;
2388        assert!(
2389            matches!(result, std::task::Poll::Ready(Ok(()))),
2390            "the replacement ERROR must not wait for a full client queue"
2391        );
2392        drop(delivery);
2393        let reason = close.try_recv().expect("the full client must close");
2394        assert!(reason
2395            .to_string()
2396            .contains("module_to_client_delivery_failed"));
2397    }
2398
2399    #[tokio::test]
2400    async fn oversized_reverse_request_errors_module_without_reaching_client() {
2401        let (router, _sink, mut rx, mut ctx, routes, mut close) = multi_route_client(
2402            "reverse-oversize-provider",
2403            ConnectionId::new(84),
2404            ConnectionId::new(85),
2405            2,
2406        )
2407        .await;
2408        let (module_tx, mut module_rx) = mpsc::channel(8);
2409        ctx.egress = FrameSink::new(module_tx);
2410        let limit = crate::server::CONNECTION_EGRESS_BYTE_BUDGET;
2411        router
2412            .route_for_connection(
2413                &ctx,
2414                Frame::build(
2415                    FrameType::Request,
2416                    Flags::new(false, Priority::Interactive, false),
2417                    routes[0].module_channel,
2418                    routes[0].module_epoch,
2419                    301,
2420                    vec![b'r'; limit - subc_protocol::HEADER_LEN + 1],
2421                )
2422                .unwrap(),
2423            )
2424            .await
2425            .unwrap();
2426        assert!(
2427            rx.try_recv().is_err(),
2428            "the reverse request never reaches the client"
2429        );
2430        let error = module_rx.try_recv().expect("the module gets the refusal");
2431        assert_frame_size_error(
2432            &error,
2433            routes[0].module_channel,
2434            routes[0].module_epoch,
2435            301,
2436            limit + 1,
2437        );
2438        assert!(module_rx.try_recv().is_err());
2439        for (index, route) in routes.iter().enumerate() {
2440            module_route_for_test(&router, &ctx, route);
2441            router
2442                .route_for_connection(
2443                    &ctx,
2444                    route_frame(
2445                        FrameType::Response,
2446                        route.module_channel,
2447                        route.module_epoch,
2448                        302 + index as u64,
2449                    ),
2450                )
2451                .await
2452                .unwrap();
2453            let reply = rx.try_recv().expect("both routes still deliver");
2454            assert_eq!(reply.header.ty, FrameType::Response);
2455            assert_eq!(reply.header.channel, route.client_channel);
2456        }
2457        assert!(close.try_recv().is_err());
2458        assert_eq!(
2459            router.counters.snapshot()["client_egress_close_delivery_failed"],
2460            0
2461        );
2462    }
2463
2464    #[tokio::test]
2465    async fn oversized_push_closes_client_with_precise_warn() {
2466        let (logs, _guard) = test_log::log_capture(tracing::Level::WARN);
2467        let (router, _sink, mut rx, ctx, routes, mut close) = multi_route_client(
2468            "push-oversize-provider",
2469            ConnectionId::new(86),
2470            ConnectionId::new(87),
2471            2,
2472        )
2473        .await;
2474        let limit = crate::server::CONNECTION_EGRESS_BYTE_BUDGET;
2475        router
2476            .route_for_connection(
2477                &ctx,
2478                Frame::build(
2479                    FrameType::Push,
2480                    Flags::new(false, Priority::Interactive, false),
2481                    routes[0].module_channel,
2482                    routes[0].module_epoch,
2483                    0,
2484                    vec![b'p'; limit - subc_protocol::HEADER_LEN + 1],
2485                )
2486                .unwrap(),
2487            )
2488            .await
2489            .unwrap();
2490        let reason = close
2491            .try_recv()
2492            .expect("unsolicited oversize still closes the client");
2493        assert!(reason
2494            .to_string()
2495            .contains("frame_too_large: oversized push"));
2496        assert!(
2497            rx.try_recv().is_err(),
2498            "no part of the oversized push is forwarded"
2499        );
2500        let captured = test_log::captured_logs(&logs);
2501        let warnings: Vec<_> = captured
2502            .lines()
2503            .filter(|line| line.contains("WARN"))
2504            .collect();
2505        assert_eq!(warnings.len(), 1, "{captured}");
2506        let warning = warnings[0];
2507        for expected in [
2508            "oversized push".to_string(),
2509            "module_id=\"push-oversize-provider\"".to_string(),
2510            "principals=direct".to_string(),
2511            format!("client_channel={}", routes[0].client_channel),
2512            format!("size={}", limit + 1),
2513            format!("limit={limit}"),
2514        ] {
2515            assert!(warning.contains(&expected), "missing {expected}: {warning}");
2516        }
2517        assert_eq!(
2518            router.counters.snapshot()["client_egress_close_delivery_failed"],
2519            1
2520        );
2521    }
2522
2523    #[tokio::test]
2524    async fn oversized_stream_and_error_frames_end_request_even_with_queued_traffic() {
2525        let (router, sink, mut rx, ctx, routes, mut close) = multi_route_client(
2526            "stream-oversize-provider",
2527            ConnectionId::new(88),
2528            ConnectionId::new(89),
2529            2,
2530        )
2531        .await;
2532        let binding = module_route_for_test(&router, &ctx, &routes[0]);
2533        let limit = crate::server::CONNECTION_EGRESS_BYTE_BUDGET;
2534        for ty in [
2535            FrameType::StreamData,
2536            FrameType::StreamEnd,
2537            FrameType::Error,
2538        ] {
2539            binding.flow.acquire_tagged(401, false).await.unwrap();
2540            sink.try_send(route_frame(
2541                FrameType::Response,
2542                routes[1].client_channel,
2543                routes[1].client_epoch,
2544                402,
2545            ))
2546            .unwrap();
2547            router
2548                .route_for_connection(
2549                    &ctx,
2550                    Frame::build(
2551                        ty,
2552                        Flags::new(false, Priority::Interactive, false),
2553                        routes[0].module_channel,
2554                        routes[0].module_epoch,
2555                        401,
2556                        vec![b's'; limit - subc_protocol::HEADER_LEN + 1],
2557                    )
2558                    .unwrap(),
2559                )
2560                .await
2561                .unwrap();
2562            assert!(close.try_recv().is_err());
2563            assert_eq!(rx.try_recv().unwrap().header.corr, 402);
2564            let error = rx
2565                .try_recv()
2566                .expect("every correlated oversize frame becomes ERROR");
2567            assert_frame_size_error(
2568                &error,
2569                routes[0].client_channel,
2570                routes[0].client_epoch,
2571                401,
2572                limit + 1,
2573            );
2574            assert_eq!(binding.flow.drain_in_flight(), 0, "{ty:?} ends the request");
2575            assert!(rx.try_recv().is_err());
2576        }
2577    }
2578
2579    /// An awaited send (a control or error reply) is never refused by the byte
2580    /// budget: it parks until the writer frees bytes. If a release could miss a
2581    /// parked sender, that reply would hang for the connection's lifetime, so
2582    /// this pins the wakeup: the send stays parked while the queue is full and
2583    /// completes as soon as one frame leaves it.
2584    #[tokio::test]
2585    async fn awaited_send_parked_behind_a_full_byte_budget_wakes_when_bytes_free() {
2586        let (tx, mut rx) = mpsc::channel(1024);
2587        let sink = FrameSink::with_byte_budget(tx, 2_000);
2588        let mut queued = 0u64;
2589        while sink
2590            .try_send(stream_frame(7, 1, queued, vec![b'x'; 200]))
2591            .is_ok()
2592        {
2593            queued += 1;
2594        }
2595        assert!(queued > 0, "the budget admitted nothing");
2596
2597        let parked = tokio::spawn({
2598            let sink = sink.clone();
2599            async move { sink.send(stream_frame(0, 0, 999, vec![b'r'; 200])).await }
2600        });
2601        tokio::time::sleep(Duration::from_millis(100)).await;
2602        assert!(
2603            !parked.is_finished(),
2604            "the awaited send must wait while the byte budget is full"
2605        );
2606
2607        // The writer takes one frame and drops it, releasing its bytes.
2608        drop(rx.recv().await.expect("a queued frame"));
2609        tokio::time::timeout(Duration::from_secs(2), parked)
2610            .await
2611            .expect("the parked send was never woken after bytes were freed")
2612            .unwrap()
2613            .unwrap();
2614    }
2615
2616    /// A client multiplexing several token streams pauses its reader while the
2617    /// module keeps producing small frames. Far more frames than the old
2618    /// 64-frame queue allowed, but far fewer bytes than the budget, must all be
2619    /// held without closing the connection and delivered in order afterwards.
2620    #[tokio::test]
2621    async fn paused_client_reader_keeps_connection_through_small_frame_burst() {
2622        const ROUTES: usize = 4;
2623        const FRAMES_PER_ROUTE: usize = 250;
2624        let (router, client_sink, mut client_rx, module_ctx, routes, mut close_receiver) =
2625            multi_route_client(
2626                "burst-provider",
2627                ConnectionId::new(60),
2628                ConnectionId::new(61),
2629                ROUTES,
2630            )
2631            .await;
2632
2633        // The reader is paused: nothing is received until every frame is sent.
2634        for seq in 0..FRAMES_PER_ROUTE as u64 {
2635            for (index, route) in routes.iter().enumerate() {
2636                let body = format!("route-{index}-token-{seq:05}-{}", "t".repeat(170));
2637                router
2638                    .route_for_connection(
2639                        &module_ctx,
2640                        stream_frame(
2641                            route.module_channel,
2642                            route.module_epoch,
2643                            seq,
2644                            body.into_bytes(),
2645                        ),
2646                    )
2647                    .await
2648                    .unwrap();
2649            }
2650        }
2651
2652        let backlog = client_sink.backlog();
2653        assert_eq!(backlog.queued_frames, ROUTES * FRAMES_PER_ROUTE);
2654        assert!(backlog.queued_bytes < crate::server::CONNECTION_EGRESS_BYTE_BUDGET);
2655        assert!(
2656            close_receiver.try_recv().is_err(),
2657            "a paused reader under the byte budget must not be closed"
2658        );
2659        assert_eq!(
2660            router.counters.snapshot()["client_egress_close_delivery_failed"],
2661            0
2662        );
2663
2664        // The reader resumes: every frame arrives, in order within each route.
2665        let mut next_seq = vec![0u64; ROUTES];
2666        for _ in 0..ROUTES * FRAMES_PER_ROUTE {
2667            let frame = client_rx.try_recv().expect("every queued frame arrives");
2668            let index = routes
2669                .iter()
2670                .position(|route| route.client_channel == frame.header.channel)
2671                .expect("frame arrives on one of the bound client channels");
2672            assert_eq!(frame.header.corr, next_seq[index], "route {index} order");
2673            let expected_prefix = format!("route-{index}-token-{:05}-", next_seq[index]);
2674            assert!(frame.body.starts_with(expected_prefix.as_bytes()));
2675            next_seq[index] += 1;
2676        }
2677        assert!(client_rx.try_recv().is_err());
2678        assert_eq!(next_seq, vec![FRAMES_PER_ROUTE as u64; ROUTES]);
2679        assert_eq!(client_sink.backlog().queued_bytes, 0);
2680    }
2681
2682    /// A client that never reads is closed once the module's frames exceed
2683    /// the byte budget, and that close is reported once at WARN with what an
2684    /// operator needs to find the stuck reader.
2685    #[tokio::test]
2686    async fn never_reading_client_is_closed_at_byte_budget_with_warn_diagnosis() {
2687        let (logs, _guard) = test_log::log_capture(tracing::Level::WARN);
2688        const BODY: usize = 16 * 1024;
2689        let (router, client_sink, _client_rx, module_ctx, routes, mut close_receiver) =
2690            multi_route_client(
2691                "stuck-reader-provider",
2692                ConnectionId::new(70),
2693                ConnectionId::new(71),
2694                2,
2695            )
2696            .await;
2697
2698        let mut admitted = 0usize;
2699        let mut sent = 0u64;
2700        while router.counters.snapshot()["client_egress_close_delivery_failed"] == 0 {
2701            assert!(sent < 1_000, "the byte budget never refused a frame");
2702            let route = &routes[(sent % 2) as usize];
2703            router
2704                .route_for_connection(
2705                    &module_ctx,
2706                    stream_frame(
2707                        route.module_channel,
2708                        route.module_epoch,
2709                        sent,
2710                        vec![b'z'; BODY],
2711                    ),
2712                )
2713                .await
2714                .unwrap();
2715            sent += 1;
2716            admitted = client_sink.backlog().queued_frames;
2717        }
2718        // The budget, not the frame-count backstop, did the refusing.
2719        let frame_bytes = subc_protocol::HEADER_LEN + BODY;
2720        assert_eq!(
2721            admitted,
2722            crate::server::CONNECTION_EGRESS_BYTE_BUDGET / frame_bytes
2723        );
2724        let reason = close_receiver
2725            .try_recv()
2726            .expect("the client connection must be asked to close");
2727        assert!(reason
2728            .to_string()
2729            .contains("module_to_client_delivery_failed"));
2730
2731        // A second refused frame for the same connection adds no second WARN.
2732        router
2733            .route_for_connection(
2734                &module_ctx,
2735                stream_frame(
2736                    routes[0].module_channel,
2737                    routes[0].module_epoch,
2738                    sent,
2739                    vec![b'z'; BODY],
2740                ),
2741            )
2742            .await
2743            .unwrap();
2744
2745        let captured = test_log::captured_logs(&logs);
2746        let warn_lines = captured
2747            .lines()
2748            .filter(|line| {
2749                line.contains("closing client connection: its egress queue could not take a frame")
2750            })
2751            .collect::<Vec<_>>();
2752        assert_eq!(warn_lines.len(), 1, "exactly one WARN, got: {captured}");
2753        let line = warn_lines[0];
2754        assert!(line.contains("WARN"), "{line}");
2755        assert!(line.contains("connection_id=71"), "{line}");
2756        assert!(
2757            line.contains("module_id=\"stuck-reader-provider\""),
2758            "{line}"
2759        );
2760        assert!(line.contains("client_channel="), "{line}");
2761        assert!(line.contains("principals=direct"), "{line}");
2762        let queued_bytes: usize = line
2763            .split("queued_bytes=")
2764            .nth(1)
2765            .and_then(|rest| rest.split_whitespace().next())
2766            .and_then(|value| value.parse().ok())
2767            .expect("queued_bytes is logged");
2768        assert_eq!(queued_bytes, admitted * frame_bytes);
2769        assert!(
2770            line.contains(&format!("queued_frames={admitted}")),
2771            "{line}"
2772        );
2773        assert!(line.contains("oldest_queued_ms="), "{line}");
2774    }
2775
2776    fn route_frame(ty: FrameType, channel: u16, epoch: u32, corr: u64) -> Frame {
2777        Frame::build(
2778            ty,
2779            Flags::new(false, Priority::Interactive, false),
2780            channel,
2781            epoch,
2782            corr,
2783            if ty == FrameType::Request || ty == FrameType::Response {
2784                b"route-body".to_vec()
2785            } else {
2786                Vec::new()
2787            },
2788        )
2789        .unwrap()
2790    }
2791
2792    type DynamicRouteFixture = (
2793        Router,
2794        Arc<ForwardingTable>,
2795        RouteCtx,
2796        mpsc::Receiver<crate::router::OutboundFrame>,
2797        RouteCtx,
2798        mpsc::Receiver<crate::router::OutboundFrame>,
2799        mpsc::Receiver<crate::router::OutboundFrame>,
2800        crate::forwarding::PendingRouteBindRelay,
2801    );
2802
2803    fn dynamic_route_fixture(commit: bool) -> DynamicRouteFixture {
2804        let forwarding = Arc::new(ForwardingTable::default());
2805        let control = Arc::new(crate::ControlHandler::with_forwarding(
2806            Arc::new(crate::Registry::default()),
2807            Arc::clone(&forwarding),
2808        ));
2809        let router = Router::with_control_handler(control);
2810        let module_connection = ConnectionId::new(500);
2811        let client_connection = ConnectionId::new(501);
2812        let (module_tx, module_rx) = mpsc::channel(8);
2813        forwarding
2814            .register_module_connection(
2815                module_connection,
2816                "epoch-router".into(),
2817                2,
2818                Concurrency::ModuleManaged,
2819                FrameSink::new(module_tx),
2820            )
2821            .unwrap();
2822        let (client_tx, client_rx) = mpsc::channel(8);
2823        let client_sink = FrameSink::new(client_tx);
2824        let pending = forwarding
2825            .begin_route_bind_relay_for_test(
2826                client_connection,
2827                client_sink.clone(),
2828                700,
2829                "epoch-router",
2830            )
2831            .unwrap();
2832        if commit {
2833            forwarding
2834                .complete_pending_relay(
2835                    module_connection,
2836                    pending.corr,
2837                    RouteBindRelayOutcome::Accepted,
2838                )
2839                .unwrap();
2840        }
2841        let (module_egress_tx, module_egress_rx) = mpsc::channel(8);
2842        (
2843            router,
2844            forwarding,
2845            RouteCtx {
2846                connection_id: client_connection,
2847                egress: client_sink,
2848            },
2849            client_rx,
2850            RouteCtx {
2851                connection_id: module_connection,
2852                egress: FrameSink::new(module_egress_tx),
2853            },
2854            module_egress_rx,
2855            module_rx,
2856            pending,
2857        )
2858    }
2859
2860    #[tokio::test]
2861    async fn route_epochs_validate_both_directions_and_rewrite_to_peer_handle() {
2862        let (
2863            router,
2864            _forwarding,
2865            client_ctx,
2866            mut client_rx,
2867            module_ctx,
2868            _module_egress_rx,
2869            mut module_rx,
2870            pending,
2871        ) = dynamic_route_fixture(true);
2872        let route_open = client_rx.recv().await.unwrap();
2873        assert_eq!(route_open.header.corr, 700);
2874
2875        router
2876            .route_for_connection(
2877                &client_ctx,
2878                route_frame(
2879                    FrameType::Request,
2880                    pending.client_channel,
2881                    pending.client_epoch,
2882                    701,
2883                ),
2884            )
2885            .await
2886            .unwrap();
2887        let forwarded = module_rx.recv().await.unwrap();
2888        assert_eq!(forwarded.header.channel, pending.module_channel);
2889        assert_eq!(forwarded.header.epoch, pending.module_epoch);
2890
2891        router
2892            .route_for_connection(
2893                &module_ctx,
2894                route_frame(
2895                    FrameType::Response,
2896                    pending.module_channel,
2897                    pending.module_epoch,
2898                    701,
2899                ),
2900            )
2901            .await
2902            .unwrap();
2903        let delivered = client_rx.recv().await.unwrap();
2904        assert_eq!(delivered.header.channel, pending.client_channel);
2905        assert_eq!(delivered.header.epoch, pending.client_epoch);
2906
2907        router
2908            .route_for_connection(
2909                &client_ctx,
2910                route_frame(
2911                    FrameType::Request,
2912                    pending.client_channel,
2913                    pending.client_epoch + 1,
2914                    702,
2915                ),
2916            )
2917            .await
2918            .unwrap();
2919        router
2920            .route_for_connection(
2921                &module_ctx,
2922                route_frame(
2923                    FrameType::Response,
2924                    pending.module_channel,
2925                    pending.module_epoch + 1,
2926                    703,
2927                ),
2928            )
2929            .await
2930            .unwrap();
2931        let stale_error = client_rx.recv().await.unwrap();
2932        assert_eq!(stale_error.header.ty, FrameType::Error);
2933        assert_eq!(stale_error.header.channel, pending.client_channel);
2934        assert_eq!(stale_error.header.epoch, pending.client_epoch + 1);
2935        assert_eq!(stale_error.header.corr, 702);
2936        let body: ErrorBody = serde_json::from_slice(&stale_error.body).unwrap();
2937        assert_eq!(body.code, "stale_route_epoch");
2938        assert!(module_rx.try_recv().is_err());
2939        assert!(client_rx.try_recv().is_err());
2940        let counters = router.counters.snapshot();
2941        assert_eq!(counters["client_frames_dropped_stale_route"], 1);
2942        assert_eq!(counters["module_frames_dropped_no_route"], 1);
2943    }
2944
2945    #[tokio::test]
2946    async fn accepted_route_publishes_route_open_before_immediate_reverse_request() {
2947        let (
2948            router,
2949            _,
2950            _client_ctx,
2951            mut client_rx,
2952            module_ctx,
2953            _module_egress_rx,
2954            _module_rx,
2955            pending,
2956        ) = dynamic_route_fixture(true);
2957        router
2958            .route_for_connection(
2959                &module_ctx,
2960                route_frame(
2961                    FrameType::Request,
2962                    pending.module_channel,
2963                    pending.module_epoch,
2964                    800,
2965                ),
2966            )
2967            .await
2968            .unwrap();
2969
2970        let first = client_rx.recv().await.unwrap();
2971        let second = client_rx.recv().await.unwrap();
2972        assert_eq!(first.header.channel, 0);
2973        assert_eq!(first.header.corr, 700);
2974        assert_eq!(second.header.channel, pending.client_channel);
2975        assert_eq!(second.header.epoch, pending.client_epoch);
2976        assert_eq!(second.header.corr, 800);
2977    }
2978
2979    #[tokio::test]
2980    async fn reserved_slot_ingress_errors_only_matching_client_requests() {
2981        let (
2982            router,
2983            _forwarding,
2984            client_ctx,
2985            mut client_rx,
2986            _module_ctx,
2987            _module_egress_rx,
2988            mut module_rx,
2989            pending,
2990        ) = dynamic_route_fixture(false);
2991        router
2992            .route_for_connection(
2993                &client_ctx,
2994                route_frame(
2995                    FrameType::Request,
2996                    pending.client_channel,
2997                    pending.client_epoch,
2998                    900,
2999                ),
3000            )
3001            .await
3002            .unwrap();
3003        let error = client_rx.recv().await.unwrap();
3004        assert_eq!(error.header.ty, FrameType::Error);
3005        assert_eq!(error.header.channel, pending.client_channel);
3006        assert_eq!(error.header.epoch, pending.client_epoch);
3007        assert_eq!(error.header.corr, 900);
3008
3009        router
3010            .route_for_connection(
3011                &client_ctx,
3012                route_frame(
3013                    FrameType::Response,
3014                    pending.client_channel,
3015                    pending.client_epoch,
3016                    901,
3017                ),
3018            )
3019            .await
3020            .unwrap();
3021        router
3022            .route_for_connection(
3023                &client_ctx,
3024                route_frame(
3025                    FrameType::Request,
3026                    pending.client_channel,
3027                    pending.client_epoch + 1,
3028                    902,
3029                ),
3030            )
3031            .await
3032            .unwrap();
3033        let stale_error = client_rx.recv().await.unwrap();
3034        assert_eq!(stale_error.header.ty, FrameType::Error);
3035        assert_eq!(stale_error.header.channel, pending.client_channel);
3036        assert_eq!(stale_error.header.epoch, pending.client_epoch + 1);
3037        assert_eq!(stale_error.header.corr, 902);
3038        let body: ErrorBody = serde_json::from_slice(&stale_error.body).unwrap();
3039        assert_eq!(body.code, "stale_route_epoch");
3040        assert!(module_rx.try_recv().is_err());
3041        let counters = router.counters.snapshot();
3042        assert_eq!(counters["client_frames_dropped_stale_route"], 1);
3043        assert_eq!(counters["module_frames_dropped_no_route"], 0);
3044    }
3045
3046    #[tokio::test]
3047    async fn dropped_module_route_goodbye_increments_counter() {
3048        let (
3049            router,
3050            _forwarding,
3051            client_ctx,
3052            mut client_rx,
3053            _module_ctx,
3054            _module_egress_rx,
3055            mut module_rx,
3056            pending,
3057        ) = dynamic_route_fixture(true);
3058        let _ = client_rx.recv().await;
3059        module_rx.close();
3060
3061        router
3062            .route_for_connection(
3063                &client_ctx,
3064                route_frame(
3065                    FrameType::Goodbye,
3066                    pending.client_channel,
3067                    pending.client_epoch,
3068                    999,
3069                ),
3070            )
3071            .await
3072            .unwrap();
3073
3074        let counters = router.counters.snapshot();
3075        assert_eq!(counters["goodbye_relay_module_dropped"], 1);
3076        assert_eq!(
3077            counters["goodbye_relay_module_dropped_by_module"],
3078            serde_json::json!({ "epoch-router": 1 })
3079        );
3080        assert_eq!(counters["route_released_epoch_fenced"], 1);
3081    }
3082
3083    #[tokio::test]
3084    async fn module_request_on_stale_epoch_receives_stale_route_epoch() {
3085        let (
3086            router,
3087            _forwarding,
3088            _client_ctx,
3089            _client_rx,
3090            module_ctx,
3091            mut module_egress_rx,
3092            mut module_rx,
3093            pending,
3094        ) = dynamic_route_fixture(true);
3095
3096        router
3097            .route_for_connection(
3098                &module_ctx,
3099                route_frame(
3100                    FrameType::Request,
3101                    pending.module_channel,
3102                    pending.module_epoch + 1,
3103                    1_000,
3104                ),
3105            )
3106            .await
3107            .unwrap();
3108
3109        let error = module_egress_rx.try_recv().unwrap();
3110        assert_eq!(error.header.ty, FrameType::Error);
3111        assert_eq!(error.header.channel, pending.module_channel);
3112        assert_eq!(error.header.epoch, pending.module_epoch + 1);
3113        assert_eq!(error.header.corr, 1_000);
3114        let body: ErrorBody = serde_json::from_slice(&error.body).unwrap();
3115        assert_eq!(body.code, "stale_route_epoch");
3116        assert!(module_rx.try_recv().is_err());
3117        let counters = router.counters.snapshot();
3118        assert_eq!(counters["module_requests_dropped_stale_route"], 1);
3119        assert_eq!(counters["module_frames_dropped_no_route"], 0);
3120    }
3121
3122    #[tokio::test]
3123    async fn module_request_on_reserved_or_absent_route_receives_unknown_channel() {
3124        let (
3125            reserved_router,
3126            _forwarding,
3127            _client_ctx,
3128            _client_rx,
3129            reserved_module_ctx,
3130            mut reserved_module_egress_rx,
3131            _module_rx,
3132            reserved,
3133        ) = dynamic_route_fixture(false);
3134        reserved_router
3135            .route_for_connection(
3136                &reserved_module_ctx,
3137                route_frame(
3138                    FrameType::Request,
3139                    reserved.module_channel,
3140                    reserved.module_epoch,
3141                    1_001,
3142                ),
3143            )
3144            .await
3145            .unwrap();
3146        let reserved_error = reserved_module_egress_rx.try_recv().unwrap();
3147        let reserved_body: ErrorBody = serde_json::from_slice(&reserved_error.body).unwrap();
3148        assert_eq!(reserved_error.header.ty, FrameType::Error);
3149        assert_eq!(reserved_error.header.channel, reserved.module_channel);
3150        assert_eq!(reserved_error.header.epoch, reserved.module_epoch);
3151        assert_eq!(reserved_error.header.corr, 1_001);
3152        assert_eq!(reserved_body.code, "unknown_channel");
3153        assert_eq!(
3154            reserved_router.counters.snapshot()["module_requests_dropped_stale_route"],
3155            1
3156        );
3157
3158        let (
3159            absent_router,
3160            _forwarding,
3161            _client_ctx,
3162            _client_rx,
3163            absent_module_ctx,
3164            mut absent_module_egress_rx,
3165            _module_rx,
3166            absent,
3167        ) = dynamic_route_fixture(false);
3168        absent_router
3169            .route_for_connection(
3170                &absent_module_ctx,
3171                route_frame(
3172                    FrameType::Request,
3173                    absent.module_channel + 1,
3174                    absent.module_epoch,
3175                    1_002,
3176                ),
3177            )
3178            .await
3179            .unwrap();
3180        let absent_error = absent_module_egress_rx.try_recv().unwrap();
3181        let absent_body: ErrorBody = serde_json::from_slice(&absent_error.body).unwrap();
3182        assert_eq!(absent_error.header.ty, FrameType::Error);
3183        assert_eq!(absent_error.header.channel, absent.module_channel + 1);
3184        assert_eq!(absent_error.header.epoch, absent.module_epoch);
3185        assert_eq!(absent_error.header.corr, 1_002);
3186        assert_eq!(absent_body.code, "unknown_channel");
3187        assert_eq!(
3188            absent_router.counters.snapshot()["module_requests_dropped_stale_route"],
3189            1
3190        );
3191    }
3192
3193    #[tokio::test]
3194    async fn non_request_module_frame_on_dead_route_is_counted_without_error() {
3195        let (
3196            router,
3197            forwarding,
3198            client_ctx,
3199            mut client_rx,
3200            module_ctx,
3201            mut module_egress_rx,
3202            mut module_rx,
3203            pending,
3204        ) = dynamic_route_fixture(true);
3205        let (other_module_tx, _other_module_rx) = mpsc::channel(8);
3206        forwarding
3207            .register_module_connection(
3208                ConnectionId::new(502),
3209                "other-module".into(),
3210                2,
3211                Concurrency::ModuleManaged,
3212                FrameSink::new(other_module_tx),
3213            )
3214            .unwrap();
3215        let _ = client_rx.recv().await.unwrap();
3216
3217        router
3218            .route_for_connection(
3219                &client_ctx,
3220                route_frame(
3221                    FrameType::Goodbye,
3222                    pending.client_channel,
3223                    pending.client_epoch,
3224                    1_003,
3225                ),
3226            )
3227            .await
3228            .unwrap();
3229        let _ = module_rx.recv().await.unwrap();
3230
3231        router
3232            .route_for_connection(
3233                &module_ctx,
3234                route_frame(
3235                    FrameType::StreamData,
3236                    pending.module_channel,
3237                    pending.module_epoch,
3238                    1_004,
3239                ),
3240            )
3241            .await
3242            .unwrap();
3243
3244        // No ERROR goes back for a non-request frame. The one reply is the
3245        // route GOODBYE telling the module to let go of the released route.
3246        let reply = module_egress_rx.try_recv().unwrap();
3247        assert_eq!(reply.header.ty, FrameType::Goodbye);
3248        assert!(module_egress_rx.try_recv().is_err());
3249        let counters = router.counters.snapshot();
3250        assert_eq!(counters["module_frames_dropped_no_route"], 1);
3251        assert_eq!(
3252            counters["module_frames_dropped_no_route_by_module"],
3253            serde_json::json!({ "epoch-router": 1 })
3254        );
3255        assert_eq!(counters["module_requests_dropped_stale_route"], 0);
3256    }
3257
3258    /// Drain whatever the module connection's queue holds right now, the way a
3259    /// module's reader would before it stalls.
3260    fn drain_now(rx: &mut mpsc::Receiver<OutboundFrame>) {
3261        while rx.try_recv().is_ok() {}
3262    }
3263
3264    /// A module that stops reading for a moment when a client closes one of
3265    /// its routes still learns the route is gone: the GOODBYE its full egress
3266    /// queue refused is delivered as soon as it reads again, rather than
3267    /// dropped, which would leave the module holding the route for the rest of
3268    /// its connection.
3269    #[tokio::test]
3270    async fn route_goodbye_refused_by_stalled_module_is_delivered_when_it_resumes_reading() {
3271        const BUDGET: usize = 4_096;
3272        let forwarding = Arc::new(ForwardingTable::default());
3273        let control = Arc::new(ControlHandler::with_forwarding(
3274            Arc::new(Registry::default()),
3275            Arc::clone(&forwarding),
3276        ));
3277        let router = Router::with_control_handler(control);
3278        let module_connection = ConnectionId::new(80);
3279        let client_connection = ConnectionId::new(81);
3280        let (module_tx, mut module_rx) = mpsc::channel(64);
3281        let module_sink = FrameSink::with_byte_budget(module_tx, BUDGET);
3282        forwarding
3283            .register_module_connection(
3284                module_connection,
3285                "stalled-provider".into(),
3286                2,
3287                Concurrency::ModuleManaged,
3288                module_sink.clone(),
3289            )
3290            .unwrap();
3291        let (client_tx, mut client_rx) = mpsc::channel(8);
3292        let client_sink = FrameSink::new(client_tx);
3293        let pending = forwarding
3294            .begin_route_bind_relay_for_test(
3295                client_connection,
3296                client_sink.clone(),
3297                1_100,
3298                "stalled-provider",
3299            )
3300            .unwrap();
3301        forwarding
3302            .complete_pending_relay(
3303                module_connection,
3304                pending.corr,
3305                RouteBindRelayOutcome::Accepted,
3306            )
3307            .unwrap();
3308        let _ = client_rx.recv().await.unwrap();
3309        drain_now(&mut module_rx);
3310
3311        // The module stalls: its queue holds more than the byte budget, so the
3312        // queue refuses anything further.
3313        module_sink
3314            .try_send(stream_frame(9, 1, 0, vec![b'f'; BUDGET]))
3315            .unwrap();
3316        assert!(module_sink
3317            .try_send(stream_frame(9, 1, 1, Vec::new()))
3318            .is_err());
3319
3320        let client_ctx = RouteCtx {
3321            connection_id: client_connection,
3322            egress: client_sink,
3323        };
3324        router
3325            .route_for_connection(
3326                &client_ctx,
3327                route_frame(
3328                    FrameType::Goodbye,
3329                    pending.client_channel,
3330                    pending.client_epoch,
3331                    1_101,
3332                ),
3333            )
3334            .await
3335            .unwrap();
3336        tokio::task::yield_now().await;
3337        assert_eq!(
3338            router.counters.snapshot()["goodbye_relay_module_dropped"],
3339            0,
3340            "a GOODBYE refused by a momentarily full module queue must not be dropped"
3341        );
3342
3343        // The module reads again: the filler comes off, then the GOODBYE.
3344        let filler = module_rx.recv().await.unwrap();
3345        assert_eq!(filler.header.ty, FrameType::StreamData);
3346        drop(filler);
3347        let goodbye = tokio::time::timeout(Duration::from_secs(2), module_rx.recv())
3348            .await
3349            .expect("the refused GOODBYE must be delivered once the module frees room")
3350            .unwrap();
3351        assert_eq!(goodbye.header.ty, FrameType::Goodbye);
3352        assert_eq!(goodbye.header.channel, pending.module_channel);
3353        assert_eq!(goodbye.header.epoch, pending.module_epoch);
3354        assert_eq!(
3355            router.counters.snapshot()["goodbye_relay_module_dropped"],
3356            0
3357        );
3358    }
3359
3360    /// A module still sending on a route the daemon released is told, with a
3361    /// route GOODBYE for exactly the (channel, epoch) it sent on. The same
3362    /// happens for a stale epoch on a channel whose route moved on and for a
3363    /// channel that never had a route; only the first is counted as traffic on
3364    /// a released route.
3365    #[tokio::test]
3366    async fn module_frame_on_route_the_daemon_does_not_hold_is_answered_with_goodbye() {
3367        let (
3368            router,
3369            _forwarding,
3370            client_ctx,
3371            mut client_rx,
3372            module_ctx,
3373            mut module_egress_rx,
3374            mut module_rx,
3375            pending,
3376        ) = dynamic_route_fixture(true);
3377        let _ = client_rx.recv().await.unwrap();
3378        router
3379            .route_for_connection(
3380                &client_ctx,
3381                route_frame(
3382                    FrameType::Goodbye,
3383                    pending.client_channel,
3384                    pending.client_epoch,
3385                    1_200,
3386                ),
3387            )
3388            .await
3389            .unwrap();
3390        drain_now(&mut module_rx);
3391
3392        let cases = [
3393            // Released route: the daemon allocated this (channel, epoch).
3394            (pending.module_channel, pending.module_epoch),
3395            // A channel the daemon never allocated on this connection.
3396            (pending.module_channel + 1, 1),
3397        ];
3398        for (channel, epoch) in cases {
3399            router
3400                .route_for_connection(
3401                    &module_ctx,
3402                    route_frame(FrameType::StreamData, channel, epoch, 1_201),
3403                )
3404                .await
3405                .unwrap();
3406            let reply = module_egress_rx
3407                .try_recv()
3408                .expect("a frame on a route the daemon does not hold is answered");
3409            assert_eq!(reply.header.ty, FrameType::Goodbye);
3410            assert_eq!(reply.header.channel, channel);
3411            assert_eq!(reply.header.epoch, epoch);
3412            assert_eq!(reply.header.corr, 0);
3413            assert!(module_egress_rx.try_recv().is_err());
3414        }
3415
3416        // A GOODBYE from the module for a route the daemon already released
3417        // is not answered: the module is letting go already.
3418        router
3419            .route_for_connection(
3420                &module_ctx,
3421                route_frame(FrameType::Goodbye, pending.module_channel + 2, 1, 0),
3422            )
3423            .await
3424            .unwrap();
3425        assert!(module_egress_rx.try_recv().is_err());
3426
3427        let counters = router.counters.snapshot();
3428        assert_eq!(counters["module_frames_dropped_no_route"], 3);
3429        assert_eq!(counters["module_frames_dropped_released_route"], 1);
3430        assert_eq!(
3431            counters["module_frames_dropped_released_route_by_module"],
3432            serde_json::json!({ "epoch-router": 1 })
3433        );
3434        assert_eq!(counters["module_orphan_route_goodbyes_sent"], 2);
3435    }
3436
3437    /// A chatty orphan (a streaming module still producing on a route it
3438    /// missed the GOODBYE for) is answered once per interval, not once per
3439    /// frame, and answered again once the interval has passed.
3440    #[tokio::test(start_paused = true)]
3441    async fn burst_of_orphan_module_frames_is_answered_once_per_interval() {
3442        let (
3443            router,
3444            _forwarding,
3445            client_ctx,
3446            mut client_rx,
3447            module_ctx,
3448            mut module_egress_rx,
3449            mut module_rx,
3450            pending,
3451        ) = dynamic_route_fixture(true);
3452        let _ = client_rx.recv().await.unwrap();
3453        router
3454            .route_for_connection(
3455                &client_ctx,
3456                route_frame(
3457                    FrameType::Goodbye,
3458                    pending.client_channel,
3459                    pending.client_epoch,
3460                    1_300,
3461                ),
3462            )
3463            .await
3464            .unwrap();
3465        drain_now(&mut module_rx);
3466
3467        let send_burst = |corr: u64| {
3468            route_frame(
3469                FrameType::StreamData,
3470                pending.module_channel,
3471                pending.module_epoch,
3472                corr,
3473            )
3474        };
3475        for corr in 0..20 {
3476            router
3477                .route_for_connection(&module_ctx, send_burst(corr))
3478                .await
3479                .unwrap();
3480        }
3481        let mut replies = 0;
3482        while let Ok(reply) = module_egress_rx.try_recv() {
3483            assert_eq!(reply.header.ty, FrameType::Goodbye);
3484            replies += 1;
3485        }
3486        assert_eq!(replies, 1, "a burst within the interval gets one GOODBYE");
3487
3488        tokio::time::advance(ORPHAN_ROUTE_GOODBYE_INTERVAL).await;
3489        router
3490            .route_for_connection(&module_ctx, send_burst(20))
3491            .await
3492            .unwrap();
3493        let retry = module_egress_rx
3494            .try_recv()
3495            .expect("the first orphan frame after the interval is answered again");
3496        assert_eq!(retry.header.channel, pending.module_channel);
3497        assert_eq!(retry.header.epoch, pending.module_epoch);
3498
3499        let counters = router.counters.snapshot();
3500        assert_eq!(counters["module_frames_dropped_no_route"], 21);
3501        assert_eq!(counters["module_orphan_route_goodbyes_sent"], 2);
3502    }
3503
3504    /// The rate-limit memory is per connection and goes away with it.
3505    #[test]
3506    fn orphan_goodbye_rate_limit_state_is_released_with_the_connection() {
3507        let router = Router::with_default_self_handler();
3508        let connection = router.begin_connection();
3509        let id = connection.id();
3510        assert!(router.orphan_goodbyes.claim(id, 7));
3511        assert!(!router.orphan_goodbyes.claim(id, 7));
3512        assert!(router.orphan_goodbyes.claim(id, 8));
3513        drop(connection);
3514        assert!(router
3515            .orphan_goodbyes
3516            .last_sent
3517            .lock()
3518            .unwrap()
3519            .get(&id)
3520            .is_none());
3521    }
3522
3523    #[test]
3524    fn channel_zero_cannot_be_registered_as_backend() {
3525        let mut router = Router::with_default_self_handler();
3526
3527        let err = router.register_backend(0, EchoBackend).unwrap_err();
3528
3529        assert_eq!(err, RouterError::ReservedChannelZero);
3530    }
3531}