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;
15
16use crate::{
17    control::ControlHandler,
18    forwarding::{
19        CloseReason, ConnectionCloseReceiver, DataRoute, DataRouteState, ForwardingError,
20        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                // A terminal frame ends the request at the module whether or not
962                // the client can still take it, so its credit is released before
963                // delivery is attempted. Releasing only after a successful
964                // enqueue would leave a drain counting a finished request until
965                // the client connection's cleanup removes the route.
966                let releases_credit = is_terminal_frame(frame.header.ty);
967                if releases_credit {
968                    route.flow.release_corr(corr);
969                }
970                let mut frame = frame;
971                frame.header.channel = route.client_channel;
972                frame.header.epoch = route.client_epoch;
973                if let Err(err) = route.client_sink.try_send(frame) {
974                    if self
975                        .forwarding
976                        .escalate_client_delivery_failure(
977                            route.client_connection_id,
978                            route.client_channel,
979                            route.client_epoch,
980                            CloseReason::new(
981                                "module_to_client_delivery_failed",
982                                format!(
983                                    "failed to enqueue module frame for client channel {} corr {corr}: {err}",
984                                    route.client_channel
985                                ),
986                            ),
987                            UndeliveredFrame {
988                                module_id: Some(&route.module_id),
989                                sink: &route.client_sink,
990                            },
991                        )
992                        .map_err(RouterError::Forwarding)?
993                    {
994                        self.counters
995                            .increment_client_egress_close_delivery_failed();
996                    }
997                    return Ok(());
998                }
999                return Ok(());
1000            }
1001            DataRoute::Client(DataRouteState::EpochMismatch) => {
1002                if frame.header.ty == FrameType::Request {
1003                    self.counters.increment_client_frames_dropped_stale_route();
1004                    // Dropped before forwarding; a re-bind retry cannot double-execute this request.
1005                    let err = RouterError::StaleRouteEpoch {
1006                        channel,
1007                        epoch,
1008                        corr,
1009                    };
1010                    if let Some(error_frame) = err.to_error_frame() {
1011                        ctx.egress.send(error_frame).await?;
1012                    }
1013                }
1014                debug!(
1015                    connection_id = ctx.connection_id.get(),
1016                    channel, epoch, corr, "dropping client frame for stale route epoch"
1017                );
1018                return Ok(());
1019            }
1020            DataRoute::Client(DataRouteState::Reserved) => {
1021                if frame.header.ty == FrameType::Request {
1022                    let err = RouterError::UnknownChannel {
1023                        channel,
1024                        epoch,
1025                        corr,
1026                    };
1027                    if let Some(error_frame) = err.to_error_frame() {
1028                        ctx.egress.send(error_frame).await?;
1029                    }
1030                }
1031                return Ok(());
1032            }
1033            DataRoute::Client(DataRouteState::Bound(route)) => {
1034                if frame.header.ty == FrameType::Goodbye {
1035                    let _ = self
1036                        .control
1037                        .handle_route_goodbye(ctx.connection_id, channel, epoch)?;
1038                    return Ok(());
1039                }
1040                return self.forward_backend.handle_bound(frame, route).await;
1041            }
1042            DataRoute::Client(DataRouteState::Absent) => {}
1043        }
1044
1045        if let Some(backend) = self.backends.get(&channel) {
1046            return backend.handle(ctx.clone(), frame).await;
1047        }
1048        if frame.header.ty == FrameType::Request {
1049            let err = RouterError::UnknownChannel {
1050                channel,
1051                epoch,
1052                corr,
1053            };
1054            if let Some(error_frame) = err.to_error_frame() {
1055                ctx.egress.send(error_frame).await?;
1056            }
1057        }
1058        Ok(())
1059    }
1060}
1061
1062impl Default for Router {
1063    fn default() -> Self {
1064        Self::with_default_self_handler()
1065    }
1066}
1067
1068/// Connection-scoped cleanup guard returned by [`Router::begin_connection`].
1069#[must_use]
1070pub struct RouterConnection {
1071    id: ConnectionId,
1072    control_handler: Arc<ControlHandler>,
1073    forwarding: Arc<ForwardingTable>,
1074    close_receiver: Option<ConnectionCloseReceiver>,
1075    orphan_goodbyes: Arc<OrphanGoodbyeLimiter>,
1076}
1077
1078impl RouterConnection {
1079    pub fn id(&self) -> ConnectionId {
1080        self.id
1081    }
1082
1083    pub(crate) fn take_close_receiver(&mut self) -> ConnectionCloseReceiver {
1084        self.close_receiver
1085            .take()
1086            .expect("connection close receiver can only be taken once")
1087    }
1088}
1089
1090impl Drop for RouterConnection {
1091    fn drop(&mut self) {
1092        self.forwarding.unregister_connection_close(self.id);
1093        self.orphan_goodbyes.forget_connection(self.id);
1094        // GOODBYE (explicit) and connection-drop cleanup both call the same
1095        // idempotent deregistration path.
1096        let _ = self.control_handler.cleanup_connection(self.id);
1097    }
1098}
1099
1100/// Minimal in-memory backend used by tests and early wiring: it emits a
1101/// `RESPONSE` on the same channel/correlation id with the exact same body bytes.
1102#[derive(Debug, Default, Clone, Copy)]
1103pub struct EchoBackend;
1104
1105impl EchoBackend {
1106    pub async fn handle(&self, ctx: RouteCtx, frame: Frame) -> Result<(), RouterError> {
1107        let response = Frame::build_with_version(
1108            frame.header.ver,
1109            FrameType::Response,
1110            frame.header.flags,
1111            frame.header.channel,
1112            frame.header.epoch,
1113            frame.header.corr,
1114            frame.body,
1115        )
1116        .map_err(RouterError::FrameBuild)?;
1117        ctx.egress.send(response).await
1118    }
1119}
1120
1121/// Data-plane backend that splices client frames to the module connection bound at attach time.
1122#[derive(Debug, Clone)]
1123pub struct ForwardBackend {
1124    forwarding: Arc<ForwardingTable>,
1125}
1126
1127impl ForwardBackend {
1128    pub fn new(forwarding: Arc<ForwardingTable>) -> Self {
1129        Self { forwarding }
1130    }
1131
1132    pub async fn handle(&self, ctx: RouteCtx, frame: Frame) -> Result<(), RouterError> {
1133        let channel = frame.header.channel;
1134        let corr = frame.header.corr;
1135        let route = match self
1136            .forwarding
1137            .lookup_data_route(ctx.connection_id, channel, frame.header.epoch)
1138            .map_err(RouterError::Forwarding)?
1139        {
1140            DataRoute::Client(DataRouteState::Bound(route)) => route,
1141            DataRoute::Client(_) | DataRoute::Module(_) => {
1142                return Err(RouterError::UnknownChannel {
1143                    channel,
1144                    epoch: frame.header.epoch,
1145                    corr,
1146                });
1147            }
1148        };
1149        self.handle_bound(frame, route).await
1150    }
1151
1152    pub(crate) async fn handle_bound(
1153        &self,
1154        frame: Frame,
1155        route: Arc<RouteBinding>,
1156    ) -> Result<(), RouterError> {
1157        let channel = frame.header.channel;
1158        let corr = frame.header.corr;
1159        let frame_type = frame.header.ty;
1160
1161        // CANCEL and other non-REQUEST frames bypass the request-credit window;
1162        // the original request's credit is freed only by the module's terminal frame.
1163        let acquired_credit = frame_type == FrameType::Request;
1164        if acquired_credit {
1165            if let Err(err) = route
1166                .flow
1167                .acquire_tagged(corr, frame.header.flags.is_subscription())
1168                .await
1169            {
1170                // `module_reloading` here is answered BEFORE the frame is
1171                // forwarded, so the module never sees the request and callers
1172                // may re-dispatch it after reopening the route. That is a wire
1173                // guarantee documented on `error_codes::MODULE_RELOADING`; do
1174                // not emit this code for a request that already reached
1175                // `module_sink.send` below.
1176                if self
1177                    .forwarding
1178                    .endpoint_is_draining(route.module_endpoint)
1179                    .map_err(RouterError::Forwarding)?
1180                {
1181                    return Err(RouterError::route_error_with_epoch(
1182                        channel,
1183                        frame.header.epoch,
1184                        corr,
1185                        "module_reloading",
1186                        format!("module endpoint for route channel {channel} is reloading"),
1187                    ));
1188                }
1189                return Err(RouterError::backend_with_epoch(
1190                    channel,
1191                    frame.header.epoch,
1192                    corr,
1193                    format!("{err} for route channel {channel}"),
1194                ));
1195            }
1196        }
1197
1198        let mut frame = frame;
1199        frame.header.channel = route.module_channel;
1200        frame.header.epoch = route.module_epoch;
1201        let result = route.module_sink.send(frame).await.map_err(|err| {
1202            RouterError::backend_with_epoch(channel, route.client_epoch, corr, err.to_string())
1203        });
1204        if acquired_credit && result.is_err() {
1205            route.flow.release_corr(corr);
1206        }
1207        result
1208    }
1209}
1210
1211fn is_terminal_frame(frame_type: FrameType) -> bool {
1212    matches!(
1213        frame_type,
1214        FrameType::Response | FrameType::Error | FrameType::StreamEnd
1215    )
1216}
1217
1218/// Typed router errors. Routable failures can be translated to canonical JSON
1219/// `ERROR` frames with [`RouterError::to_error_frame`].
1220#[derive(Debug, Clone, PartialEq, Eq)]
1221pub enum RouterError {
1222    ReservedChannelZero,
1223    DuplicateChannel {
1224        channel: u16,
1225    },
1226    UnknownChannel {
1227        channel: u16,
1228        epoch: u32,
1229        corr: u64,
1230    },
1231    StaleRouteEpoch {
1232        channel: u16,
1233        epoch: u32,
1234        corr: u64,
1235    },
1236    Backend {
1237        channel: u16,
1238        epoch: u32,
1239        corr: u64,
1240        message: String,
1241    },
1242    RouteError {
1243        channel: u16,
1244        epoch: u32,
1245        corr: u64,
1246        code: String,
1247        message: String,
1248    },
1249    FrameBuild(FrameBuildError),
1250    Forwarding(ForwardingError),
1251}
1252
1253impl RouterError {
1254    pub fn backend(channel: u16, corr: u64, message: impl Into<String>) -> Self {
1255        Self::backend_with_epoch(channel, 0, corr, message)
1256    }
1257
1258    pub fn backend_with_epoch(
1259        channel: u16,
1260        epoch: u32,
1261        corr: u64,
1262        message: impl Into<String>,
1263    ) -> Self {
1264        Self::Backend {
1265            channel,
1266            epoch,
1267            corr,
1268            message: message.into(),
1269        }
1270    }
1271
1272    pub fn route_error(
1273        channel: u16,
1274        corr: u64,
1275        code: impl Into<String>,
1276        message: impl Into<String>,
1277    ) -> Self {
1278        Self::route_error_with_epoch(channel, 0, corr, code, message)
1279    }
1280
1281    pub fn route_error_with_epoch(
1282        channel: u16,
1283        epoch: u32,
1284        corr: u64,
1285        code: impl Into<String>,
1286        message: impl Into<String>,
1287    ) -> Self {
1288        Self::RouteError {
1289            channel,
1290            epoch,
1291            corr,
1292            code: code.into(),
1293            message: message.into(),
1294        }
1295    }
1296
1297    /// Translate route failures that belong on the wire into an `ERROR` frame.
1298    pub fn to_error_frame(&self) -> Option<Frame> {
1299        match self {
1300            Self::UnknownChannel {
1301                channel,
1302                epoch,
1303                corr,
1304            } => error_frame(
1305                *channel,
1306                *epoch,
1307                *corr,
1308                error_codes::UNKNOWN_CHANNEL,
1309                format!("unknown channel {channel}"),
1310            ),
1311            Self::StaleRouteEpoch {
1312                channel,
1313                epoch,
1314                corr,
1315            } => error_frame(
1316                *channel,
1317                *epoch,
1318                *corr,
1319                error_codes::STALE_ROUTE_EPOCH,
1320                format!("stale route epoch for channel {channel}"),
1321            ),
1322            Self::Backend {
1323                channel,
1324                epoch,
1325                corr,
1326                message,
1327            } => error_frame(*channel, *epoch, *corr, "backend_error", message.clone()),
1328            Self::RouteError {
1329                channel,
1330                epoch,
1331                corr,
1332                code,
1333                message,
1334            } => error_frame(*channel, *epoch, *corr, code, message.clone()),
1335            Self::ReservedChannelZero
1336            | Self::DuplicateChannel { .. }
1337            | Self::FrameBuild(_)
1338            | Self::Forwarding(_) => None,
1339        }
1340    }
1341}
1342
1343fn error_frame(channel: u16, epoch: u32, corr: u64, code: &str, message: String) -> Option<Frame> {
1344    let body = serde_json::to_vec(&ErrorBody {
1345        code: code.to_string(),
1346        message,
1347        detail: None,
1348    })
1349    .ok()?;
1350
1351    Frame::build(
1352        FrameType::Error,
1353        Flags::new(false, Priority::Passive, false),
1354        channel,
1355        epoch,
1356        corr,
1357        body,
1358    )
1359    .ok()
1360}
1361
1362impl fmt::Display for RouterError {
1363    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1364        match self {
1365            Self::ReservedChannelZero => write!(f, "channel 0 is reserved for subc"),
1366            Self::DuplicateChannel { channel } => {
1367                write!(f, "backend already registered for channel {channel}")
1368            }
1369            Self::UnknownChannel { channel, corr, .. } => {
1370                write!(f, "unknown channel {channel} for corr {corr}")
1371            }
1372            Self::StaleRouteEpoch { channel, corr, .. } => {
1373                write!(f, "stale route epoch for channel {channel} corr {corr}")
1374            }
1375            Self::Backend {
1376                channel,
1377                corr,
1378                message,
1379                ..
1380            } => write!(
1381                f,
1382                "backend error on channel {channel} corr {corr}: {message}"
1383            ),
1384            Self::RouteError {
1385                channel,
1386                corr,
1387                code,
1388                message,
1389                ..
1390            } => write!(
1391                f,
1392                "route error {code} on channel {channel} corr {corr}: {message}"
1393            ),
1394            Self::FrameBuild(err) => write!(f, "failed to build routed frame: {err}"),
1395            Self::Forwarding(err) => write!(f, "forwarding error: {err}"),
1396        }
1397    }
1398}
1399
1400impl Error for RouterError {
1401    fn source(&self) -> Option<&(dyn Error + 'static)> {
1402        match self {
1403            Self::FrameBuild(err) => Some(err),
1404            Self::Forwarding(err) => Some(err),
1405            Self::ReservedChannelZero
1406            | Self::DuplicateChannel { .. }
1407            | Self::UnknownChannel { .. }
1408            | Self::StaleRouteEpoch { .. }
1409            | Self::Backend { .. }
1410            | Self::RouteError { .. } => None,
1411        }
1412    }
1413}
1414
1415#[cfg(test)]
1416mod tests {
1417    use super::*;
1418    use crate::{
1419        forwarding::RouteBindRelayOutcome,
1420        supervise::{ModuleSpec, RestartPolicy, Supervisor, SupervisorHandle},
1421        ControlHandler, Registry,
1422    };
1423    use std::{
1424        sync::{mpsc as std_mpsc, Arc},
1425        time::Duration,
1426    };
1427    use subc_control::ModuleProtocol;
1428    use subc_protocol::{manifest::Concurrency, ErrorBody, Flags, FrameType, Priority};
1429    use tokio::sync::mpsc;
1430
1431    pub(crate) use crate::router::test_log::{captured_logs, log_capture};
1432
1433    fn logged_millis(logs: &str, field: &str) -> u64 {
1434        logs.split_whitespace()
1435            .find_map(|part| part.strip_prefix(field))
1436            .and_then(|value| value.parse().ok())
1437            .unwrap_or_else(|| panic!("missing numeric {field} in logs: {logs}"))
1438    }
1439
1440    fn request(channel: u16, corr: u64, body: &[u8]) -> Frame {
1441        Frame::build(
1442            FrameType::Request,
1443            Flags::new(true, Priority::Interactive, false),
1444            channel,
1445            0,
1446            corr,
1447            body.to_vec(),
1448        )
1449        .unwrap()
1450    }
1451
1452    fn ping(corr: u64) -> Frame {
1453        Frame::build(
1454            FrameType::Ping,
1455            Flags::new(false, Priority::Passive, false),
1456            0,
1457            0,
1458            corr,
1459            Vec::new(),
1460        )
1461        .unwrap()
1462    }
1463
1464    fn route_ctx() -> (RouteCtx, mpsc::Receiver<crate::router::OutboundFrame>) {
1465        let (tx, rx) = mpsc::channel(8);
1466        (
1467            RouteCtx {
1468                connection_id: ConnectionId::LOCAL,
1469                egress: FrameSink::new(tx),
1470            },
1471            rx,
1472        )
1473    }
1474
1475    #[tokio::test]
1476    async fn echo_backend_returns_response_with_byte_identical_body() {
1477        let mut router = Router::with_default_self_handler();
1478        router.register_backend(7, EchoBackend).unwrap();
1479        let (ctx, mut rx) = route_ctx();
1480        let body = b"{not parsed}\0\xff";
1481
1482        router
1483            .route_for_connection(&ctx, request(7, 123, body))
1484            .await
1485            .unwrap();
1486        let response = rx.recv().await.unwrap();
1487
1488        assert_eq!(response.header.ty, FrameType::Response);
1489        assert_eq!(response.header.channel, 7);
1490        assert_eq!(response.header.corr, 123);
1491        assert_eq!(response.body, body);
1492        assert!(rx.try_recv().is_err());
1493    }
1494
1495    #[tokio::test]
1496    async fn unknown_channel_emits_canonical_error_frame() {
1497        let router = Router::with_default_self_handler();
1498        let (ctx, mut rx) = route_ctx();
1499
1500        router
1501            .route_for_connection(&ctx, request(99, 5, b"payload"))
1502            .await
1503            .unwrap();
1504        let error_frame = rx.recv().await.unwrap();
1505
1506        assert_eq!(error_frame.header.ty, FrameType::Error);
1507        assert_eq!(error_frame.header.channel, 99);
1508        assert_eq!(error_frame.header.corr, 5);
1509        let body: ErrorBody = serde_json::from_slice(&error_frame.body).unwrap();
1510        assert_eq!(body.code, "unknown_channel");
1511        assert_eq!(body.message, "unknown channel 99");
1512    }
1513
1514    #[tokio::test]
1515    async fn channel_zero_uses_control_handler_not_backend_registry() {
1516        let mut router = Router::with_default_self_handler();
1517        router.register_backend(1, EchoBackend).unwrap();
1518        let (ctx, mut rx) = route_ctx();
1519
1520        router.route_for_connection(&ctx, ping(77)).await.unwrap();
1521        let response = rx.recv().await.unwrap();
1522
1523        assert_eq!(response.header.ty, FrameType::Pong);
1524        assert_eq!(response.header.channel, 0);
1525        assert_eq!(response.header.corr, 77);
1526        assert!(response.body.is_empty());
1527    }
1528
1529    #[tokio::test]
1530    async fn slow_control_dispatch_logs_decoded_op_and_elapsed_time() {
1531        let control = Arc::new(
1532            ControlHandler::new(Arc::new(Registry::default()))
1533                .with_control_dispatch_delay(Duration::from_millis(1050)),
1534        );
1535        let router = Router::with_control_handler(control);
1536        let (ctx, mut rx) = route_ctx();
1537        let (output, guard) = log_capture(tracing::Level::WARN);
1538
1539        router
1540            .route_for_connection(&ctx, request(0, 41, br#"{"op":"server.describe"}"#))
1541            .await
1542            .expect("slow request routes");
1543        assert!(rx.recv().await.is_some(), "request receives a response");
1544        drop(guard);
1545
1546        let logs = captured_logs(&output);
1547        assert!(logs.contains("slow control dispatch"));
1548        assert!(logs.contains("op=server.describe"));
1549        assert!(logs.contains("connection_id=0"));
1550        assert!(logs.contains("corr=41"));
1551        assert!(
1552            logged_millis(&logs, "elapsed_ms=") >= 1050,
1553            "elapsed must include the injected handler delay: {logs}"
1554        );
1555    }
1556
1557    #[tokio::test]
1558    async fn fast_control_dispatch_emits_arrival_without_slow_warning() {
1559        let router = Router::with_default_self_handler();
1560        let (ctx, mut rx) = route_ctx();
1561        let (output, guard) = log_capture(tracing::Level::DEBUG);
1562
1563        router
1564            .route_for_connection(&ctx, request(0, 42, br#"{"op":"server.describe"}"#))
1565            .await
1566            .expect("fast request routes");
1567        assert!(rx.recv().await.is_some(), "request receives a response");
1568        drop(guard);
1569
1570        let logs = captured_logs(&output);
1571        assert!(logs.contains("control dispatch op=server.describe connection_id=0 corr=42"));
1572        assert!(!logs.contains("slow control dispatch"));
1573    }
1574
1575    #[tokio::test]
1576    async fn control_dispatch_arrival_is_hidden_at_info() {
1577        let router = Router::with_default_self_handler();
1578        let (ctx, mut rx) = route_ctx();
1579        let (output, guard) = log_capture(tracing::Level::INFO);
1580
1581        router
1582            .route_for_connection(&ctx, request(0, 43, br#"{"op":"server.describe"}"#))
1583            .await
1584            .expect("fast request routes");
1585        assert!(rx.recv().await.is_some(), "request receives a response");
1586        drop(guard);
1587
1588        assert!(
1589            !captured_logs(&output).contains("control dispatch"),
1590            "arrival logging must stay hidden at INFO"
1591        );
1592    }
1593
1594    #[tokio::test]
1595    async fn supervisor_list_logs_contended_snapshot_lock_only() {
1596        let registry = Arc::new(Registry::default());
1597        let handle = SupervisorHandle::new();
1598        let supervisor = Supervisor::new_for_test(Arc::clone(&registry), RestartPolicy::default())
1599            .with_handle(handle.clone());
1600        let module = supervisor
1601            .supervise_configured(
1602                ModuleSpec {
1603                    module_id: "held-module".to_string(),
1604                    program: "test-module".into(),
1605                    args: Vec::new(),
1606                    env: Vec::new(),
1607                    reserved: false,
1608                    reserved_prefixes: Vec::new(),
1609                    protocol: ModuleProtocol::Subc,
1610                    overlap: Default::default(),
1611                },
1612                false,
1613            )
1614            .expect("disabled test module is supervised");
1615        let router = Router::with_control_handler(Arc::new(
1616            ControlHandler::new(Arc::clone(&registry)).with_supervisor(handle),
1617        ));
1618        let (ctx, mut rx) = route_ctx();
1619        let (acquired, ready) = std_mpsc::channel();
1620        let holder = module.hold_snapshot_for_test(acquired, Duration::from_millis(400));
1621        ready.recv().expect("holder acquired snapshot lock");
1622        let (output, guard) = log_capture(tracing::Level::WARN);
1623
1624        router
1625            .route_for_connection(&ctx, request(0, 44, br#"{"op":"supervisor.list"}"#))
1626            .await
1627            .expect("list request routes after the lock releases");
1628        assert!(
1629            rx.recv().await.is_some(),
1630            "list request receives a response"
1631        );
1632        holder.join().expect("snapshot holder exits cleanly");
1633        drop(guard);
1634
1635        let logs = captured_logs(&output);
1636        assert!(logs.contains("slow snapshot lock"));
1637        assert!(logs.contains("module_id=held-module"));
1638        assert!(logs.contains("caller=list"));
1639        assert!(
1640            logged_millis(&logs, "waited_ms=") >= 250,
1641            "wait must exceed the slow-lock threshold: {logs}"
1642        );
1643
1644        let (output, guard) = log_capture(tracing::Level::WARN);
1645        router
1646            .route_for_connection(&ctx, request(0, 45, br#"{"op":"supervisor.list"}"#))
1647            .await
1648            .expect("uncontended list request routes");
1649        assert!(
1650            rx.recv().await.is_some(),
1651            "uncontended list receives a response"
1652        );
1653        drop(guard);
1654        assert!(
1655            !captured_logs(&output).contains("slow snapshot lock"),
1656            "uncontended list acquisition must not warn"
1657        );
1658    }
1659
1660    #[tokio::test]
1661    async fn full_module_to_client_sink_requests_client_close_without_erroring_module() {
1662        let forwarding = Arc::new(ForwardingTable::default());
1663        let control = Arc::new(ControlHandler::with_forwarding(
1664            Arc::new(crate::Registry::default()),
1665            Arc::clone(&forwarding),
1666        ));
1667        let router = Router::with_control_handler(control);
1668        let module_connection = ConnectionId::new(10);
1669        let client_connection = ConnectionId::new(20);
1670        let mut close_receiver = forwarding.register_connection_close(client_connection);
1671        let (module_tx, _module_rx) = mpsc::channel(1);
1672        forwarding
1673            .register_module_connection(
1674                module_connection,
1675                "full-sink-provider".to_string(),
1676                1,
1677                Concurrency::ModuleManaged,
1678                FrameSink::new(module_tx),
1679            )
1680            .unwrap();
1681        let (client_tx, mut client_rx) = mpsc::channel(1);
1682        let pending = forwarding
1683            .begin_route_bind_relay_for_test(
1684                client_connection,
1685                FrameSink::new(client_tx),
1686                700,
1687                "full-sink-provider",
1688            )
1689            .unwrap();
1690        forwarding
1691            .complete_pending_relay(
1692                module_connection,
1693                pending.corr,
1694                RouteBindRelayOutcome::Accepted,
1695            )
1696            .unwrap();
1697
1698        let (module_egress_tx, _module_egress_rx) = mpsc::channel(1);
1699        let module_ctx = RouteCtx {
1700            connection_id: module_connection,
1701            egress: FrameSink::new(module_egress_tx),
1702        };
1703        let terminal = Frame::build(
1704            FrameType::Response,
1705            Flags::new(false, Priority::Interactive, true),
1706            pending.module_channel,
1707            pending.module_epoch,
1708            701,
1709            b"terminal".to_vec(),
1710        )
1711        .unwrap();
1712
1713        router
1714            .route_for_connection(&module_ctx, terminal)
1715            .await
1716            .unwrap();
1717        let reason = tokio::time::timeout(Duration::from_secs(1), &mut close_receiver)
1718            .await
1719            .expect("close request should be sent for the full client sink")
1720            .expect("close sender should include a reason");
1721        assert!(
1722            reason
1723                .to_string()
1724                .contains("module_to_client_delivery_failed"),
1725            "unexpected close reason: {reason}"
1726        );
1727        assert_eq!(client_rx.try_recv().unwrap().header.corr, 700);
1728        assert!(client_rx.try_recv().is_err());
1729        assert_eq!(
1730            router.counters.snapshot()["client_egress_close_delivery_failed"],
1731            1
1732        );
1733    }
1734
1735    /// A terminal frame ends the request at the module even when the client
1736    /// cannot take it, so the drain must stop counting it at once rather than
1737    /// when the client connection's cleanup later removes the route.
1738    #[tokio::test]
1739    async fn terminal_frame_releases_its_credit_even_when_client_delivery_fails() {
1740        let forwarding = Arc::new(ForwardingTable::default());
1741        let control = Arc::new(ControlHandler::with_forwarding(
1742            Arc::new(crate::Registry::default()),
1743            Arc::clone(&forwarding),
1744        ));
1745        let router = Router::with_control_handler(control);
1746        let module_connection = ConnectionId::new(11);
1747        let client_connection = ConnectionId::new(21);
1748        let _close_receiver = forwarding.register_connection_close(client_connection);
1749        let (module_tx, _module_rx) = mpsc::channel(1);
1750        forwarding
1751            .register_module_connection(
1752                module_connection,
1753                "credit-provider".to_string(),
1754                1,
1755                Concurrency::ModuleManaged,
1756                FrameSink::new(module_tx),
1757            )
1758            .unwrap();
1759        // Capacity one, filled by the route.open response, so the terminal
1760        // frame below cannot be enqueued for the client.
1761        let (client_tx, _client_rx) = mpsc::channel(1);
1762        let pending = forwarding
1763            .begin_route_bind_relay_for_test(
1764                client_connection,
1765                FrameSink::new(client_tx),
1766                800,
1767                "credit-provider",
1768            )
1769            .unwrap();
1770        forwarding
1771            .complete_pending_relay(
1772                module_connection,
1773                pending.corr,
1774                RouteBindRelayOutcome::Accepted,
1775            )
1776            .unwrap();
1777        let DataRoute::Client(DataRouteState::Bound(route)) = forwarding
1778            .lookup_data_route(
1779                client_connection,
1780                pending.client_channel,
1781                pending.client_epoch,
1782            )
1783            .unwrap()
1784        else {
1785            panic!("expected a bound client route");
1786        };
1787        route.flow.acquire_tagged(801, false).await.unwrap();
1788        assert_eq!(route.flow.drain_in_flight(), 1);
1789
1790        let (module_egress_tx, _module_egress_rx) = mpsc::channel(1);
1791        let module_ctx = RouteCtx {
1792            connection_id: module_connection,
1793            egress: FrameSink::new(module_egress_tx),
1794        };
1795        let terminal = Frame::build(
1796            FrameType::Response,
1797            Flags::new(false, Priority::Interactive, true),
1798            pending.module_channel,
1799            pending.module_epoch,
1800            801,
1801            b"terminal".to_vec(),
1802        )
1803        .unwrap();
1804        router
1805            .route_for_connection(&module_ctx, terminal)
1806            .await
1807            .unwrap();
1808
1809        assert_eq!(
1810            router.counters.snapshot()["client_egress_close_delivery_failed"],
1811            1,
1812            "the client delivery must have failed for this test to mean anything"
1813        );
1814        assert_eq!(
1815            route.flow.drain_in_flight(),
1816            0,
1817            "the module's terminal frame must release its credit even though the client could not take it"
1818        );
1819    }
1820
1821    #[tokio::test]
1822    async fn full_route_goodbye_sink_requests_target_close_without_erroring_module() {
1823        let forwarding = Arc::new(ForwardingTable::default());
1824        let control = Arc::new(ControlHandler::with_forwarding(
1825            Arc::new(crate::Registry::default()),
1826            Arc::clone(&forwarding),
1827        ));
1828        let router = Router::with_control_handler(control);
1829        let module_connection = ConnectionId::new(30);
1830        let client_connection = ConnectionId::new(40);
1831        let mut close_receiver = forwarding.register_connection_close(client_connection);
1832        let (module_tx, _module_rx) = mpsc::channel(1);
1833        forwarding
1834            .register_module_connection(
1835                module_connection,
1836                "goodbye-full-provider".to_string(),
1837                1,
1838                Concurrency::ModuleManaged,
1839                FrameSink::new(module_tx),
1840            )
1841            .unwrap();
1842        let (client_tx, mut client_rx) = mpsc::channel(1);
1843        let pending = forwarding
1844            .begin_route_bind_relay_for_test(
1845                client_connection,
1846                FrameSink::new(client_tx),
1847                800,
1848                "goodbye-full-provider",
1849            )
1850            .unwrap();
1851        forwarding
1852            .complete_pending_relay(
1853                module_connection,
1854                pending.corr,
1855                RouteBindRelayOutcome::Accepted,
1856            )
1857            .unwrap();
1858
1859        let (module_egress_tx, _module_egress_rx) = mpsc::channel(1);
1860        let module_ctx = RouteCtx {
1861            connection_id: module_connection,
1862            egress: FrameSink::new(module_egress_tx),
1863        };
1864        let goodbye = Frame::build(
1865            FrameType::Goodbye,
1866            Flags::new(false, Priority::Passive, true),
1867            pending.module_channel,
1868            pending.module_epoch,
1869            801,
1870            Vec::new(),
1871        )
1872        .unwrap();
1873
1874        router
1875            .route_for_connection(&module_ctx, goodbye)
1876            .await
1877            .unwrap();
1878        let reason = tokio::time::timeout(Duration::from_secs(1), &mut close_receiver)
1879            .await
1880            .expect("close request should be sent for the full GOODBYE sink")
1881            .expect("close sender should include a reason");
1882        assert!(
1883            reason.to_string().contains("route_goodbye_delivery_failed"),
1884            "unexpected close reason: {reason}"
1885        );
1886        assert_eq!(client_rx.try_recv().unwrap().header.corr, 800);
1887        assert!(client_rx.try_recv().is_err());
1888        assert_eq!(router.counters.snapshot()["goodbye_relay_client_failed"], 1);
1889        assert_eq!(router.counters.snapshot()["route_released_epoch_fenced"], 1);
1890    }
1891
1892    /// One module and one client connection with `routes` routes bound between
1893    /// them. The client uses a real connection egress queue (the same byte
1894    /// budget and frame-count backstop as a live connection), and its route.open
1895    /// responses are drained so the queue starts empty.
1896    async fn multi_route_client(
1897        module_id: &str,
1898        module_connection: ConnectionId,
1899        client_connection: ConnectionId,
1900        routes: usize,
1901    ) -> (
1902        Router,
1903        FrameSink,
1904        mpsc::Receiver<OutboundFrame>,
1905        RouteCtx,
1906        Vec<crate::forwarding::PendingRouteBindRelay>,
1907        ConnectionCloseReceiver,
1908    ) {
1909        let forwarding = Arc::new(ForwardingTable::default());
1910        let control = Arc::new(ControlHandler::with_forwarding(
1911            Arc::new(crate::Registry::default()),
1912            Arc::clone(&forwarding),
1913        ));
1914        let router = Router::with_control_handler(control);
1915        let close_receiver = forwarding.register_connection_close(client_connection);
1916        let (module_tx, _module_rx) = mpsc::channel(8);
1917        forwarding
1918            .register_module_connection(
1919                module_connection,
1920                module_id.to_string(),
1921                1,
1922                Concurrency::ModuleManaged,
1923                FrameSink::new(module_tx),
1924            )
1925            .unwrap();
1926        let (client_sink, mut client_rx) = crate::server::connection_egress();
1927        let mut bound = Vec::with_capacity(routes);
1928        for index in 0..routes {
1929            let pending = forwarding
1930                .begin_route_bind_relay_for_test(
1931                    client_connection,
1932                    client_sink.clone(),
1933                    900 + index as u64,
1934                    module_id,
1935                )
1936                .unwrap();
1937            forwarding
1938                .complete_pending_relay(
1939                    module_connection,
1940                    pending.corr,
1941                    RouteBindRelayOutcome::Accepted,
1942                )
1943                .unwrap();
1944            assert_eq!(
1945                client_rx.recv().await.unwrap().header.corr,
1946                900 + index as u64
1947            );
1948            bound.push(pending);
1949        }
1950        let (module_egress_tx, _module_egress_rx) = mpsc::channel(8);
1951        let module_ctx = RouteCtx {
1952            connection_id: module_connection,
1953            egress: FrameSink::new(module_egress_tx),
1954        };
1955        (
1956            router,
1957            client_sink,
1958            client_rx,
1959            module_ctx,
1960            bound,
1961            close_receiver,
1962        )
1963    }
1964
1965    /// Per-frame cost of the egress sink's admission and release accounting:
1966    /// one million 200-byte frames enqueued with `try_send` and taken off the
1967    /// queue the way the connection writer does, in batches of 1,000 so the
1968    /// queue stays well inside its budget. Prints nanoseconds per frame; it is
1969    /// a measurement, not a gate. Run with
1970    /// `cargo test --release -p subc-daemon --lib egress_sink_per_frame_cost -- --ignored --nocapture`.
1971    #[test]
1972    #[ignore = "timing measurement, run on demand"]
1973    fn egress_sink_per_frame_cost() {
1974        const FRAMES: usize = 1_000_000;
1975        const BATCH: usize = 1_000;
1976        let (sink, mut rx) = crate::server::connection_egress();
1977        let template = stream_frame(9, 1, 0, vec![b't'; 200]);
1978        let started = Instant::now();
1979        for _ in 0..FRAMES / BATCH {
1980            for _ in 0..BATCH {
1981                sink.try_send(template.clone()).unwrap();
1982            }
1983            for _ in 0..BATCH {
1984                let outbound = rx.try_recv().unwrap();
1985                if let Some(charge) = &outbound.charge {
1986                    charge.taken_by_writer();
1987                }
1988                drop(std::hint::black_box(outbound));
1989            }
1990        }
1991        let elapsed = started.elapsed();
1992        assert_eq!(sink.backlog().queued_bytes, 0);
1993        println!(
1994            "egress sink: {FRAMES} frames in {elapsed:?}, {:.1} ns/frame",
1995            elapsed.as_nanos() as f64 / FRAMES as f64
1996        );
1997    }
1998
1999    fn stream_frame(channel: u16, epoch: u32, corr: u64, body: Vec<u8>) -> Frame {
2000        Frame::build(
2001            FrameType::StreamData,
2002            Flags::new(false, Priority::Interactive, false),
2003            channel,
2004            epoch,
2005            corr,
2006            body,
2007        )
2008        .unwrap()
2009    }
2010
2011    /// An awaited send (a control or error reply) is never refused by the byte
2012    /// budget: it parks until the writer frees bytes. If a release could miss a
2013    /// parked sender, that reply would hang for the connection's lifetime, so
2014    /// this pins the wakeup: the send stays parked while the queue is full and
2015    /// completes as soon as one frame leaves it.
2016    #[tokio::test]
2017    async fn awaited_send_parked_behind_a_full_byte_budget_wakes_when_bytes_free() {
2018        let (tx, mut rx) = mpsc::channel(1024);
2019        let sink = FrameSink::with_byte_budget(tx, 2_000);
2020        let mut queued = 0u64;
2021        while sink
2022            .try_send(stream_frame(7, 1, queued, vec![b'x'; 200]))
2023            .is_ok()
2024        {
2025            queued += 1;
2026        }
2027        assert!(queued > 0, "the budget admitted nothing");
2028
2029        let parked = tokio::spawn({
2030            let sink = sink.clone();
2031            async move { sink.send(stream_frame(0, 0, 999, vec![b'r'; 200])).await }
2032        });
2033        tokio::time::sleep(Duration::from_millis(100)).await;
2034        assert!(
2035            !parked.is_finished(),
2036            "the awaited send must wait while the byte budget is full"
2037        );
2038
2039        // The writer takes one frame and drops it, releasing its bytes.
2040        drop(rx.recv().await.expect("a queued frame"));
2041        tokio::time::timeout(Duration::from_secs(2), parked)
2042            .await
2043            .expect("the parked send was never woken after bytes were freed")
2044            .unwrap()
2045            .unwrap();
2046    }
2047
2048    /// A client multiplexing several token streams pauses its reader while the
2049    /// module keeps producing small frames. Far more frames than the old
2050    /// 64-frame queue allowed, but far fewer bytes than the budget, must all be
2051    /// held without closing the connection and delivered in order afterwards.
2052    #[tokio::test]
2053    async fn paused_client_reader_keeps_connection_through_small_frame_burst() {
2054        const ROUTES: usize = 4;
2055        const FRAMES_PER_ROUTE: usize = 250;
2056        let (router, client_sink, mut client_rx, module_ctx, routes, mut close_receiver) =
2057            multi_route_client(
2058                "burst-provider",
2059                ConnectionId::new(60),
2060                ConnectionId::new(61),
2061                ROUTES,
2062            )
2063            .await;
2064
2065        // The reader is paused: nothing is received until every frame is sent.
2066        for seq in 0..FRAMES_PER_ROUTE as u64 {
2067            for (index, route) in routes.iter().enumerate() {
2068                let body = format!("route-{index}-token-{seq:05}-{}", "t".repeat(170));
2069                router
2070                    .route_for_connection(
2071                        &module_ctx,
2072                        stream_frame(
2073                            route.module_channel,
2074                            route.module_epoch,
2075                            seq,
2076                            body.into_bytes(),
2077                        ),
2078                    )
2079                    .await
2080                    .unwrap();
2081            }
2082        }
2083
2084        let backlog = client_sink.backlog();
2085        assert_eq!(backlog.queued_frames, ROUTES * FRAMES_PER_ROUTE);
2086        assert!(backlog.queued_bytes < crate::server::CONNECTION_EGRESS_BYTE_BUDGET);
2087        assert!(
2088            close_receiver.try_recv().is_err(),
2089            "a paused reader under the byte budget must not be closed"
2090        );
2091        assert_eq!(
2092            router.counters.snapshot()["client_egress_close_delivery_failed"],
2093            0
2094        );
2095
2096        // The reader resumes: every frame arrives, in order within each route.
2097        let mut next_seq = vec![0u64; ROUTES];
2098        for _ in 0..ROUTES * FRAMES_PER_ROUTE {
2099            let frame = client_rx.try_recv().expect("every queued frame arrives");
2100            let index = routes
2101                .iter()
2102                .position(|route| route.client_channel == frame.header.channel)
2103                .expect("frame arrives on one of the bound client channels");
2104            assert_eq!(frame.header.corr, next_seq[index], "route {index} order");
2105            let expected_prefix = format!("route-{index}-token-{:05}-", next_seq[index]);
2106            assert!(frame.body.starts_with(expected_prefix.as_bytes()));
2107            next_seq[index] += 1;
2108        }
2109        assert!(client_rx.try_recv().is_err());
2110        assert_eq!(next_seq, vec![FRAMES_PER_ROUTE as u64; ROUTES]);
2111        assert_eq!(client_sink.backlog().queued_bytes, 0);
2112    }
2113
2114    /// A client that never reads is closed once the module's frames exceed
2115    /// the byte budget, and that close is reported once at WARN with what an
2116    /// operator needs to find the stuck reader.
2117    #[tokio::test]
2118    async fn never_reading_client_is_closed_at_byte_budget_with_warn_diagnosis() {
2119        let (logs, _guard) = test_log::log_capture(tracing::Level::WARN);
2120        const BODY: usize = 16 * 1024;
2121        let (router, client_sink, _client_rx, module_ctx, routes, mut close_receiver) =
2122            multi_route_client(
2123                "stuck-reader-provider",
2124                ConnectionId::new(70),
2125                ConnectionId::new(71),
2126                2,
2127            )
2128            .await;
2129
2130        let mut admitted = 0usize;
2131        let mut sent = 0u64;
2132        while router.counters.snapshot()["client_egress_close_delivery_failed"] == 0 {
2133            assert!(sent < 1_000, "the byte budget never refused a frame");
2134            let route = &routes[(sent % 2) as usize];
2135            router
2136                .route_for_connection(
2137                    &module_ctx,
2138                    stream_frame(
2139                        route.module_channel,
2140                        route.module_epoch,
2141                        sent,
2142                        vec![b'z'; BODY],
2143                    ),
2144                )
2145                .await
2146                .unwrap();
2147            sent += 1;
2148            admitted = client_sink.backlog().queued_frames;
2149        }
2150        // The budget, not the frame-count backstop, did the refusing.
2151        let frame_bytes = subc_protocol::HEADER_LEN + BODY;
2152        assert_eq!(
2153            admitted,
2154            crate::server::CONNECTION_EGRESS_BYTE_BUDGET / frame_bytes
2155        );
2156        let reason = close_receiver
2157            .try_recv()
2158            .expect("the client connection must be asked to close");
2159        assert!(reason
2160            .to_string()
2161            .contains("module_to_client_delivery_failed"));
2162
2163        // A second refused frame for the same connection adds no second WARN.
2164        router
2165            .route_for_connection(
2166                &module_ctx,
2167                stream_frame(
2168                    routes[0].module_channel,
2169                    routes[0].module_epoch,
2170                    sent,
2171                    vec![b'z'; BODY],
2172                ),
2173            )
2174            .await
2175            .unwrap();
2176
2177        let captured = test_log::captured_logs(&logs);
2178        let warn_lines = captured
2179            .lines()
2180            .filter(|line| {
2181                line.contains("closing client connection: its egress queue could not take a frame")
2182            })
2183            .collect::<Vec<_>>();
2184        assert_eq!(warn_lines.len(), 1, "exactly one WARN, got: {captured}");
2185        let line = warn_lines[0];
2186        assert!(line.contains("WARN"), "{line}");
2187        assert!(line.contains("connection_id=71"), "{line}");
2188        assert!(
2189            line.contains("module_id=\"stuck-reader-provider\""),
2190            "{line}"
2191        );
2192        assert!(line.contains("client_channel="), "{line}");
2193        assert!(line.contains("principals=direct"), "{line}");
2194        let queued_bytes: usize = line
2195            .split("queued_bytes=")
2196            .nth(1)
2197            .and_then(|rest| rest.split_whitespace().next())
2198            .and_then(|value| value.parse().ok())
2199            .expect("queued_bytes is logged");
2200        assert_eq!(queued_bytes, admitted * frame_bytes);
2201        assert!(
2202            line.contains(&format!("queued_frames={admitted}")),
2203            "{line}"
2204        );
2205        assert!(line.contains("oldest_queued_ms="), "{line}");
2206    }
2207
2208    fn route_frame(ty: FrameType, channel: u16, epoch: u32, corr: u64) -> Frame {
2209        Frame::build(
2210            ty,
2211            Flags::new(false, Priority::Interactive, false),
2212            channel,
2213            epoch,
2214            corr,
2215            if ty == FrameType::Request || ty == FrameType::Response {
2216                b"route-body".to_vec()
2217            } else {
2218                Vec::new()
2219            },
2220        )
2221        .unwrap()
2222    }
2223
2224    type DynamicRouteFixture = (
2225        Router,
2226        Arc<ForwardingTable>,
2227        RouteCtx,
2228        mpsc::Receiver<crate::router::OutboundFrame>,
2229        RouteCtx,
2230        mpsc::Receiver<crate::router::OutboundFrame>,
2231        mpsc::Receiver<crate::router::OutboundFrame>,
2232        crate::forwarding::PendingRouteBindRelay,
2233    );
2234
2235    fn dynamic_route_fixture(commit: bool) -> DynamicRouteFixture {
2236        let forwarding = Arc::new(ForwardingTable::default());
2237        let control = Arc::new(crate::ControlHandler::with_forwarding(
2238            Arc::new(crate::Registry::default()),
2239            Arc::clone(&forwarding),
2240        ));
2241        let router = Router::with_control_handler(control);
2242        let module_connection = ConnectionId::new(500);
2243        let client_connection = ConnectionId::new(501);
2244        let (module_tx, module_rx) = mpsc::channel(8);
2245        forwarding
2246            .register_module_connection(
2247                module_connection,
2248                "epoch-router".into(),
2249                2,
2250                Concurrency::ModuleManaged,
2251                FrameSink::new(module_tx),
2252            )
2253            .unwrap();
2254        let (client_tx, client_rx) = mpsc::channel(8);
2255        let client_sink = FrameSink::new(client_tx);
2256        let pending = forwarding
2257            .begin_route_bind_relay_for_test(
2258                client_connection,
2259                client_sink.clone(),
2260                700,
2261                "epoch-router",
2262            )
2263            .unwrap();
2264        if commit {
2265            forwarding
2266                .complete_pending_relay(
2267                    module_connection,
2268                    pending.corr,
2269                    RouteBindRelayOutcome::Accepted,
2270                )
2271                .unwrap();
2272        }
2273        let (module_egress_tx, module_egress_rx) = mpsc::channel(8);
2274        (
2275            router,
2276            forwarding,
2277            RouteCtx {
2278                connection_id: client_connection,
2279                egress: client_sink,
2280            },
2281            client_rx,
2282            RouteCtx {
2283                connection_id: module_connection,
2284                egress: FrameSink::new(module_egress_tx),
2285            },
2286            module_egress_rx,
2287            module_rx,
2288            pending,
2289        )
2290    }
2291
2292    #[tokio::test]
2293    async fn route_epochs_validate_both_directions_and_rewrite_to_peer_handle() {
2294        let (
2295            router,
2296            _forwarding,
2297            client_ctx,
2298            mut client_rx,
2299            module_ctx,
2300            _module_egress_rx,
2301            mut module_rx,
2302            pending,
2303        ) = dynamic_route_fixture(true);
2304        let route_open = client_rx.recv().await.unwrap();
2305        assert_eq!(route_open.header.corr, 700);
2306
2307        router
2308            .route_for_connection(
2309                &client_ctx,
2310                route_frame(
2311                    FrameType::Request,
2312                    pending.client_channel,
2313                    pending.client_epoch,
2314                    701,
2315                ),
2316            )
2317            .await
2318            .unwrap();
2319        let forwarded = module_rx.recv().await.unwrap();
2320        assert_eq!(forwarded.header.channel, pending.module_channel);
2321        assert_eq!(forwarded.header.epoch, pending.module_epoch);
2322
2323        router
2324            .route_for_connection(
2325                &module_ctx,
2326                route_frame(
2327                    FrameType::Response,
2328                    pending.module_channel,
2329                    pending.module_epoch,
2330                    701,
2331                ),
2332            )
2333            .await
2334            .unwrap();
2335        let delivered = client_rx.recv().await.unwrap();
2336        assert_eq!(delivered.header.channel, pending.client_channel);
2337        assert_eq!(delivered.header.epoch, pending.client_epoch);
2338
2339        router
2340            .route_for_connection(
2341                &client_ctx,
2342                route_frame(
2343                    FrameType::Request,
2344                    pending.client_channel,
2345                    pending.client_epoch + 1,
2346                    702,
2347                ),
2348            )
2349            .await
2350            .unwrap();
2351        router
2352            .route_for_connection(
2353                &module_ctx,
2354                route_frame(
2355                    FrameType::Response,
2356                    pending.module_channel,
2357                    pending.module_epoch + 1,
2358                    703,
2359                ),
2360            )
2361            .await
2362            .unwrap();
2363        let stale_error = client_rx.recv().await.unwrap();
2364        assert_eq!(stale_error.header.ty, FrameType::Error);
2365        assert_eq!(stale_error.header.channel, pending.client_channel);
2366        assert_eq!(stale_error.header.epoch, pending.client_epoch + 1);
2367        assert_eq!(stale_error.header.corr, 702);
2368        let body: ErrorBody = serde_json::from_slice(&stale_error.body).unwrap();
2369        assert_eq!(body.code, "stale_route_epoch");
2370        assert!(module_rx.try_recv().is_err());
2371        assert!(client_rx.try_recv().is_err());
2372        let counters = router.counters.snapshot();
2373        assert_eq!(counters["client_frames_dropped_stale_route"], 1);
2374        assert_eq!(counters["module_frames_dropped_no_route"], 1);
2375    }
2376
2377    #[tokio::test]
2378    async fn accepted_route_publishes_route_open_before_immediate_reverse_request() {
2379        let (
2380            router,
2381            _,
2382            _client_ctx,
2383            mut client_rx,
2384            module_ctx,
2385            _module_egress_rx,
2386            _module_rx,
2387            pending,
2388        ) = dynamic_route_fixture(true);
2389        router
2390            .route_for_connection(
2391                &module_ctx,
2392                route_frame(
2393                    FrameType::Request,
2394                    pending.module_channel,
2395                    pending.module_epoch,
2396                    800,
2397                ),
2398            )
2399            .await
2400            .unwrap();
2401
2402        let first = client_rx.recv().await.unwrap();
2403        let second = client_rx.recv().await.unwrap();
2404        assert_eq!(first.header.channel, 0);
2405        assert_eq!(first.header.corr, 700);
2406        assert_eq!(second.header.channel, pending.client_channel);
2407        assert_eq!(second.header.epoch, pending.client_epoch);
2408        assert_eq!(second.header.corr, 800);
2409    }
2410
2411    #[tokio::test]
2412    async fn reserved_slot_ingress_errors_only_matching_client_requests() {
2413        let (
2414            router,
2415            _forwarding,
2416            client_ctx,
2417            mut client_rx,
2418            _module_ctx,
2419            _module_egress_rx,
2420            mut module_rx,
2421            pending,
2422        ) = dynamic_route_fixture(false);
2423        router
2424            .route_for_connection(
2425                &client_ctx,
2426                route_frame(
2427                    FrameType::Request,
2428                    pending.client_channel,
2429                    pending.client_epoch,
2430                    900,
2431                ),
2432            )
2433            .await
2434            .unwrap();
2435        let error = client_rx.recv().await.unwrap();
2436        assert_eq!(error.header.ty, FrameType::Error);
2437        assert_eq!(error.header.channel, pending.client_channel);
2438        assert_eq!(error.header.epoch, pending.client_epoch);
2439        assert_eq!(error.header.corr, 900);
2440
2441        router
2442            .route_for_connection(
2443                &client_ctx,
2444                route_frame(
2445                    FrameType::Response,
2446                    pending.client_channel,
2447                    pending.client_epoch,
2448                    901,
2449                ),
2450            )
2451            .await
2452            .unwrap();
2453        router
2454            .route_for_connection(
2455                &client_ctx,
2456                route_frame(
2457                    FrameType::Request,
2458                    pending.client_channel,
2459                    pending.client_epoch + 1,
2460                    902,
2461                ),
2462            )
2463            .await
2464            .unwrap();
2465        let stale_error = client_rx.recv().await.unwrap();
2466        assert_eq!(stale_error.header.ty, FrameType::Error);
2467        assert_eq!(stale_error.header.channel, pending.client_channel);
2468        assert_eq!(stale_error.header.epoch, pending.client_epoch + 1);
2469        assert_eq!(stale_error.header.corr, 902);
2470        let body: ErrorBody = serde_json::from_slice(&stale_error.body).unwrap();
2471        assert_eq!(body.code, "stale_route_epoch");
2472        assert!(module_rx.try_recv().is_err());
2473        let counters = router.counters.snapshot();
2474        assert_eq!(counters["client_frames_dropped_stale_route"], 1);
2475        assert_eq!(counters["module_frames_dropped_no_route"], 0);
2476    }
2477
2478    #[tokio::test]
2479    async fn dropped_module_route_goodbye_increments_counter() {
2480        let (
2481            router,
2482            _forwarding,
2483            client_ctx,
2484            mut client_rx,
2485            _module_ctx,
2486            _module_egress_rx,
2487            mut module_rx,
2488            pending,
2489        ) = dynamic_route_fixture(true);
2490        let _ = client_rx.recv().await;
2491        module_rx.close();
2492
2493        router
2494            .route_for_connection(
2495                &client_ctx,
2496                route_frame(
2497                    FrameType::Goodbye,
2498                    pending.client_channel,
2499                    pending.client_epoch,
2500                    999,
2501                ),
2502            )
2503            .await
2504            .unwrap();
2505
2506        let counters = router.counters.snapshot();
2507        assert_eq!(counters["goodbye_relay_module_dropped"], 1);
2508        assert_eq!(
2509            counters["goodbye_relay_module_dropped_by_module"],
2510            serde_json::json!({ "epoch-router": 1 })
2511        );
2512        assert_eq!(counters["route_released_epoch_fenced"], 1);
2513    }
2514
2515    #[tokio::test]
2516    async fn module_request_on_stale_epoch_receives_stale_route_epoch() {
2517        let (
2518            router,
2519            _forwarding,
2520            _client_ctx,
2521            _client_rx,
2522            module_ctx,
2523            mut module_egress_rx,
2524            mut module_rx,
2525            pending,
2526        ) = dynamic_route_fixture(true);
2527
2528        router
2529            .route_for_connection(
2530                &module_ctx,
2531                route_frame(
2532                    FrameType::Request,
2533                    pending.module_channel,
2534                    pending.module_epoch + 1,
2535                    1_000,
2536                ),
2537            )
2538            .await
2539            .unwrap();
2540
2541        let error = module_egress_rx.try_recv().unwrap();
2542        assert_eq!(error.header.ty, FrameType::Error);
2543        assert_eq!(error.header.channel, pending.module_channel);
2544        assert_eq!(error.header.epoch, pending.module_epoch + 1);
2545        assert_eq!(error.header.corr, 1_000);
2546        let body: ErrorBody = serde_json::from_slice(&error.body).unwrap();
2547        assert_eq!(body.code, "stale_route_epoch");
2548        assert!(module_rx.try_recv().is_err());
2549        let counters = router.counters.snapshot();
2550        assert_eq!(counters["module_requests_dropped_stale_route"], 1);
2551        assert_eq!(counters["module_frames_dropped_no_route"], 0);
2552    }
2553
2554    #[tokio::test]
2555    async fn module_request_on_reserved_or_absent_route_receives_unknown_channel() {
2556        let (
2557            reserved_router,
2558            _forwarding,
2559            _client_ctx,
2560            _client_rx,
2561            reserved_module_ctx,
2562            mut reserved_module_egress_rx,
2563            _module_rx,
2564            reserved,
2565        ) = dynamic_route_fixture(false);
2566        reserved_router
2567            .route_for_connection(
2568                &reserved_module_ctx,
2569                route_frame(
2570                    FrameType::Request,
2571                    reserved.module_channel,
2572                    reserved.module_epoch,
2573                    1_001,
2574                ),
2575            )
2576            .await
2577            .unwrap();
2578        let reserved_error = reserved_module_egress_rx.try_recv().unwrap();
2579        let reserved_body: ErrorBody = serde_json::from_slice(&reserved_error.body).unwrap();
2580        assert_eq!(reserved_error.header.ty, FrameType::Error);
2581        assert_eq!(reserved_error.header.channel, reserved.module_channel);
2582        assert_eq!(reserved_error.header.epoch, reserved.module_epoch);
2583        assert_eq!(reserved_error.header.corr, 1_001);
2584        assert_eq!(reserved_body.code, "unknown_channel");
2585        assert_eq!(
2586            reserved_router.counters.snapshot()["module_requests_dropped_stale_route"],
2587            1
2588        );
2589
2590        let (
2591            absent_router,
2592            _forwarding,
2593            _client_ctx,
2594            _client_rx,
2595            absent_module_ctx,
2596            mut absent_module_egress_rx,
2597            _module_rx,
2598            absent,
2599        ) = dynamic_route_fixture(false);
2600        absent_router
2601            .route_for_connection(
2602                &absent_module_ctx,
2603                route_frame(
2604                    FrameType::Request,
2605                    absent.module_channel + 1,
2606                    absent.module_epoch,
2607                    1_002,
2608                ),
2609            )
2610            .await
2611            .unwrap();
2612        let absent_error = absent_module_egress_rx.try_recv().unwrap();
2613        let absent_body: ErrorBody = serde_json::from_slice(&absent_error.body).unwrap();
2614        assert_eq!(absent_error.header.ty, FrameType::Error);
2615        assert_eq!(absent_error.header.channel, absent.module_channel + 1);
2616        assert_eq!(absent_error.header.epoch, absent.module_epoch);
2617        assert_eq!(absent_error.header.corr, 1_002);
2618        assert_eq!(absent_body.code, "unknown_channel");
2619        assert_eq!(
2620            absent_router.counters.snapshot()["module_requests_dropped_stale_route"],
2621            1
2622        );
2623    }
2624
2625    #[tokio::test]
2626    async fn non_request_module_frame_on_dead_route_is_counted_without_error() {
2627        let (
2628            router,
2629            forwarding,
2630            client_ctx,
2631            mut client_rx,
2632            module_ctx,
2633            mut module_egress_rx,
2634            mut module_rx,
2635            pending,
2636        ) = dynamic_route_fixture(true);
2637        let (other_module_tx, _other_module_rx) = mpsc::channel(8);
2638        forwarding
2639            .register_module_connection(
2640                ConnectionId::new(502),
2641                "other-module".into(),
2642                2,
2643                Concurrency::ModuleManaged,
2644                FrameSink::new(other_module_tx),
2645            )
2646            .unwrap();
2647        let _ = client_rx.recv().await.unwrap();
2648
2649        router
2650            .route_for_connection(
2651                &client_ctx,
2652                route_frame(
2653                    FrameType::Goodbye,
2654                    pending.client_channel,
2655                    pending.client_epoch,
2656                    1_003,
2657                ),
2658            )
2659            .await
2660            .unwrap();
2661        let _ = module_rx.recv().await.unwrap();
2662
2663        router
2664            .route_for_connection(
2665                &module_ctx,
2666                route_frame(
2667                    FrameType::StreamData,
2668                    pending.module_channel,
2669                    pending.module_epoch,
2670                    1_004,
2671                ),
2672            )
2673            .await
2674            .unwrap();
2675
2676        // No ERROR goes back for a non-request frame. The one reply is the
2677        // route GOODBYE telling the module to let go of the released route.
2678        let reply = module_egress_rx.try_recv().unwrap();
2679        assert_eq!(reply.header.ty, FrameType::Goodbye);
2680        assert!(module_egress_rx.try_recv().is_err());
2681        let counters = router.counters.snapshot();
2682        assert_eq!(counters["module_frames_dropped_no_route"], 1);
2683        assert_eq!(
2684            counters["module_frames_dropped_no_route_by_module"],
2685            serde_json::json!({ "epoch-router": 1 })
2686        );
2687        assert_eq!(counters["module_requests_dropped_stale_route"], 0);
2688    }
2689
2690    /// Drain whatever the module connection's queue holds right now, the way a
2691    /// module's reader would before it stalls.
2692    fn drain_now(rx: &mut mpsc::Receiver<OutboundFrame>) {
2693        while rx.try_recv().is_ok() {}
2694    }
2695
2696    /// A module that stops reading for a moment when a client closes one of
2697    /// its routes still learns the route is gone: the GOODBYE its full egress
2698    /// queue refused is delivered as soon as it reads again, rather than
2699    /// dropped, which would leave the module holding the route for the rest of
2700    /// its connection.
2701    #[tokio::test]
2702    async fn route_goodbye_refused_by_stalled_module_is_delivered_when_it_resumes_reading() {
2703        const BUDGET: usize = 4_096;
2704        let forwarding = Arc::new(ForwardingTable::default());
2705        let control = Arc::new(ControlHandler::with_forwarding(
2706            Arc::new(Registry::default()),
2707            Arc::clone(&forwarding),
2708        ));
2709        let router = Router::with_control_handler(control);
2710        let module_connection = ConnectionId::new(80);
2711        let client_connection = ConnectionId::new(81);
2712        let (module_tx, mut module_rx) = mpsc::channel(64);
2713        let module_sink = FrameSink::with_byte_budget(module_tx, BUDGET);
2714        forwarding
2715            .register_module_connection(
2716                module_connection,
2717                "stalled-provider".into(),
2718                2,
2719                Concurrency::ModuleManaged,
2720                module_sink.clone(),
2721            )
2722            .unwrap();
2723        let (client_tx, mut client_rx) = mpsc::channel(8);
2724        let client_sink = FrameSink::new(client_tx);
2725        let pending = forwarding
2726            .begin_route_bind_relay_for_test(
2727                client_connection,
2728                client_sink.clone(),
2729                1_100,
2730                "stalled-provider",
2731            )
2732            .unwrap();
2733        forwarding
2734            .complete_pending_relay(
2735                module_connection,
2736                pending.corr,
2737                RouteBindRelayOutcome::Accepted,
2738            )
2739            .unwrap();
2740        let _ = client_rx.recv().await.unwrap();
2741        drain_now(&mut module_rx);
2742
2743        // The module stalls: its queue holds more than the byte budget, so the
2744        // queue refuses anything further.
2745        module_sink
2746            .try_send(stream_frame(9, 1, 0, vec![b'f'; BUDGET]))
2747            .unwrap();
2748        assert!(module_sink
2749            .try_send(stream_frame(9, 1, 1, Vec::new()))
2750            .is_err());
2751
2752        let client_ctx = RouteCtx {
2753            connection_id: client_connection,
2754            egress: client_sink,
2755        };
2756        router
2757            .route_for_connection(
2758                &client_ctx,
2759                route_frame(
2760                    FrameType::Goodbye,
2761                    pending.client_channel,
2762                    pending.client_epoch,
2763                    1_101,
2764                ),
2765            )
2766            .await
2767            .unwrap();
2768        tokio::task::yield_now().await;
2769        assert_eq!(
2770            router.counters.snapshot()["goodbye_relay_module_dropped"],
2771            0,
2772            "a GOODBYE refused by a momentarily full module queue must not be dropped"
2773        );
2774
2775        // The module reads again: the filler comes off, then the GOODBYE.
2776        let filler = module_rx.recv().await.unwrap();
2777        assert_eq!(filler.header.ty, FrameType::StreamData);
2778        drop(filler);
2779        let goodbye = tokio::time::timeout(Duration::from_secs(2), module_rx.recv())
2780            .await
2781            .expect("the refused GOODBYE must be delivered once the module frees room")
2782            .unwrap();
2783        assert_eq!(goodbye.header.ty, FrameType::Goodbye);
2784        assert_eq!(goodbye.header.channel, pending.module_channel);
2785        assert_eq!(goodbye.header.epoch, pending.module_epoch);
2786        assert_eq!(
2787            router.counters.snapshot()["goodbye_relay_module_dropped"],
2788            0
2789        );
2790    }
2791
2792    /// A module still sending on a route the daemon released is told, with a
2793    /// route GOODBYE for exactly the (channel, epoch) it sent on. The same
2794    /// happens for a stale epoch on a channel whose route moved on and for a
2795    /// channel that never had a route; only the first is counted as traffic on
2796    /// a released route.
2797    #[tokio::test]
2798    async fn module_frame_on_route_the_daemon_does_not_hold_is_answered_with_goodbye() {
2799        let (
2800            router,
2801            _forwarding,
2802            client_ctx,
2803            mut client_rx,
2804            module_ctx,
2805            mut module_egress_rx,
2806            mut module_rx,
2807            pending,
2808        ) = dynamic_route_fixture(true);
2809        let _ = client_rx.recv().await.unwrap();
2810        router
2811            .route_for_connection(
2812                &client_ctx,
2813                route_frame(
2814                    FrameType::Goodbye,
2815                    pending.client_channel,
2816                    pending.client_epoch,
2817                    1_200,
2818                ),
2819            )
2820            .await
2821            .unwrap();
2822        drain_now(&mut module_rx);
2823
2824        let cases = [
2825            // Released route: the daemon allocated this (channel, epoch).
2826            (pending.module_channel, pending.module_epoch),
2827            // A channel the daemon never allocated on this connection.
2828            (pending.module_channel + 1, 1),
2829        ];
2830        for (channel, epoch) in cases {
2831            router
2832                .route_for_connection(
2833                    &module_ctx,
2834                    route_frame(FrameType::StreamData, channel, epoch, 1_201),
2835                )
2836                .await
2837                .unwrap();
2838            let reply = module_egress_rx
2839                .try_recv()
2840                .expect("a frame on a route the daemon does not hold is answered");
2841            assert_eq!(reply.header.ty, FrameType::Goodbye);
2842            assert_eq!(reply.header.channel, channel);
2843            assert_eq!(reply.header.epoch, epoch);
2844            assert_eq!(reply.header.corr, 0);
2845            assert!(module_egress_rx.try_recv().is_err());
2846        }
2847
2848        // A GOODBYE from the module for a route the daemon already released
2849        // is not answered: the module is letting go already.
2850        router
2851            .route_for_connection(
2852                &module_ctx,
2853                route_frame(FrameType::Goodbye, pending.module_channel + 2, 1, 0),
2854            )
2855            .await
2856            .unwrap();
2857        assert!(module_egress_rx.try_recv().is_err());
2858
2859        let counters = router.counters.snapshot();
2860        assert_eq!(counters["module_frames_dropped_no_route"], 3);
2861        assert_eq!(counters["module_frames_dropped_released_route"], 1);
2862        assert_eq!(
2863            counters["module_frames_dropped_released_route_by_module"],
2864            serde_json::json!({ "epoch-router": 1 })
2865        );
2866        assert_eq!(counters["module_orphan_route_goodbyes_sent"], 2);
2867    }
2868
2869    /// A chatty orphan (a streaming module still producing on a route it
2870    /// missed the GOODBYE for) is answered once per interval, not once per
2871    /// frame, and answered again once the interval has passed.
2872    #[tokio::test(start_paused = true)]
2873    async fn burst_of_orphan_module_frames_is_answered_once_per_interval() {
2874        let (
2875            router,
2876            _forwarding,
2877            client_ctx,
2878            mut client_rx,
2879            module_ctx,
2880            mut module_egress_rx,
2881            mut module_rx,
2882            pending,
2883        ) = dynamic_route_fixture(true);
2884        let _ = client_rx.recv().await.unwrap();
2885        router
2886            .route_for_connection(
2887                &client_ctx,
2888                route_frame(
2889                    FrameType::Goodbye,
2890                    pending.client_channel,
2891                    pending.client_epoch,
2892                    1_300,
2893                ),
2894            )
2895            .await
2896            .unwrap();
2897        drain_now(&mut module_rx);
2898
2899        let send_burst = |corr: u64| {
2900            route_frame(
2901                FrameType::StreamData,
2902                pending.module_channel,
2903                pending.module_epoch,
2904                corr,
2905            )
2906        };
2907        for corr in 0..20 {
2908            router
2909                .route_for_connection(&module_ctx, send_burst(corr))
2910                .await
2911                .unwrap();
2912        }
2913        let mut replies = 0;
2914        while let Ok(reply) = module_egress_rx.try_recv() {
2915            assert_eq!(reply.header.ty, FrameType::Goodbye);
2916            replies += 1;
2917        }
2918        assert_eq!(replies, 1, "a burst within the interval gets one GOODBYE");
2919
2920        tokio::time::advance(ORPHAN_ROUTE_GOODBYE_INTERVAL).await;
2921        router
2922            .route_for_connection(&module_ctx, send_burst(20))
2923            .await
2924            .unwrap();
2925        let retry = module_egress_rx
2926            .try_recv()
2927            .expect("the first orphan frame after the interval is answered again");
2928        assert_eq!(retry.header.channel, pending.module_channel);
2929        assert_eq!(retry.header.epoch, pending.module_epoch);
2930
2931        let counters = router.counters.snapshot();
2932        assert_eq!(counters["module_frames_dropped_no_route"], 21);
2933        assert_eq!(counters["module_orphan_route_goodbyes_sent"], 2);
2934    }
2935
2936    /// The rate-limit memory is per connection and goes away with it.
2937    #[test]
2938    fn orphan_goodbye_rate_limit_state_is_released_with_the_connection() {
2939        let router = Router::with_default_self_handler();
2940        let connection = router.begin_connection();
2941        let id = connection.id();
2942        assert!(router.orphan_goodbyes.claim(id, 7));
2943        assert!(!router.orphan_goodbyes.claim(id, 7));
2944        assert!(router.orphan_goodbyes.claim(id, 8));
2945        drop(connection);
2946        assert!(router
2947            .orphan_goodbyes
2948            .last_sent
2949            .lock()
2950            .unwrap()
2951            .get(&id)
2952            .is_none());
2953    }
2954
2955    #[test]
2956    fn channel_zero_cannot_be_registered_as_backend() {
2957        let mut router = Router::with_default_self_handler();
2958
2959        let err = router.register_backend(0, EchoBackend).unwrap_err();
2960
2961        assert_eq!(err, RouterError::ReservedChannelZero);
2962    }
2963}