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},
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        let output = Arc::new(Mutex::new(Vec::new()));
288        let writer = Arc::clone(&output);
289        let subscriber = tracing_subscriber::fmt()
290            .with_max_level(level)
291            .with_ansi(false)
292            .without_time()
293            .with_target(false)
294            .with_writer(move || TestLogWriter(Arc::clone(&writer)))
295            .finish();
296        let guard = tracing::subscriber::set_default(subscriber);
297        (output, guard)
298    }
299
300    pub(crate) fn captured_logs(output: &Arc<Mutex<Vec<u8>>>) -> String {
301        String::from_utf8(
302            output
303                .lock()
304                .expect("test log capture is not poisoned")
305                .clone(),
306        )
307        .expect("tracing output is UTF-8")
308    }
309}
310
311/// Cheaply cloneable handle to one connection's bounded outbound frame queue.
312///
313/// Backends emit responses, streaming frames, and future PUSH frames through this
314/// single path. The queue is bounded twice: by queued BYTES (the budget that
315/// matters for memory and for how long a slow reader may pause), and by the
316/// `mpsc` channel's frame count (a backstop). The socket layer owns the sole
317/// receiver/writer.
318#[derive(Debug, Clone)]
319pub struct FrameSink {
320    tx: mpsc::Sender<OutboundFrame>,
321    accounting: Arc<EgressAccounting>,
322}
323
324impl FrameSink {
325    /// A sink with the standard per-connection byte budget
326    /// ([`crate::server::CONNECTION_EGRESS_BYTE_BUDGET`]); the frame-count bound
327    /// is whatever capacity `tx`'s channel was created with.
328    pub fn new(tx: mpsc::Sender<OutboundFrame>) -> Self {
329        Self::with_byte_budget(tx, crate::server::CONNECTION_EGRESS_BYTE_BUDGET)
330    }
331
332    pub(crate) fn with_byte_budget(tx: mpsc::Sender<OutboundFrame>, byte_budget: usize) -> Self {
333        Self {
334            tx,
335            accounting: Arc::new(EgressAccounting::new(byte_budget)),
336        }
337    }
338
339    /// Wait until the frame's bytes fit the budget, then charge them. Awaited
340    /// senders (control replies, error replies, shutdown notices) are never
341    /// refused by the byte budget: they wait for the writer to free bytes, just
342    /// as they wait for a free slot, which is the same backpressure they had
343    /// when the queue was bounded by frame count alone. Returns `None` if the
344    /// writer goes away while waiting.
345    async fn charge_waiting(&self, bytes: usize) -> Option<EgressCharge> {
346        if let Some(charge) = self.accounting.try_charge(bytes) {
347            return Some(charge);
348        }
349        // Counted as a waiter for as long as this future is parked, including
350        // when it is cancelled, so releases notify only while someone waits.
351        struct Waiting<'a>(&'a AtomicUsize);
352        impl Drop for Waiting<'_> {
353            fn drop(&mut self) {
354                self.0.fetch_sub(1, Ordering::SeqCst);
355            }
356        }
357        self.accounting.waiters.fetch_add(1, Ordering::SeqCst);
358        let _waiting = Waiting(&self.accounting.waiters);
359        loop {
360            let freed = self.accounting.freed.notified();
361            tokio::pin!(freed);
362            // Register interest before checking, so a release that happens
363            // between the check and the await still wakes this sender.
364            freed.as_mut().enable();
365            if let Some(charge) = self.accounting.try_charge(bytes) {
366                return Some(charge);
367            }
368            tokio::select! {
369                _ = &mut freed => {}
370                _ = self.tx.closed() => return None,
371            }
372        }
373    }
374
375    pub async fn send(&self, frame: Frame) -> Result<(), RouterError> {
376        let channel = frame.header.channel;
377        let epoch = frame.header.epoch;
378        let corr = frame.header.corr;
379        let closed =
380            || RouterError::backend_with_epoch(channel, epoch, corr, "connection writer closed");
381        let charge = self
382            .charge_waiting(queued_frame_bytes(&frame))
383            .await
384            .ok_or_else(closed)?;
385        self.tx
386            .send(OutboundFrame::charged(frame, charge))
387            .await
388            .map_err(|_| closed())
389    }
390
391    /// Shutdown notices must leave the socket writer before an idle daemon exits.
392    /// Queue admission alone does not prove this; the writer acknowledges flush.
393    #[cfg(unix)]
394    pub(crate) async fn send_flushed(&self, frame: Frame) -> Result<(), RouterError> {
395        let (tx, rx) = tokio::sync::oneshot::channel();
396        let charge = self
397            .charge_waiting(queued_frame_bytes(&frame))
398            .await
399            .ok_or_else(|| RouterError::backend(0, 0, "connection writer closed"))?;
400        let mut outbound = OutboundFrame::charged(frame, charge);
401        outbound.flushed = Some(tx);
402        self.tx
403            .send(outbound)
404            .await
405            .map_err(|_| RouterError::backend(0, 0, "connection writer closed"))?;
406        rx.await
407            .map_err(|_| RouterError::backend(0, 0, "connection flush failed"))
408    }
409
410    pub(crate) async fn reserve_owned(&self) -> Result<EgressPermit, RouterError> {
411        let permit = self
412            .tx
413            .clone()
414            .reserve_owned()
415            .await
416            .map_err(|_| RouterError::backend(0, 0, "connection writer closed"))?;
417        Ok(EgressPermit {
418            permit,
419            accounting: Arc::clone(&self.accounting),
420        })
421    }
422
423    #[cfg(test)]
424    pub(crate) fn try_reserve_owned(&self) -> Result<EgressPermit, RouterError> {
425        let permit = self
426            .tx
427            .clone()
428            .try_reserve_owned()
429            .map_err(|err| RouterError::backend(0, 0, err.to_string()))?;
430        Ok(EgressPermit {
431            permit,
432            accounting: Arc::clone(&self.accounting),
433        })
434    }
435
436    pub(crate) fn is_closed(&self) -> bool {
437        self.tx.is_closed()
438    }
439
440    /// What the connection's egress queue holds right now.
441    pub(crate) fn backlog(&self) -> EgressBacklog {
442        self.accounting.backlog()
443    }
444
445    /// Enqueue without waiting. Fails when the frame's bytes would take the
446    /// queue past its byte budget, when the channel's frame-count backstop is
447    /// full, or when the writer is gone.
448    pub(crate) fn try_send(&self, frame: Frame) -> Result<(), RouterError> {
449        let channel = frame.header.channel;
450        let epoch = frame.header.epoch;
451        let corr = frame.header.corr;
452        let unavailable = |why: String| {
453            RouterError::backend_with_epoch(
454                channel,
455                epoch,
456                corr,
457                format!("connection writer unavailable: {why}"),
458            )
459        };
460        let bytes = queued_frame_bytes(&frame);
461        let Some(charge) = self.accounting.try_charge(bytes) else {
462            if self.tx.is_closed() {
463                return Err(unavailable("channel closed".to_string()));
464            }
465            return Err(unavailable(format!(
466                "egress byte budget exhausted ({} queued bytes, frame of {bytes} bytes, budget {})",
467                self.accounting.queued_bytes.load(Ordering::Acquire),
468                self.accounting.byte_budget
469            )));
470        };
471        // A refused frame is dropped inside the error, which returns its charge.
472        self.tx
473            .try_send(OutboundFrame::charged(frame, charge))
474            .map_err(|err| unavailable(err.to_string()))
475    }
476}
477
478/// Minimum time between two route GOODBYEs the daemon sends one module
479/// connection for one channel in answer to the module's frames on a (channel,
480/// epoch) the daemon holds no route for.
481///
482/// A module that missed a GOODBYE keeps sending on that route (a streaming
483/// module, many frames a second), and every one of those frames lands here. One
484/// answer is enough when it arrives, so answering each frame would only flood
485/// the module's egress queue at the moment it is already behind. Five seconds
486/// is long enough for an answer queued behind a deep backlog to reach the module
487/// and take effect before a second is sent, and short enough that an answer
488/// the queue refused is retried by the next orphan frame well within a minute.
489const ORPHAN_ROUTE_GOODBYE_INTERVAL: Duration = Duration::from_secs(5);
490
491/// Once one connection has this many remembered channels, entries older than
492/// [`ORPHAN_ROUTE_GOODBYE_INTERVAL`] are pruned before another is added. An
493/// expired entry has no effect on rate limiting, so pruning never changes a
494/// decision; it keeps a module that orphaned many channels long ago from
495/// pinning memory for all of them. The map is bounded in any case by the
496/// 16-bit channel space and is dropped whole when the connection ends.
497const ORPHAN_ROUTE_GOODBYE_PRUNE_AT: usize = 256;
498
499/// When each module connection was last sent an orphan-route GOODBYE, per
500/// channel. Shared by the [`Router`] (which consults it) and every
501/// [`RouterConnection`] (which removes its own entry when the connection ends).
502#[derive(Debug, Default)]
503struct OrphanGoodbyeLimiter {
504    last_sent: Mutex<HashMap<ConnectionId, HashMap<u16, tokio::time::Instant>>>,
505}
506
507impl OrphanGoodbyeLimiter {
508    /// True, and the send recorded, when `channel` on `connection_id` has not
509    /// been answered within the interval.
510    fn claim(&self, connection_id: ConnectionId, channel: u16) -> bool {
511        let now = tokio::time::Instant::now();
512        let mut last_sent = self
513            .last_sent
514            .lock()
515            .unwrap_or_else(std::sync::PoisonError::into_inner);
516        let channels = last_sent.entry(connection_id).or_default();
517        if channels.get(&channel).is_some_and(|sent| {
518            now.saturating_duration_since(*sent) < ORPHAN_ROUTE_GOODBYE_INTERVAL
519        }) {
520            return false;
521        }
522        if channels.len() >= ORPHAN_ROUTE_GOODBYE_PRUNE_AT {
523            channels.retain(|_, sent| {
524                now.saturating_duration_since(*sent) < ORPHAN_ROUTE_GOODBYE_INTERVAL
525            });
526        }
527        channels.insert(channel, now);
528        true
529    }
530
531    fn forget_connection(&self, connection_id: ConnectionId) {
532        self.last_sent
533            .lock()
534            .unwrap_or_else(std::sync::PoisonError::into_inner)
535            .remove(&connection_id);
536    }
537}
538
539/// Per-route context shared with backends besides the frame itself.
540#[derive(Debug, Clone)]
541pub struct RouteCtx {
542    pub connection_id: ConnectionId,
543    pub egress: FrameSink,
544}
545
546/// Closed set of data-plane backends for non-zero channels.
547///
548/// Channel 0 is structurally special and remains [`Router::control`], not an
549/// enum variant. Static/test backends live in [`Router::backends`]; the forwarding
550/// backend is selected dynamically only when a per-connection forwarding binding exists.
551#[derive(Debug, Clone)]
552pub enum Backend {
553    Echo(EchoBackend),
554    Forward(ForwardBackend),
555}
556
557impl From<EchoBackend> for Backend {
558    fn from(backend: EchoBackend) -> Self {
559        Self::Echo(backend)
560    }
561}
562
563impl From<ForwardBackend> for Backend {
564    fn from(backend: ForwardBackend) -> Self {
565        Self::Forward(backend)
566    }
567}
568
569impl Backend {
570    pub async fn handle(&self, ctx: RouteCtx, frame: Frame) -> Result<(), RouterError> {
571        match self {
572            Self::Echo(backend) => backend.handle(ctx, frame).await,
573            Self::Forward(backend) => backend.handle(ctx, frame).await,
574        }
575    }
576}
577
578/// I/O-agnostic splice router keyed by envelope `channel`.
579///
580/// Channel 0 is reserved for subc itself and is always dispatched to the
581/// dedicated control handler. Other channels must be explicitly registered.
582/// Unknown client-originated non-zero channels are translated to canonical JSON `ERROR` frames on
583/// the connection sink so the peer can continue using the same socket. Module-originated frames for
584/// released route channels are logged and dropped as the channel-gone race backstop.
585///
586/// DATA-PLANE BODIES ARE NEVER DECODED HERE, and the consequence is worth stating
587/// because it looks like a guarantee and is not. Additive fields in a request or
588/// response body reach the far side untouched — not because anything permits them,
589/// but because routing reads only the 21-byte header and treats the body as opaque
590/// bytes. That is a performance property, so **nothing prevents it from changing**:
591/// a future reason to inspect a body would convert a wire-transparent path into a
592/// filtering one, and nobody would think of it as a contract change.
593///
594/// So when a peer asks whether subc sees an additive field, the answer is per-path
595/// and this path's zero means WE NEVER LOOK rather than WE LOOK AT EVERYTHING. The
596/// control plane (typed enums at the frame boundary) and the MCP gateway (envelope
597/// unwrap plus a named struct) both narrow; only this one does not.
598pub struct Router {
599    backends: HashMap<u16, Backend>,
600    control: Arc<ControlHandler>,
601    forwarding: Arc<ForwardingTable>,
602    forward_backend: ForwardBackend,
603    counters: DaemonCounters,
604    next_connection_id: AtomicU64,
605    orphan_goodbyes: Arc<OrphanGoodbyeLimiter>,
606}
607
608impl Router {
609    pub fn with_control_handler(control: Arc<ControlHandler>) -> Self {
610        // The handler the router serves is the one a swap must tell when it
611        // promotes a candidate; this is where it first sits behind an `Arc`.
612        control.install_swap_promotion_observer();
613        let forwarding = control.forwarding();
614        let counters = control.counters();
615        Self {
616            backends: HashMap::new(),
617            control,
618            forwarding: Arc::clone(&forwarding),
619            forward_backend: ForwardBackend::new(forwarding),
620            counters,
621            // ConnectionId::LOCAL is 0; real socket ids start at 1 and never collide.
622            next_connection_id: AtomicU64::new(1),
623            orphan_goodbyes: Arc::default(),
624        }
625    }
626
627    pub fn with_default_self_handler() -> Self {
628        Self::with_control_handler(Arc::new(ControlHandler::default()))
629    }
630
631    pub fn forwarding(&self) -> Arc<ForwardingTable> {
632        Arc::clone(&self.forwarding)
633    }
634
635    pub fn register_backend(
636        &mut self,
637        channel: u16,
638        backend: impl Into<Backend>,
639    ) -> Result<(), RouterError> {
640        self.register_backend_arc(channel, Arc::new(backend.into()))
641    }
642
643    pub(crate) fn register_backend_arc(
644        &mut self,
645        channel: u16,
646        backend: Arc<Backend>,
647    ) -> Result<(), RouterError> {
648        if channel == 0 {
649            return Err(RouterError::ReservedChannelZero);
650        }
651        if self.backends.contains_key(&channel) {
652            return Err(RouterError::DuplicateChannel { channel });
653        }
654        self.backends.insert(channel, backend.as_ref().clone());
655        Ok(())
656    }
657
658    /// Start a connection-scoped routing context. Dropping the guard releases
659    /// any control-plane registrations owned by the connection.
660    fn record_module_frame_drop(&self, connection_id: ConnectionId) -> Result<(), RouterError> {
661        let module_id = self
662            .forwarding
663            .module_id_for_connection(connection_id)
664            .map_err(RouterError::Forwarding)?;
665        self.counters
666            .increment_module_frames_dropped_no_route(module_id.as_deref());
667        Ok(())
668    }
669
670    /// A module sent a non-request frame on a (channel, epoch) the daemon holds
671    /// no route for: most often a route the daemon released whose GOODBYE the
672    /// module never received, so the module still believes it is open and keeps
673    /// sending. Count the drop, and tell the module to let go of exactly that
674    /// (channel, epoch) with a route GOODBYE, at most once per
675    /// [`ORPHAN_ROUTE_GOODBYE_INTERVAL`] per connection and channel.
676    ///
677    /// The answer uses `try_send`: it is a best-effort nudge, and if the queue
678    /// refuses it, the module's next frame on that route after the interval
679    /// asks again. A GOODBYE from the module is not answered, since the module
680    /// is already letting go of the route.
681    fn handle_orphan_module_frame(&self, ctx: &RouteCtx, frame: &Frame) -> Result<(), RouterError> {
682        let channel = frame.header.channel;
683        let epoch = frame.header.epoch;
684        let module_id = self
685            .forwarding
686            .module_id_for_connection(ctx.connection_id)
687            .map_err(RouterError::Forwarding)?;
688        self.counters
689            .increment_module_frames_dropped_no_route(module_id.as_deref());
690        if self
691            .forwarding
692            .module_route_epoch_was_allocated(ctx.connection_id, channel, epoch)
693            .map_err(RouterError::Forwarding)?
694        {
695            self.counters
696                .increment_module_frames_dropped_released_route(module_id.as_deref());
697        }
698        if frame.header.ty == FrameType::Goodbye
699            || !self.orphan_goodbyes.claim(ctx.connection_id, channel)
700        {
701            return Ok(());
702        }
703        let goodbye = Frame::build_with_version(
704            frame.header.ver,
705            FrameType::Goodbye,
706            Flags::new(false, Priority::Passive, false),
707            channel,
708            epoch,
709            0,
710            Vec::new(),
711        )
712        .map_err(RouterError::FrameBuild)?;
713        match ctx.egress.try_send(goodbye) {
714            Ok(()) => {
715                self.counters.increment_module_orphan_route_goodbyes_sent();
716                debug!(
717                    connection_id = ctx.connection_id.get(),
718                    module_id = module_id.as_deref().unwrap_or("unknown"),
719                    channel,
720                    epoch,
721                    "answered module frame on a route the daemon does not hold with a route GOODBYE"
722                );
723            }
724            Err(err) => debug!(
725                connection_id = ctx.connection_id.get(),
726                module_id = module_id.as_deref().unwrap_or("unknown"),
727                channel,
728                epoch,
729                error = %err,
730                "could not enqueue route GOODBYE for module frame on a route the daemon does not hold; the next such frame retries"
731            ),
732        }
733        Ok(())
734    }
735
736    pub fn begin_connection(&self) -> RouterConnection {
737        let raw = self.next_connection_id.fetch_add(1, Ordering::Relaxed);
738        let id = ConnectionId::new(raw);
739        let close_receiver = self.forwarding.register_connection_close(id);
740        RouterConnection {
741            id,
742            control_handler: Arc::clone(&self.control),
743            forwarding: Arc::clone(&self.forwarding),
744            close_receiver: Some(close_receiver),
745            orphan_goodbyes: Arc::clone(&self.orphan_goodbyes),
746        }
747    }
748
749    pub(crate) fn route_open_target(&self, frame: &Frame) -> Option<String> {
750        self.control.route_open_target(frame)
751    }
752
753    pub(crate) fn route_open_capacity_refusal(
754        &self,
755        ctx: &RouteCtx,
756        frame: &Frame,
757        target_module_id: &str,
758        in_flight: usize,
759        limit: usize,
760    ) -> Result<Frame, RouterError> {
761        self.control
762            .route_open_capacity_refusal(ctx, frame, target_module_id, in_flight, limit)
763    }
764
765    pub async fn route_for_connection(
766        &self,
767        ctx: &RouteCtx,
768        frame: Frame,
769    ) -> Result<(), RouterError> {
770        self.route_for_connection_started(ctx, frame, None).await
771    }
772
773    pub(crate) async fn route_for_connection_started(
774        &self,
775        ctx: &RouteCtx,
776        frame: Frame,
777        dispatch_started_at: Option<Instant>,
778    ) -> Result<(), RouterError> {
779        let channel = frame.header.channel;
780        let epoch = frame.header.epoch;
781        let corr = frame.header.corr;
782        if channel == 0 {
783            debug!(
784                connection_id = ctx.connection_id.get(),
785                corr,
786                frame_type = ?frame.header.ty,
787                "routing control frame"
788            );
789            // The connection loop dispatches every control operation directly
790            // except `route.open`. Its task passes a timestamp captured before
791            // spawn, so slow-dispatch timing includes scheduler delay but no
792            // wait in an application-owned queue.
793            let dispatch_started_at = (frame.header.ty == FrameType::Request)
794                .then(|| dispatch_started_at.unwrap_or_else(Instant::now));
795            let responses = self
796                .control
797                .handle_control_frame_timed(ctx, frame, dispatch_started_at)
798                .await?;
799            for response in responses {
800                ctx.egress.send(response).await?;
801            }
802            return Ok(());
803        }
804
805        let data_route = self
806            .forwarding
807            .lookup_data_route(ctx.connection_id, channel, epoch)
808            .map_err(RouterError::Forwarding)?;
809
810        match data_route {
811            DataRoute::Module(DataRouteState::EpochMismatch) => {
812                if frame.header.ty == FrameType::Request {
813                    self.counters
814                        .increment_module_requests_dropped_stale_route();
815                    let err = RouterError::StaleRouteEpoch {
816                        channel,
817                        epoch,
818                        corr,
819                    };
820                    if let Some(error_frame) = err.to_error_frame() {
821                        ctx.egress.send(error_frame).await?;
822                    }
823                } else {
824                    self.handle_orphan_module_frame(ctx, &frame)?;
825                }
826                debug!(
827                    connection_id = ctx.connection_id.get(),
828                    channel, epoch, corr, "dropping module frame for stale route epoch"
829                );
830                return Ok(());
831            }
832            DataRoute::Module(DataRouteState::Reserved) => {
833                if frame.header.ty == FrameType::Request {
834                    self.counters
835                        .increment_module_requests_dropped_stale_route();
836                    let err = RouterError::UnknownChannel {
837                        channel,
838                        epoch,
839                        corr,
840                    };
841                    if let Some(error_frame) = err.to_error_frame() {
842                        ctx.egress.send(error_frame).await?;
843                    }
844                } else {
845                    self.record_module_frame_drop(ctx.connection_id)?;
846                }
847                debug!(
848                    connection_id = ctx.connection_id.get(),
849                    channel, epoch, corr, "dropping module frame for reserved route handle"
850                );
851                return Ok(());
852            }
853            DataRoute::Module(DataRouteState::Absent) => {
854                if frame.header.ty == FrameType::Request {
855                    self.counters
856                        .increment_module_requests_dropped_stale_route();
857                    let err = RouterError::UnknownChannel {
858                        channel,
859                        epoch,
860                        corr,
861                    };
862                    if let Some(error_frame) = err.to_error_frame() {
863                        ctx.egress.send(error_frame).await?;
864                    }
865                } else {
866                    self.handle_orphan_module_frame(ctx, &frame)?;
867                }
868                debug!(
869                    connection_id = ctx.connection_id.get(),
870                    channel, epoch, corr, "dropping module frame for absent route handle"
871                );
872                return Ok(());
873            }
874            DataRoute::Module(DataRouteState::Bound(route)) => {
875                if frame.header.ty == FrameType::Goodbye {
876                    if let RouteRelease::Removed(target) = self
877                        .forwarding
878                        .release_module_route(ctx.connection_id, channel, epoch)
879                        .map_err(RouterError::Forwarding)?
880                    {
881                        let mut goodbye = frame;
882                        goodbye.header.channel = target.channel;
883                        goodbye.header.epoch = target.epoch;
884                        if let Err(err) = target.sink.try_send(goodbye) {
885                            if target.close_on_delivery_failure()
886                                && self
887                                    .forwarding
888                                    .escalate_client_delivery_failure(
889                                        target.connection_id,
890                                        target.channel,
891                                        target.epoch,
892                                        CloseReason::new(
893                                            "route_goodbye_delivery_failed",
894                                            format!(
895                                                "failed to enqueue route GOODBYE for client channel {}: {err}",
896                                                target.channel
897                                            ),
898                                        ),
899                                        UndeliveredFrame {
900                                            module_id: Some(&route.module_id),
901                                            sink: &target.sink,
902                                        },
903                                    )
904                                    .map_err(RouterError::Forwarding)?
905                            {
906                                self.counters.increment_goodbye_relay_client_failed();
907                            }
908                        }
909                    }
910                    return Ok(());
911                }
912
913                // A terminal frame ends the request at the module whether or not
914                // the client can still take it, so its credit is released before
915                // delivery is attempted. Releasing only after a successful
916                // enqueue would leave a drain counting a finished request until
917                // the client connection's cleanup removes the route.
918                let releases_credit = is_terminal_frame(frame.header.ty);
919                if releases_credit {
920                    route.flow.release_corr(corr);
921                }
922                let mut frame = frame;
923                frame.header.channel = route.client_channel;
924                frame.header.epoch = route.client_epoch;
925                if let Err(err) = route.client_sink.try_send(frame) {
926                    if self
927                        .forwarding
928                        .escalate_client_delivery_failure(
929                            route.client_connection_id,
930                            route.client_channel,
931                            route.client_epoch,
932                            CloseReason::new(
933                                "module_to_client_delivery_failed",
934                                format!(
935                                    "failed to enqueue module frame for client channel {} corr {corr}: {err}",
936                                    route.client_channel
937                                ),
938                            ),
939                            UndeliveredFrame {
940                                module_id: Some(&route.module_id),
941                                sink: &route.client_sink,
942                            },
943                        )
944                        .map_err(RouterError::Forwarding)?
945                    {
946                        self.counters
947                            .increment_client_egress_close_delivery_failed();
948                    }
949                    return Ok(());
950                }
951                return Ok(());
952            }
953            DataRoute::Client(DataRouteState::EpochMismatch) => {
954                if frame.header.ty == FrameType::Request {
955                    self.counters.increment_client_frames_dropped_stale_route();
956                    // Dropped before forwarding; a re-bind retry cannot double-execute this request.
957                    let err = RouterError::StaleRouteEpoch {
958                        channel,
959                        epoch,
960                        corr,
961                    };
962                    if let Some(error_frame) = err.to_error_frame() {
963                        ctx.egress.send(error_frame).await?;
964                    }
965                }
966                debug!(
967                    connection_id = ctx.connection_id.get(),
968                    channel, epoch, corr, "dropping client frame for stale route epoch"
969                );
970                return Ok(());
971            }
972            DataRoute::Client(DataRouteState::Reserved) => {
973                if frame.header.ty == FrameType::Request {
974                    let err = RouterError::UnknownChannel {
975                        channel,
976                        epoch,
977                        corr,
978                    };
979                    if let Some(error_frame) = err.to_error_frame() {
980                        ctx.egress.send(error_frame).await?;
981                    }
982                }
983                return Ok(());
984            }
985            DataRoute::Client(DataRouteState::Bound(route)) => {
986                if frame.header.ty == FrameType::Goodbye {
987                    let _ = self
988                        .control
989                        .handle_route_goodbye(ctx.connection_id, channel, epoch)?;
990                    return Ok(());
991                }
992                return self.forward_backend.handle_bound(frame, route).await;
993            }
994            DataRoute::Client(DataRouteState::Absent) => {}
995        }
996
997        if let Some(backend) = self.backends.get(&channel) {
998            return backend.handle(ctx.clone(), frame).await;
999        }
1000        if frame.header.ty == FrameType::Request {
1001            let err = RouterError::UnknownChannel {
1002                channel,
1003                epoch,
1004                corr,
1005            };
1006            if let Some(error_frame) = err.to_error_frame() {
1007                ctx.egress.send(error_frame).await?;
1008            }
1009        }
1010        Ok(())
1011    }
1012}
1013
1014impl Default for Router {
1015    fn default() -> Self {
1016        Self::with_default_self_handler()
1017    }
1018}
1019
1020/// Connection-scoped cleanup guard returned by [`Router::begin_connection`].
1021#[must_use]
1022pub struct RouterConnection {
1023    id: ConnectionId,
1024    control_handler: Arc<ControlHandler>,
1025    forwarding: Arc<ForwardingTable>,
1026    close_receiver: Option<ConnectionCloseReceiver>,
1027    orphan_goodbyes: Arc<OrphanGoodbyeLimiter>,
1028}
1029
1030impl RouterConnection {
1031    pub fn id(&self) -> ConnectionId {
1032        self.id
1033    }
1034
1035    pub(crate) fn take_close_receiver(&mut self) -> ConnectionCloseReceiver {
1036        self.close_receiver
1037            .take()
1038            .expect("connection close receiver can only be taken once")
1039    }
1040}
1041
1042impl Drop for RouterConnection {
1043    fn drop(&mut self) {
1044        self.forwarding.unregister_connection_close(self.id);
1045        self.orphan_goodbyes.forget_connection(self.id);
1046        // GOODBYE (explicit) and connection-drop cleanup both call the same
1047        // idempotent deregistration path.
1048        let _ = self.control_handler.cleanup_connection(self.id);
1049    }
1050}
1051
1052/// Minimal in-memory backend used by tests and early wiring: it emits a
1053/// `RESPONSE` on the same channel/correlation id with the exact same body bytes.
1054#[derive(Debug, Default, Clone, Copy)]
1055pub struct EchoBackend;
1056
1057impl EchoBackend {
1058    pub async fn handle(&self, ctx: RouteCtx, frame: Frame) -> Result<(), RouterError> {
1059        let response = Frame::build_with_version(
1060            frame.header.ver,
1061            FrameType::Response,
1062            frame.header.flags,
1063            frame.header.channel,
1064            frame.header.epoch,
1065            frame.header.corr,
1066            frame.body,
1067        )
1068        .map_err(RouterError::FrameBuild)?;
1069        ctx.egress.send(response).await
1070    }
1071}
1072
1073/// Data-plane backend that splices client frames to the module connection bound at attach time.
1074#[derive(Debug, Clone)]
1075pub struct ForwardBackend {
1076    forwarding: Arc<ForwardingTable>,
1077}
1078
1079impl ForwardBackend {
1080    pub fn new(forwarding: Arc<ForwardingTable>) -> Self {
1081        Self { forwarding }
1082    }
1083
1084    pub async fn handle(&self, ctx: RouteCtx, frame: Frame) -> Result<(), RouterError> {
1085        let channel = frame.header.channel;
1086        let corr = frame.header.corr;
1087        let route = match self
1088            .forwarding
1089            .lookup_data_route(ctx.connection_id, channel, frame.header.epoch)
1090            .map_err(RouterError::Forwarding)?
1091        {
1092            DataRoute::Client(DataRouteState::Bound(route)) => route,
1093            DataRoute::Client(_) | DataRoute::Module(_) => {
1094                return Err(RouterError::UnknownChannel {
1095                    channel,
1096                    epoch: frame.header.epoch,
1097                    corr,
1098                });
1099            }
1100        };
1101        self.handle_bound(frame, route).await
1102    }
1103
1104    pub(crate) async fn handle_bound(
1105        &self,
1106        frame: Frame,
1107        route: Arc<RouteBinding>,
1108    ) -> Result<(), RouterError> {
1109        let channel = frame.header.channel;
1110        let corr = frame.header.corr;
1111        let frame_type = frame.header.ty;
1112
1113        // CANCEL and other non-REQUEST frames bypass the request-credit window;
1114        // the original request's credit is freed only by the module's terminal frame.
1115        let acquired_credit = frame_type == FrameType::Request;
1116        if acquired_credit {
1117            if let Err(err) = route
1118                .flow
1119                .acquire_tagged(corr, frame.header.flags.is_subscription())
1120                .await
1121            {
1122                // `module_reloading` here is answered BEFORE the frame is
1123                // forwarded, so the module never sees the request and callers
1124                // may re-dispatch it after reopening the route. That is a wire
1125                // guarantee documented on `error_codes::MODULE_RELOADING`; do
1126                // not emit this code for a request that already reached
1127                // `module_sink.send` below.
1128                if self
1129                    .forwarding
1130                    .endpoint_is_draining(route.module_endpoint)
1131                    .map_err(RouterError::Forwarding)?
1132                {
1133                    return Err(RouterError::route_error_with_epoch(
1134                        channel,
1135                        frame.header.epoch,
1136                        corr,
1137                        "module_reloading",
1138                        format!("module endpoint for route channel {channel} is reloading"),
1139                    ));
1140                }
1141                return Err(RouterError::backend_with_epoch(
1142                    channel,
1143                    frame.header.epoch,
1144                    corr,
1145                    format!("{err} for route channel {channel}"),
1146                ));
1147            }
1148        }
1149
1150        let mut frame = frame;
1151        frame.header.channel = route.module_channel;
1152        frame.header.epoch = route.module_epoch;
1153        let result = route.module_sink.send(frame).await.map_err(|err| {
1154            RouterError::backend_with_epoch(channel, route.client_epoch, corr, err.to_string())
1155        });
1156        if acquired_credit && result.is_err() {
1157            route.flow.release_corr(corr);
1158        }
1159        result
1160    }
1161}
1162
1163fn is_terminal_frame(frame_type: FrameType) -> bool {
1164    matches!(
1165        frame_type,
1166        FrameType::Response | FrameType::Error | FrameType::StreamEnd
1167    )
1168}
1169
1170/// Typed router errors. Routable failures can be translated to canonical JSON
1171/// `ERROR` frames with [`RouterError::to_error_frame`].
1172#[derive(Debug, Clone, PartialEq, Eq)]
1173pub enum RouterError {
1174    ReservedChannelZero,
1175    DuplicateChannel {
1176        channel: u16,
1177    },
1178    UnknownChannel {
1179        channel: u16,
1180        epoch: u32,
1181        corr: u64,
1182    },
1183    StaleRouteEpoch {
1184        channel: u16,
1185        epoch: u32,
1186        corr: u64,
1187    },
1188    Backend {
1189        channel: u16,
1190        epoch: u32,
1191        corr: u64,
1192        message: String,
1193    },
1194    RouteError {
1195        channel: u16,
1196        epoch: u32,
1197        corr: u64,
1198        code: String,
1199        message: String,
1200    },
1201    FrameBuild(FrameBuildError),
1202    Forwarding(ForwardingError),
1203}
1204
1205impl RouterError {
1206    pub fn backend(channel: u16, corr: u64, message: impl Into<String>) -> Self {
1207        Self::backend_with_epoch(channel, 0, corr, message)
1208    }
1209
1210    pub fn backend_with_epoch(
1211        channel: u16,
1212        epoch: u32,
1213        corr: u64,
1214        message: impl Into<String>,
1215    ) -> Self {
1216        Self::Backend {
1217            channel,
1218            epoch,
1219            corr,
1220            message: message.into(),
1221        }
1222    }
1223
1224    pub fn route_error(
1225        channel: u16,
1226        corr: u64,
1227        code: impl Into<String>,
1228        message: impl Into<String>,
1229    ) -> Self {
1230        Self::route_error_with_epoch(channel, 0, corr, code, message)
1231    }
1232
1233    pub fn route_error_with_epoch(
1234        channel: u16,
1235        epoch: u32,
1236        corr: u64,
1237        code: impl Into<String>,
1238        message: impl Into<String>,
1239    ) -> Self {
1240        Self::RouteError {
1241            channel,
1242            epoch,
1243            corr,
1244            code: code.into(),
1245            message: message.into(),
1246        }
1247    }
1248
1249    /// Translate route failures that belong on the wire into an `ERROR` frame.
1250    pub fn to_error_frame(&self) -> Option<Frame> {
1251        match self {
1252            Self::UnknownChannel {
1253                channel,
1254                epoch,
1255                corr,
1256            } => error_frame(
1257                *channel,
1258                *epoch,
1259                *corr,
1260                error_codes::UNKNOWN_CHANNEL,
1261                format!("unknown channel {channel}"),
1262            ),
1263            Self::StaleRouteEpoch {
1264                channel,
1265                epoch,
1266                corr,
1267            } => error_frame(
1268                *channel,
1269                *epoch,
1270                *corr,
1271                error_codes::STALE_ROUTE_EPOCH,
1272                format!("stale route epoch for channel {channel}"),
1273            ),
1274            Self::Backend {
1275                channel,
1276                epoch,
1277                corr,
1278                message,
1279            } => error_frame(*channel, *epoch, *corr, "backend_error", message.clone()),
1280            Self::RouteError {
1281                channel,
1282                epoch,
1283                corr,
1284                code,
1285                message,
1286            } => error_frame(*channel, *epoch, *corr, code, message.clone()),
1287            Self::ReservedChannelZero
1288            | Self::DuplicateChannel { .. }
1289            | Self::FrameBuild(_)
1290            | Self::Forwarding(_) => None,
1291        }
1292    }
1293}
1294
1295fn error_frame(channel: u16, epoch: u32, corr: u64, code: &str, message: String) -> Option<Frame> {
1296    let body = serde_json::to_vec(&ErrorBody {
1297        code: code.to_string(),
1298        message,
1299        detail: None,
1300    })
1301    .ok()?;
1302
1303    Frame::build(
1304        FrameType::Error,
1305        Flags::new(false, Priority::Passive, false),
1306        channel,
1307        epoch,
1308        corr,
1309        body,
1310    )
1311    .ok()
1312}
1313
1314impl fmt::Display for RouterError {
1315    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1316        match self {
1317            Self::ReservedChannelZero => write!(f, "channel 0 is reserved for subc"),
1318            Self::DuplicateChannel { channel } => {
1319                write!(f, "backend already registered for channel {channel}")
1320            }
1321            Self::UnknownChannel { channel, corr, .. } => {
1322                write!(f, "unknown channel {channel} for corr {corr}")
1323            }
1324            Self::StaleRouteEpoch { channel, corr, .. } => {
1325                write!(f, "stale route epoch for channel {channel} corr {corr}")
1326            }
1327            Self::Backend {
1328                channel,
1329                corr,
1330                message,
1331                ..
1332            } => write!(
1333                f,
1334                "backend error on channel {channel} corr {corr}: {message}"
1335            ),
1336            Self::RouteError {
1337                channel,
1338                corr,
1339                code,
1340                message,
1341                ..
1342            } => write!(
1343                f,
1344                "route error {code} on channel {channel} corr {corr}: {message}"
1345            ),
1346            Self::FrameBuild(err) => write!(f, "failed to build routed frame: {err}"),
1347            Self::Forwarding(err) => write!(f, "forwarding error: {err}"),
1348        }
1349    }
1350}
1351
1352impl Error for RouterError {
1353    fn source(&self) -> Option<&(dyn Error + 'static)> {
1354        match self {
1355            Self::FrameBuild(err) => Some(err),
1356            Self::Forwarding(err) => Some(err),
1357            Self::ReservedChannelZero
1358            | Self::DuplicateChannel { .. }
1359            | Self::UnknownChannel { .. }
1360            | Self::StaleRouteEpoch { .. }
1361            | Self::Backend { .. }
1362            | Self::RouteError { .. } => None,
1363        }
1364    }
1365}
1366
1367#[cfg(test)]
1368mod tests {
1369    use super::*;
1370    use crate::{
1371        forwarding::RouteBindRelayOutcome,
1372        supervise::{ModuleSpec, RestartPolicy, Supervisor, SupervisorHandle},
1373        ControlHandler, Registry,
1374    };
1375    use std::{
1376        sync::{mpsc as std_mpsc, Arc},
1377        time::Duration,
1378    };
1379    use subc_control::ModuleProtocol;
1380    use subc_protocol::{manifest::Concurrency, ErrorBody, Flags, FrameType, Priority};
1381    use tokio::sync::mpsc;
1382
1383    pub(crate) use crate::router::test_log::{captured_logs, log_capture};
1384
1385    fn logged_millis(logs: &str, field: &str) -> u64 {
1386        logs.split_whitespace()
1387            .find_map(|part| part.strip_prefix(field))
1388            .and_then(|value| value.parse().ok())
1389            .unwrap_or_else(|| panic!("missing numeric {field} in logs: {logs}"))
1390    }
1391
1392    fn request(channel: u16, corr: u64, body: &[u8]) -> Frame {
1393        Frame::build(
1394            FrameType::Request,
1395            Flags::new(true, Priority::Interactive, false),
1396            channel,
1397            0,
1398            corr,
1399            body.to_vec(),
1400        )
1401        .unwrap()
1402    }
1403
1404    fn ping(corr: u64) -> Frame {
1405        Frame::build(
1406            FrameType::Ping,
1407            Flags::new(false, Priority::Passive, false),
1408            0,
1409            0,
1410            corr,
1411            Vec::new(),
1412        )
1413        .unwrap()
1414    }
1415
1416    fn route_ctx() -> (RouteCtx, mpsc::Receiver<crate::router::OutboundFrame>) {
1417        let (tx, rx) = mpsc::channel(8);
1418        (
1419            RouteCtx {
1420                connection_id: ConnectionId::LOCAL,
1421                egress: FrameSink::new(tx),
1422            },
1423            rx,
1424        )
1425    }
1426
1427    #[tokio::test]
1428    async fn echo_backend_returns_response_with_byte_identical_body() {
1429        let mut router = Router::with_default_self_handler();
1430        router.register_backend(7, EchoBackend).unwrap();
1431        let (ctx, mut rx) = route_ctx();
1432        let body = b"{not parsed}\0\xff";
1433
1434        router
1435            .route_for_connection(&ctx, request(7, 123, body))
1436            .await
1437            .unwrap();
1438        let response = rx.recv().await.unwrap();
1439
1440        assert_eq!(response.header.ty, FrameType::Response);
1441        assert_eq!(response.header.channel, 7);
1442        assert_eq!(response.header.corr, 123);
1443        assert_eq!(response.body, body);
1444        assert!(rx.try_recv().is_err());
1445    }
1446
1447    #[tokio::test]
1448    async fn unknown_channel_emits_canonical_error_frame() {
1449        let router = Router::with_default_self_handler();
1450        let (ctx, mut rx) = route_ctx();
1451
1452        router
1453            .route_for_connection(&ctx, request(99, 5, b"payload"))
1454            .await
1455            .unwrap();
1456        let error_frame = rx.recv().await.unwrap();
1457
1458        assert_eq!(error_frame.header.ty, FrameType::Error);
1459        assert_eq!(error_frame.header.channel, 99);
1460        assert_eq!(error_frame.header.corr, 5);
1461        let body: ErrorBody = serde_json::from_slice(&error_frame.body).unwrap();
1462        assert_eq!(body.code, "unknown_channel");
1463        assert_eq!(body.message, "unknown channel 99");
1464    }
1465
1466    #[tokio::test]
1467    async fn channel_zero_uses_control_handler_not_backend_registry() {
1468        let mut router = Router::with_default_self_handler();
1469        router.register_backend(1, EchoBackend).unwrap();
1470        let (ctx, mut rx) = route_ctx();
1471
1472        router.route_for_connection(&ctx, ping(77)).await.unwrap();
1473        let response = rx.recv().await.unwrap();
1474
1475        assert_eq!(response.header.ty, FrameType::Pong);
1476        assert_eq!(response.header.channel, 0);
1477        assert_eq!(response.header.corr, 77);
1478        assert!(response.body.is_empty());
1479    }
1480
1481    #[tokio::test]
1482    async fn slow_control_dispatch_logs_decoded_op_and_elapsed_time() {
1483        let control = Arc::new(
1484            ControlHandler::new(Arc::new(Registry::default()))
1485                .with_control_dispatch_delay(Duration::from_millis(1050)),
1486        );
1487        let router = Router::with_control_handler(control);
1488        let (ctx, mut rx) = route_ctx();
1489        let (output, guard) = log_capture(tracing::Level::WARN);
1490
1491        router
1492            .route_for_connection(&ctx, request(0, 41, br#"{"op":"server.describe"}"#))
1493            .await
1494            .expect("slow request routes");
1495        assert!(rx.recv().await.is_some(), "request receives a response");
1496        drop(guard);
1497
1498        let logs = captured_logs(&output);
1499        assert!(logs.contains("slow control dispatch"));
1500        assert!(logs.contains("op=server.describe"));
1501        assert!(logs.contains("connection_id=0"));
1502        assert!(logs.contains("corr=41"));
1503        assert!(
1504            logged_millis(&logs, "elapsed_ms=") >= 1050,
1505            "elapsed must include the injected handler delay: {logs}"
1506        );
1507    }
1508
1509    #[tokio::test]
1510    async fn fast_control_dispatch_emits_arrival_without_slow_warning() {
1511        let router = Router::with_default_self_handler();
1512        let (ctx, mut rx) = route_ctx();
1513        let (output, guard) = log_capture(tracing::Level::DEBUG);
1514
1515        router
1516            .route_for_connection(&ctx, request(0, 42, br#"{"op":"server.describe"}"#))
1517            .await
1518            .expect("fast request routes");
1519        assert!(rx.recv().await.is_some(), "request receives a response");
1520        drop(guard);
1521
1522        let logs = captured_logs(&output);
1523        assert!(logs.contains("control dispatch op=server.describe connection_id=0 corr=42"));
1524        assert!(!logs.contains("slow control dispatch"));
1525    }
1526
1527    #[tokio::test]
1528    async fn control_dispatch_arrival_is_hidden_at_info() {
1529        let router = Router::with_default_self_handler();
1530        let (ctx, mut rx) = route_ctx();
1531        let (output, guard) = log_capture(tracing::Level::INFO);
1532
1533        router
1534            .route_for_connection(&ctx, request(0, 43, br#"{"op":"server.describe"}"#))
1535            .await
1536            .expect("fast request routes");
1537        assert!(rx.recv().await.is_some(), "request receives a response");
1538        drop(guard);
1539
1540        assert!(
1541            !captured_logs(&output).contains("control dispatch"),
1542            "arrival logging must stay hidden at INFO"
1543        );
1544    }
1545
1546    #[tokio::test]
1547    async fn supervisor_list_logs_contended_snapshot_lock_only() {
1548        let registry = Arc::new(Registry::default());
1549        let handle = SupervisorHandle::new();
1550        let supervisor = Supervisor::new(Arc::clone(&registry), RestartPolicy::default())
1551            .with_handle(handle.clone());
1552        let module = supervisor
1553            .supervise_configured(
1554                ModuleSpec {
1555                    module_id: "held-module".to_string(),
1556                    program: "test-module".into(),
1557                    args: Vec::new(),
1558                    env: Vec::new(),
1559                    reserved: false,
1560                    reserved_prefixes: Vec::new(),
1561                    protocol: ModuleProtocol::Subc,
1562                    overlap: Default::default(),
1563                },
1564                false,
1565            )
1566            .expect("disabled test module is supervised");
1567        let router = Router::with_control_handler(Arc::new(
1568            ControlHandler::new(Arc::clone(&registry)).with_supervisor(handle),
1569        ));
1570        let (ctx, mut rx) = route_ctx();
1571        let (acquired, ready) = std_mpsc::channel();
1572        let holder = module.hold_snapshot_for_test(acquired, Duration::from_millis(400));
1573        ready.recv().expect("holder acquired snapshot lock");
1574        let (output, guard) = log_capture(tracing::Level::WARN);
1575
1576        router
1577            .route_for_connection(&ctx, request(0, 44, br#"{"op":"supervisor.list"}"#))
1578            .await
1579            .expect("list request routes after the lock releases");
1580        assert!(
1581            rx.recv().await.is_some(),
1582            "list request receives a response"
1583        );
1584        holder.join().expect("snapshot holder exits cleanly");
1585        drop(guard);
1586
1587        let logs = captured_logs(&output);
1588        assert!(logs.contains("slow snapshot lock"));
1589        assert!(logs.contains("module_id=held-module"));
1590        assert!(logs.contains("caller=list"));
1591        assert!(
1592            logged_millis(&logs, "waited_ms=") >= 250,
1593            "wait must exceed the slow-lock threshold: {logs}"
1594        );
1595
1596        let (output, guard) = log_capture(tracing::Level::WARN);
1597        router
1598            .route_for_connection(&ctx, request(0, 45, br#"{"op":"supervisor.list"}"#))
1599            .await
1600            .expect("uncontended list request routes");
1601        assert!(
1602            rx.recv().await.is_some(),
1603            "uncontended list receives a response"
1604        );
1605        drop(guard);
1606        assert!(
1607            !captured_logs(&output).contains("slow snapshot lock"),
1608            "uncontended list acquisition must not warn"
1609        );
1610    }
1611
1612    #[tokio::test]
1613    async fn full_module_to_client_sink_requests_client_close_without_erroring_module() {
1614        let forwarding = Arc::new(ForwardingTable::default());
1615        let control = Arc::new(ControlHandler::with_forwarding(
1616            Arc::new(crate::Registry::default()),
1617            Arc::clone(&forwarding),
1618        ));
1619        let router = Router::with_control_handler(control);
1620        let module_connection = ConnectionId::new(10);
1621        let client_connection = ConnectionId::new(20);
1622        let mut close_receiver = forwarding.register_connection_close(client_connection);
1623        let (module_tx, _module_rx) = mpsc::channel(1);
1624        forwarding
1625            .register_module_connection(
1626                module_connection,
1627                "full-sink-provider".to_string(),
1628                1,
1629                Concurrency::ModuleManaged,
1630                FrameSink::new(module_tx),
1631            )
1632            .unwrap();
1633        let (client_tx, mut client_rx) = mpsc::channel(1);
1634        let pending = forwarding
1635            .begin_route_bind_relay_for_test(
1636                client_connection,
1637                FrameSink::new(client_tx),
1638                700,
1639                "full-sink-provider",
1640            )
1641            .unwrap();
1642        forwarding
1643            .complete_pending_relay(
1644                module_connection,
1645                pending.corr,
1646                RouteBindRelayOutcome::Accepted,
1647            )
1648            .unwrap();
1649
1650        let (module_egress_tx, _module_egress_rx) = mpsc::channel(1);
1651        let module_ctx = RouteCtx {
1652            connection_id: module_connection,
1653            egress: FrameSink::new(module_egress_tx),
1654        };
1655        let terminal = Frame::build(
1656            FrameType::Response,
1657            Flags::new(false, Priority::Interactive, true),
1658            pending.module_channel,
1659            pending.module_epoch,
1660            701,
1661            b"terminal".to_vec(),
1662        )
1663        .unwrap();
1664
1665        router
1666            .route_for_connection(&module_ctx, terminal)
1667            .await
1668            .unwrap();
1669        let reason = tokio::time::timeout(Duration::from_secs(1), &mut close_receiver)
1670            .await
1671            .expect("close request should be sent for the full client sink")
1672            .expect("close sender should include a reason");
1673        assert!(
1674            reason
1675                .to_string()
1676                .contains("module_to_client_delivery_failed"),
1677            "unexpected close reason: {reason}"
1678        );
1679        assert_eq!(client_rx.try_recv().unwrap().header.corr, 700);
1680        assert!(client_rx.try_recv().is_err());
1681        assert_eq!(
1682            router.counters.snapshot()["client_egress_close_delivery_failed"],
1683            1
1684        );
1685    }
1686
1687    /// A terminal frame ends the request at the module even when the client
1688    /// cannot take it, so the drain must stop counting it at once rather than
1689    /// when the client connection's cleanup later removes the route.
1690    #[tokio::test]
1691    async fn terminal_frame_releases_its_credit_even_when_client_delivery_fails() {
1692        let forwarding = Arc::new(ForwardingTable::default());
1693        let control = Arc::new(ControlHandler::with_forwarding(
1694            Arc::new(crate::Registry::default()),
1695            Arc::clone(&forwarding),
1696        ));
1697        let router = Router::with_control_handler(control);
1698        let module_connection = ConnectionId::new(11);
1699        let client_connection = ConnectionId::new(21);
1700        let _close_receiver = forwarding.register_connection_close(client_connection);
1701        let (module_tx, _module_rx) = mpsc::channel(1);
1702        forwarding
1703            .register_module_connection(
1704                module_connection,
1705                "credit-provider".to_string(),
1706                1,
1707                Concurrency::ModuleManaged,
1708                FrameSink::new(module_tx),
1709            )
1710            .unwrap();
1711        // Capacity one, filled by the route.open response, so the terminal
1712        // frame below cannot be enqueued for the client.
1713        let (client_tx, _client_rx) = mpsc::channel(1);
1714        let pending = forwarding
1715            .begin_route_bind_relay_for_test(
1716                client_connection,
1717                FrameSink::new(client_tx),
1718                800,
1719                "credit-provider",
1720            )
1721            .unwrap();
1722        forwarding
1723            .complete_pending_relay(
1724                module_connection,
1725                pending.corr,
1726                RouteBindRelayOutcome::Accepted,
1727            )
1728            .unwrap();
1729        let DataRoute::Client(DataRouteState::Bound(route)) = forwarding
1730            .lookup_data_route(
1731                client_connection,
1732                pending.client_channel,
1733                pending.client_epoch,
1734            )
1735            .unwrap()
1736        else {
1737            panic!("expected a bound client route");
1738        };
1739        route.flow.acquire_tagged(801, false).await.unwrap();
1740        assert_eq!(route.flow.drain_in_flight(), 1);
1741
1742        let (module_egress_tx, _module_egress_rx) = mpsc::channel(1);
1743        let module_ctx = RouteCtx {
1744            connection_id: module_connection,
1745            egress: FrameSink::new(module_egress_tx),
1746        };
1747        let terminal = Frame::build(
1748            FrameType::Response,
1749            Flags::new(false, Priority::Interactive, true),
1750            pending.module_channel,
1751            pending.module_epoch,
1752            801,
1753            b"terminal".to_vec(),
1754        )
1755        .unwrap();
1756        router
1757            .route_for_connection(&module_ctx, terminal)
1758            .await
1759            .unwrap();
1760
1761        assert_eq!(
1762            router.counters.snapshot()["client_egress_close_delivery_failed"],
1763            1,
1764            "the client delivery must have failed for this test to mean anything"
1765        );
1766        assert_eq!(
1767            route.flow.drain_in_flight(),
1768            0,
1769            "the module's terminal frame must release its credit even though the client could not take it"
1770        );
1771    }
1772
1773    #[tokio::test]
1774    async fn full_route_goodbye_sink_requests_target_close_without_erroring_module() {
1775        let forwarding = Arc::new(ForwardingTable::default());
1776        let control = Arc::new(ControlHandler::with_forwarding(
1777            Arc::new(crate::Registry::default()),
1778            Arc::clone(&forwarding),
1779        ));
1780        let router = Router::with_control_handler(control);
1781        let module_connection = ConnectionId::new(30);
1782        let client_connection = ConnectionId::new(40);
1783        let mut close_receiver = forwarding.register_connection_close(client_connection);
1784        let (module_tx, _module_rx) = mpsc::channel(1);
1785        forwarding
1786            .register_module_connection(
1787                module_connection,
1788                "goodbye-full-provider".to_string(),
1789                1,
1790                Concurrency::ModuleManaged,
1791                FrameSink::new(module_tx),
1792            )
1793            .unwrap();
1794        let (client_tx, mut client_rx) = mpsc::channel(1);
1795        let pending = forwarding
1796            .begin_route_bind_relay_for_test(
1797                client_connection,
1798                FrameSink::new(client_tx),
1799                800,
1800                "goodbye-full-provider",
1801            )
1802            .unwrap();
1803        forwarding
1804            .complete_pending_relay(
1805                module_connection,
1806                pending.corr,
1807                RouteBindRelayOutcome::Accepted,
1808            )
1809            .unwrap();
1810
1811        let (module_egress_tx, _module_egress_rx) = mpsc::channel(1);
1812        let module_ctx = RouteCtx {
1813            connection_id: module_connection,
1814            egress: FrameSink::new(module_egress_tx),
1815        };
1816        let goodbye = Frame::build(
1817            FrameType::Goodbye,
1818            Flags::new(false, Priority::Passive, true),
1819            pending.module_channel,
1820            pending.module_epoch,
1821            801,
1822            Vec::new(),
1823        )
1824        .unwrap();
1825
1826        router
1827            .route_for_connection(&module_ctx, goodbye)
1828            .await
1829            .unwrap();
1830        let reason = tokio::time::timeout(Duration::from_secs(1), &mut close_receiver)
1831            .await
1832            .expect("close request should be sent for the full GOODBYE sink")
1833            .expect("close sender should include a reason");
1834        assert!(
1835            reason.to_string().contains("route_goodbye_delivery_failed"),
1836            "unexpected close reason: {reason}"
1837        );
1838        assert_eq!(client_rx.try_recv().unwrap().header.corr, 800);
1839        assert!(client_rx.try_recv().is_err());
1840        assert_eq!(router.counters.snapshot()["goodbye_relay_client_failed"], 1);
1841        assert_eq!(router.counters.snapshot()["route_released_epoch_fenced"], 1);
1842    }
1843
1844    /// One module and one client connection with `routes` routes bound between
1845    /// them. The client uses a real connection egress queue (the same byte
1846    /// budget and frame-count backstop as a live connection), and its route.open
1847    /// responses are drained so the queue starts empty.
1848    async fn multi_route_client(
1849        module_id: &str,
1850        module_connection: ConnectionId,
1851        client_connection: ConnectionId,
1852        routes: usize,
1853    ) -> (
1854        Router,
1855        FrameSink,
1856        mpsc::Receiver<OutboundFrame>,
1857        RouteCtx,
1858        Vec<crate::forwarding::PendingRouteBindRelay>,
1859        ConnectionCloseReceiver,
1860    ) {
1861        let forwarding = Arc::new(ForwardingTable::default());
1862        let control = Arc::new(ControlHandler::with_forwarding(
1863            Arc::new(crate::Registry::default()),
1864            Arc::clone(&forwarding),
1865        ));
1866        let router = Router::with_control_handler(control);
1867        let close_receiver = forwarding.register_connection_close(client_connection);
1868        let (module_tx, _module_rx) = mpsc::channel(8);
1869        forwarding
1870            .register_module_connection(
1871                module_connection,
1872                module_id.to_string(),
1873                1,
1874                Concurrency::ModuleManaged,
1875                FrameSink::new(module_tx),
1876            )
1877            .unwrap();
1878        let (client_sink, mut client_rx) = crate::server::connection_egress();
1879        let mut bound = Vec::with_capacity(routes);
1880        for index in 0..routes {
1881            let pending = forwarding
1882                .begin_route_bind_relay_for_test(
1883                    client_connection,
1884                    client_sink.clone(),
1885                    900 + index as u64,
1886                    module_id,
1887                )
1888                .unwrap();
1889            forwarding
1890                .complete_pending_relay(
1891                    module_connection,
1892                    pending.corr,
1893                    RouteBindRelayOutcome::Accepted,
1894                )
1895                .unwrap();
1896            assert_eq!(
1897                client_rx.recv().await.unwrap().header.corr,
1898                900 + index as u64
1899            );
1900            bound.push(pending);
1901        }
1902        let (module_egress_tx, _module_egress_rx) = mpsc::channel(8);
1903        let module_ctx = RouteCtx {
1904            connection_id: module_connection,
1905            egress: FrameSink::new(module_egress_tx),
1906        };
1907        (
1908            router,
1909            client_sink,
1910            client_rx,
1911            module_ctx,
1912            bound,
1913            close_receiver,
1914        )
1915    }
1916
1917    /// Per-frame cost of the egress sink's admission and release accounting:
1918    /// one million 200-byte frames enqueued with `try_send` and taken off the
1919    /// queue the way the connection writer does, in batches of 1,000 so the
1920    /// queue stays well inside its budget. Prints nanoseconds per frame; it is
1921    /// a measurement, not a gate. Run with
1922    /// `cargo test --release -p subc-daemon --lib egress_sink_per_frame_cost -- --ignored --nocapture`.
1923    #[test]
1924    #[ignore = "timing measurement, run on demand"]
1925    fn egress_sink_per_frame_cost() {
1926        const FRAMES: usize = 1_000_000;
1927        const BATCH: usize = 1_000;
1928        let (sink, mut rx) = crate::server::connection_egress();
1929        let template = stream_frame(9, 1, 0, vec![b't'; 200]);
1930        let started = Instant::now();
1931        for _ in 0..FRAMES / BATCH {
1932            for _ in 0..BATCH {
1933                sink.try_send(template.clone()).unwrap();
1934            }
1935            for _ in 0..BATCH {
1936                let outbound = rx.try_recv().unwrap();
1937                if let Some(charge) = &outbound.charge {
1938                    charge.taken_by_writer();
1939                }
1940                drop(std::hint::black_box(outbound));
1941            }
1942        }
1943        let elapsed = started.elapsed();
1944        assert_eq!(sink.backlog().queued_bytes, 0);
1945        println!(
1946            "egress sink: {FRAMES} frames in {elapsed:?}, {:.1} ns/frame",
1947            elapsed.as_nanos() as f64 / FRAMES as f64
1948        );
1949    }
1950
1951    fn stream_frame(channel: u16, epoch: u32, corr: u64, body: Vec<u8>) -> Frame {
1952        Frame::build(
1953            FrameType::StreamData,
1954            Flags::new(false, Priority::Interactive, false),
1955            channel,
1956            epoch,
1957            corr,
1958            body,
1959        )
1960        .unwrap()
1961    }
1962
1963    /// An awaited send (a control or error reply) is never refused by the byte
1964    /// budget: it parks until the writer frees bytes. If a release could miss a
1965    /// parked sender, that reply would hang for the connection's lifetime, so
1966    /// this pins the wakeup: the send stays parked while the queue is full and
1967    /// completes as soon as one frame leaves it.
1968    #[tokio::test]
1969    async fn awaited_send_parked_behind_a_full_byte_budget_wakes_when_bytes_free() {
1970        let (tx, mut rx) = mpsc::channel(1024);
1971        let sink = FrameSink::with_byte_budget(tx, 2_000);
1972        let mut queued = 0u64;
1973        while sink
1974            .try_send(stream_frame(7, 1, queued, vec![b'x'; 200]))
1975            .is_ok()
1976        {
1977            queued += 1;
1978        }
1979        assert!(queued > 0, "the budget admitted nothing");
1980
1981        let parked = tokio::spawn({
1982            let sink = sink.clone();
1983            async move { sink.send(stream_frame(0, 0, 999, vec![b'r'; 200])).await }
1984        });
1985        tokio::time::sleep(Duration::from_millis(100)).await;
1986        assert!(
1987            !parked.is_finished(),
1988            "the awaited send must wait while the byte budget is full"
1989        );
1990
1991        // The writer takes one frame and drops it, releasing its bytes.
1992        drop(rx.recv().await.expect("a queued frame"));
1993        tokio::time::timeout(Duration::from_secs(2), parked)
1994            .await
1995            .expect("the parked send was never woken after bytes were freed")
1996            .unwrap()
1997            .unwrap();
1998    }
1999
2000    /// A client multiplexing several token streams pauses its reader while the
2001    /// module keeps producing small frames. Far more frames than the old
2002    /// 64-frame queue allowed, but far fewer bytes than the budget, must all be
2003    /// held without closing the connection and delivered in order afterwards.
2004    #[tokio::test]
2005    async fn paused_client_reader_keeps_connection_through_small_frame_burst() {
2006        const ROUTES: usize = 4;
2007        const FRAMES_PER_ROUTE: usize = 250;
2008        let (router, client_sink, mut client_rx, module_ctx, routes, mut close_receiver) =
2009            multi_route_client(
2010                "burst-provider",
2011                ConnectionId::new(60),
2012                ConnectionId::new(61),
2013                ROUTES,
2014            )
2015            .await;
2016
2017        // The reader is paused: nothing is received until every frame is sent.
2018        for seq in 0..FRAMES_PER_ROUTE as u64 {
2019            for (index, route) in routes.iter().enumerate() {
2020                let body = format!("route-{index}-token-{seq:05}-{}", "t".repeat(170));
2021                router
2022                    .route_for_connection(
2023                        &module_ctx,
2024                        stream_frame(
2025                            route.module_channel,
2026                            route.module_epoch,
2027                            seq,
2028                            body.into_bytes(),
2029                        ),
2030                    )
2031                    .await
2032                    .unwrap();
2033            }
2034        }
2035
2036        let backlog = client_sink.backlog();
2037        assert_eq!(backlog.queued_frames, ROUTES * FRAMES_PER_ROUTE);
2038        assert!(backlog.queued_bytes < crate::server::CONNECTION_EGRESS_BYTE_BUDGET);
2039        assert!(
2040            close_receiver.try_recv().is_err(),
2041            "a paused reader under the byte budget must not be closed"
2042        );
2043        assert_eq!(
2044            router.counters.snapshot()["client_egress_close_delivery_failed"],
2045            0
2046        );
2047
2048        // The reader resumes: every frame arrives, in order within each route.
2049        let mut next_seq = vec![0u64; ROUTES];
2050        for _ in 0..ROUTES * FRAMES_PER_ROUTE {
2051            let frame = client_rx.try_recv().expect("every queued frame arrives");
2052            let index = routes
2053                .iter()
2054                .position(|route| route.client_channel == frame.header.channel)
2055                .expect("frame arrives on one of the bound client channels");
2056            assert_eq!(frame.header.corr, next_seq[index], "route {index} order");
2057            let expected_prefix = format!("route-{index}-token-{:05}-", next_seq[index]);
2058            assert!(frame.body.starts_with(expected_prefix.as_bytes()));
2059            next_seq[index] += 1;
2060        }
2061        assert!(client_rx.try_recv().is_err());
2062        assert_eq!(next_seq, vec![FRAMES_PER_ROUTE as u64; ROUTES]);
2063        assert_eq!(client_sink.backlog().queued_bytes, 0);
2064    }
2065
2066    /// A client that never reads is closed once the module's frames exceed
2067    /// the byte budget, and that close is reported once at WARN with what an
2068    /// operator needs to find the stuck reader.
2069    #[tokio::test]
2070    async fn never_reading_client_is_closed_at_byte_budget_with_warn_diagnosis() {
2071        let (logs, _guard) = test_log::log_capture(tracing::Level::WARN);
2072        const BODY: usize = 16 * 1024;
2073        let (router, client_sink, _client_rx, module_ctx, routes, mut close_receiver) =
2074            multi_route_client(
2075                "stuck-reader-provider",
2076                ConnectionId::new(70),
2077                ConnectionId::new(71),
2078                2,
2079            )
2080            .await;
2081
2082        let mut admitted = 0usize;
2083        let mut sent = 0u64;
2084        while router.counters.snapshot()["client_egress_close_delivery_failed"] == 0 {
2085            assert!(sent < 1_000, "the byte budget never refused a frame");
2086            // Other tests running in parallel hit the same WARN call site with
2087            // no subscriber installed; if one of them registers that call site
2088            // while this test's capture subscriber is being installed, tracing
2089            // can cache the call site as disabled. Recomputing the cache just
2090            // before each frame that may trigger the WARN keeps the capture
2091            // from silently missing it.
2092            tracing::callsite::rebuild_interest_cache();
2093            let route = &routes[(sent % 2) as usize];
2094            router
2095                .route_for_connection(
2096                    &module_ctx,
2097                    stream_frame(
2098                        route.module_channel,
2099                        route.module_epoch,
2100                        sent,
2101                        vec![b'z'; BODY],
2102                    ),
2103                )
2104                .await
2105                .unwrap();
2106            sent += 1;
2107            admitted = client_sink.backlog().queued_frames;
2108        }
2109        // The budget, not the frame-count backstop, did the refusing.
2110        let frame_bytes = subc_protocol::HEADER_LEN + BODY;
2111        assert_eq!(
2112            admitted,
2113            crate::server::CONNECTION_EGRESS_BYTE_BUDGET / frame_bytes
2114        );
2115        let reason = close_receiver
2116            .try_recv()
2117            .expect("the client connection must be asked to close");
2118        assert!(reason
2119            .to_string()
2120            .contains("module_to_client_delivery_failed"));
2121
2122        // A second refused frame for the same connection adds no second WARN.
2123        router
2124            .route_for_connection(
2125                &module_ctx,
2126                stream_frame(
2127                    routes[0].module_channel,
2128                    routes[0].module_epoch,
2129                    sent,
2130                    vec![b'z'; BODY],
2131                ),
2132            )
2133            .await
2134            .unwrap();
2135
2136        let captured = test_log::captured_logs(&logs);
2137        let warn_lines = captured
2138            .lines()
2139            .filter(|line| {
2140                line.contains("closing client connection: its egress queue could not take a frame")
2141            })
2142            .collect::<Vec<_>>();
2143        assert_eq!(warn_lines.len(), 1, "exactly one WARN, got: {captured}");
2144        let line = warn_lines[0];
2145        assert!(line.contains("WARN"), "{line}");
2146        assert!(line.contains("connection_id=71"), "{line}");
2147        assert!(
2148            line.contains("module_id=\"stuck-reader-provider\""),
2149            "{line}"
2150        );
2151        assert!(line.contains("client_channel="), "{line}");
2152        assert!(line.contains("principals=direct"), "{line}");
2153        let queued_bytes: usize = line
2154            .split("queued_bytes=")
2155            .nth(1)
2156            .and_then(|rest| rest.split_whitespace().next())
2157            .and_then(|value| value.parse().ok())
2158            .expect("queued_bytes is logged");
2159        assert_eq!(queued_bytes, admitted * frame_bytes);
2160        assert!(
2161            line.contains(&format!("queued_frames={admitted}")),
2162            "{line}"
2163        );
2164        assert!(line.contains("oldest_queued_ms="), "{line}");
2165    }
2166
2167    fn route_frame(ty: FrameType, channel: u16, epoch: u32, corr: u64) -> Frame {
2168        Frame::build(
2169            ty,
2170            Flags::new(false, Priority::Interactive, false),
2171            channel,
2172            epoch,
2173            corr,
2174            if ty == FrameType::Request || ty == FrameType::Response {
2175                b"route-body".to_vec()
2176            } else {
2177                Vec::new()
2178            },
2179        )
2180        .unwrap()
2181    }
2182
2183    type DynamicRouteFixture = (
2184        Router,
2185        Arc<ForwardingTable>,
2186        RouteCtx,
2187        mpsc::Receiver<crate::router::OutboundFrame>,
2188        RouteCtx,
2189        mpsc::Receiver<crate::router::OutboundFrame>,
2190        mpsc::Receiver<crate::router::OutboundFrame>,
2191        crate::forwarding::PendingRouteBindRelay,
2192    );
2193
2194    fn dynamic_route_fixture(commit: bool) -> DynamicRouteFixture {
2195        let forwarding = Arc::new(ForwardingTable::default());
2196        let control = Arc::new(crate::ControlHandler::with_forwarding(
2197            Arc::new(crate::Registry::default()),
2198            Arc::clone(&forwarding),
2199        ));
2200        let router = Router::with_control_handler(control);
2201        let module_connection = ConnectionId::new(500);
2202        let client_connection = ConnectionId::new(501);
2203        let (module_tx, module_rx) = mpsc::channel(8);
2204        forwarding
2205            .register_module_connection(
2206                module_connection,
2207                "epoch-router".into(),
2208                2,
2209                Concurrency::ModuleManaged,
2210                FrameSink::new(module_tx),
2211            )
2212            .unwrap();
2213        let (client_tx, client_rx) = mpsc::channel(8);
2214        let client_sink = FrameSink::new(client_tx);
2215        let pending = forwarding
2216            .begin_route_bind_relay_for_test(
2217                client_connection,
2218                client_sink.clone(),
2219                700,
2220                "epoch-router",
2221            )
2222            .unwrap();
2223        if commit {
2224            forwarding
2225                .complete_pending_relay(
2226                    module_connection,
2227                    pending.corr,
2228                    RouteBindRelayOutcome::Accepted,
2229                )
2230                .unwrap();
2231        }
2232        let (module_egress_tx, module_egress_rx) = mpsc::channel(8);
2233        (
2234            router,
2235            forwarding,
2236            RouteCtx {
2237                connection_id: client_connection,
2238                egress: client_sink,
2239            },
2240            client_rx,
2241            RouteCtx {
2242                connection_id: module_connection,
2243                egress: FrameSink::new(module_egress_tx),
2244            },
2245            module_egress_rx,
2246            module_rx,
2247            pending,
2248        )
2249    }
2250
2251    #[tokio::test]
2252    async fn route_epochs_validate_both_directions_and_rewrite_to_peer_handle() {
2253        let (
2254            router,
2255            _forwarding,
2256            client_ctx,
2257            mut client_rx,
2258            module_ctx,
2259            _module_egress_rx,
2260            mut module_rx,
2261            pending,
2262        ) = dynamic_route_fixture(true);
2263        let route_open = client_rx.recv().await.unwrap();
2264        assert_eq!(route_open.header.corr, 700);
2265
2266        router
2267            .route_for_connection(
2268                &client_ctx,
2269                route_frame(
2270                    FrameType::Request,
2271                    pending.client_channel,
2272                    pending.client_epoch,
2273                    701,
2274                ),
2275            )
2276            .await
2277            .unwrap();
2278        let forwarded = module_rx.recv().await.unwrap();
2279        assert_eq!(forwarded.header.channel, pending.module_channel);
2280        assert_eq!(forwarded.header.epoch, pending.module_epoch);
2281
2282        router
2283            .route_for_connection(
2284                &module_ctx,
2285                route_frame(
2286                    FrameType::Response,
2287                    pending.module_channel,
2288                    pending.module_epoch,
2289                    701,
2290                ),
2291            )
2292            .await
2293            .unwrap();
2294        let delivered = client_rx.recv().await.unwrap();
2295        assert_eq!(delivered.header.channel, pending.client_channel);
2296        assert_eq!(delivered.header.epoch, pending.client_epoch);
2297
2298        router
2299            .route_for_connection(
2300                &client_ctx,
2301                route_frame(
2302                    FrameType::Request,
2303                    pending.client_channel,
2304                    pending.client_epoch + 1,
2305                    702,
2306                ),
2307            )
2308            .await
2309            .unwrap();
2310        router
2311            .route_for_connection(
2312                &module_ctx,
2313                route_frame(
2314                    FrameType::Response,
2315                    pending.module_channel,
2316                    pending.module_epoch + 1,
2317                    703,
2318                ),
2319            )
2320            .await
2321            .unwrap();
2322        let stale_error = client_rx.recv().await.unwrap();
2323        assert_eq!(stale_error.header.ty, FrameType::Error);
2324        assert_eq!(stale_error.header.channel, pending.client_channel);
2325        assert_eq!(stale_error.header.epoch, pending.client_epoch + 1);
2326        assert_eq!(stale_error.header.corr, 702);
2327        let body: ErrorBody = serde_json::from_slice(&stale_error.body).unwrap();
2328        assert_eq!(body.code, "stale_route_epoch");
2329        assert!(module_rx.try_recv().is_err());
2330        assert!(client_rx.try_recv().is_err());
2331        let counters = router.counters.snapshot();
2332        assert_eq!(counters["client_frames_dropped_stale_route"], 1);
2333        assert_eq!(counters["module_frames_dropped_no_route"], 1);
2334    }
2335
2336    #[tokio::test]
2337    async fn accepted_route_publishes_route_open_before_immediate_reverse_request() {
2338        let (
2339            router,
2340            _,
2341            _client_ctx,
2342            mut client_rx,
2343            module_ctx,
2344            _module_egress_rx,
2345            _module_rx,
2346            pending,
2347        ) = dynamic_route_fixture(true);
2348        router
2349            .route_for_connection(
2350                &module_ctx,
2351                route_frame(
2352                    FrameType::Request,
2353                    pending.module_channel,
2354                    pending.module_epoch,
2355                    800,
2356                ),
2357            )
2358            .await
2359            .unwrap();
2360
2361        let first = client_rx.recv().await.unwrap();
2362        let second = client_rx.recv().await.unwrap();
2363        assert_eq!(first.header.channel, 0);
2364        assert_eq!(first.header.corr, 700);
2365        assert_eq!(second.header.channel, pending.client_channel);
2366        assert_eq!(second.header.epoch, pending.client_epoch);
2367        assert_eq!(second.header.corr, 800);
2368    }
2369
2370    #[tokio::test]
2371    async fn reserved_slot_ingress_errors_only_matching_client_requests() {
2372        let (
2373            router,
2374            _forwarding,
2375            client_ctx,
2376            mut client_rx,
2377            _module_ctx,
2378            _module_egress_rx,
2379            mut module_rx,
2380            pending,
2381        ) = dynamic_route_fixture(false);
2382        router
2383            .route_for_connection(
2384                &client_ctx,
2385                route_frame(
2386                    FrameType::Request,
2387                    pending.client_channel,
2388                    pending.client_epoch,
2389                    900,
2390                ),
2391            )
2392            .await
2393            .unwrap();
2394        let error = client_rx.recv().await.unwrap();
2395        assert_eq!(error.header.ty, FrameType::Error);
2396        assert_eq!(error.header.channel, pending.client_channel);
2397        assert_eq!(error.header.epoch, pending.client_epoch);
2398        assert_eq!(error.header.corr, 900);
2399
2400        router
2401            .route_for_connection(
2402                &client_ctx,
2403                route_frame(
2404                    FrameType::Response,
2405                    pending.client_channel,
2406                    pending.client_epoch,
2407                    901,
2408                ),
2409            )
2410            .await
2411            .unwrap();
2412        router
2413            .route_for_connection(
2414                &client_ctx,
2415                route_frame(
2416                    FrameType::Request,
2417                    pending.client_channel,
2418                    pending.client_epoch + 1,
2419                    902,
2420                ),
2421            )
2422            .await
2423            .unwrap();
2424        let stale_error = client_rx.recv().await.unwrap();
2425        assert_eq!(stale_error.header.ty, FrameType::Error);
2426        assert_eq!(stale_error.header.channel, pending.client_channel);
2427        assert_eq!(stale_error.header.epoch, pending.client_epoch + 1);
2428        assert_eq!(stale_error.header.corr, 902);
2429        let body: ErrorBody = serde_json::from_slice(&stale_error.body).unwrap();
2430        assert_eq!(body.code, "stale_route_epoch");
2431        assert!(module_rx.try_recv().is_err());
2432        let counters = router.counters.snapshot();
2433        assert_eq!(counters["client_frames_dropped_stale_route"], 1);
2434        assert_eq!(counters["module_frames_dropped_no_route"], 0);
2435    }
2436
2437    #[tokio::test]
2438    async fn dropped_module_route_goodbye_increments_counter() {
2439        let (
2440            router,
2441            _forwarding,
2442            client_ctx,
2443            mut client_rx,
2444            _module_ctx,
2445            _module_egress_rx,
2446            mut module_rx,
2447            pending,
2448        ) = dynamic_route_fixture(true);
2449        let _ = client_rx.recv().await;
2450        module_rx.close();
2451
2452        router
2453            .route_for_connection(
2454                &client_ctx,
2455                route_frame(
2456                    FrameType::Goodbye,
2457                    pending.client_channel,
2458                    pending.client_epoch,
2459                    999,
2460                ),
2461            )
2462            .await
2463            .unwrap();
2464
2465        let counters = router.counters.snapshot();
2466        assert_eq!(counters["goodbye_relay_module_dropped"], 1);
2467        assert_eq!(
2468            counters["goodbye_relay_module_dropped_by_module"],
2469            serde_json::json!({ "epoch-router": 1 })
2470        );
2471        assert_eq!(counters["route_released_epoch_fenced"], 1);
2472    }
2473
2474    #[tokio::test]
2475    async fn module_request_on_stale_epoch_receives_stale_route_epoch() {
2476        let (
2477            router,
2478            _forwarding,
2479            _client_ctx,
2480            _client_rx,
2481            module_ctx,
2482            mut module_egress_rx,
2483            mut module_rx,
2484            pending,
2485        ) = dynamic_route_fixture(true);
2486
2487        router
2488            .route_for_connection(
2489                &module_ctx,
2490                route_frame(
2491                    FrameType::Request,
2492                    pending.module_channel,
2493                    pending.module_epoch + 1,
2494                    1_000,
2495                ),
2496            )
2497            .await
2498            .unwrap();
2499
2500        let error = module_egress_rx.try_recv().unwrap();
2501        assert_eq!(error.header.ty, FrameType::Error);
2502        assert_eq!(error.header.channel, pending.module_channel);
2503        assert_eq!(error.header.epoch, pending.module_epoch + 1);
2504        assert_eq!(error.header.corr, 1_000);
2505        let body: ErrorBody = serde_json::from_slice(&error.body).unwrap();
2506        assert_eq!(body.code, "stale_route_epoch");
2507        assert!(module_rx.try_recv().is_err());
2508        let counters = router.counters.snapshot();
2509        assert_eq!(counters["module_requests_dropped_stale_route"], 1);
2510        assert_eq!(counters["module_frames_dropped_no_route"], 0);
2511    }
2512
2513    #[tokio::test]
2514    async fn module_request_on_reserved_or_absent_route_receives_unknown_channel() {
2515        let (
2516            reserved_router,
2517            _forwarding,
2518            _client_ctx,
2519            _client_rx,
2520            reserved_module_ctx,
2521            mut reserved_module_egress_rx,
2522            _module_rx,
2523            reserved,
2524        ) = dynamic_route_fixture(false);
2525        reserved_router
2526            .route_for_connection(
2527                &reserved_module_ctx,
2528                route_frame(
2529                    FrameType::Request,
2530                    reserved.module_channel,
2531                    reserved.module_epoch,
2532                    1_001,
2533                ),
2534            )
2535            .await
2536            .unwrap();
2537        let reserved_error = reserved_module_egress_rx.try_recv().unwrap();
2538        let reserved_body: ErrorBody = serde_json::from_slice(&reserved_error.body).unwrap();
2539        assert_eq!(reserved_error.header.ty, FrameType::Error);
2540        assert_eq!(reserved_error.header.channel, reserved.module_channel);
2541        assert_eq!(reserved_error.header.epoch, reserved.module_epoch);
2542        assert_eq!(reserved_error.header.corr, 1_001);
2543        assert_eq!(reserved_body.code, "unknown_channel");
2544        assert_eq!(
2545            reserved_router.counters.snapshot()["module_requests_dropped_stale_route"],
2546            1
2547        );
2548
2549        let (
2550            absent_router,
2551            _forwarding,
2552            _client_ctx,
2553            _client_rx,
2554            absent_module_ctx,
2555            mut absent_module_egress_rx,
2556            _module_rx,
2557            absent,
2558        ) = dynamic_route_fixture(false);
2559        absent_router
2560            .route_for_connection(
2561                &absent_module_ctx,
2562                route_frame(
2563                    FrameType::Request,
2564                    absent.module_channel + 1,
2565                    absent.module_epoch,
2566                    1_002,
2567                ),
2568            )
2569            .await
2570            .unwrap();
2571        let absent_error = absent_module_egress_rx.try_recv().unwrap();
2572        let absent_body: ErrorBody = serde_json::from_slice(&absent_error.body).unwrap();
2573        assert_eq!(absent_error.header.ty, FrameType::Error);
2574        assert_eq!(absent_error.header.channel, absent.module_channel + 1);
2575        assert_eq!(absent_error.header.epoch, absent.module_epoch);
2576        assert_eq!(absent_error.header.corr, 1_002);
2577        assert_eq!(absent_body.code, "unknown_channel");
2578        assert_eq!(
2579            absent_router.counters.snapshot()["module_requests_dropped_stale_route"],
2580            1
2581        );
2582    }
2583
2584    #[tokio::test]
2585    async fn non_request_module_frame_on_dead_route_is_counted_without_error() {
2586        let (
2587            router,
2588            forwarding,
2589            client_ctx,
2590            mut client_rx,
2591            module_ctx,
2592            mut module_egress_rx,
2593            mut module_rx,
2594            pending,
2595        ) = dynamic_route_fixture(true);
2596        let (other_module_tx, _other_module_rx) = mpsc::channel(8);
2597        forwarding
2598            .register_module_connection(
2599                ConnectionId::new(502),
2600                "other-module".into(),
2601                2,
2602                Concurrency::ModuleManaged,
2603                FrameSink::new(other_module_tx),
2604            )
2605            .unwrap();
2606        let _ = client_rx.recv().await.unwrap();
2607
2608        router
2609            .route_for_connection(
2610                &client_ctx,
2611                route_frame(
2612                    FrameType::Goodbye,
2613                    pending.client_channel,
2614                    pending.client_epoch,
2615                    1_003,
2616                ),
2617            )
2618            .await
2619            .unwrap();
2620        let _ = module_rx.recv().await.unwrap();
2621
2622        router
2623            .route_for_connection(
2624                &module_ctx,
2625                route_frame(
2626                    FrameType::StreamData,
2627                    pending.module_channel,
2628                    pending.module_epoch,
2629                    1_004,
2630                ),
2631            )
2632            .await
2633            .unwrap();
2634
2635        // No ERROR goes back for a non-request frame. The one reply is the
2636        // route GOODBYE telling the module to let go of the released route.
2637        let reply = module_egress_rx.try_recv().unwrap();
2638        assert_eq!(reply.header.ty, FrameType::Goodbye);
2639        assert!(module_egress_rx.try_recv().is_err());
2640        let counters = router.counters.snapshot();
2641        assert_eq!(counters["module_frames_dropped_no_route"], 1);
2642        assert_eq!(
2643            counters["module_frames_dropped_no_route_by_module"],
2644            serde_json::json!({ "epoch-router": 1 })
2645        );
2646        assert_eq!(counters["module_requests_dropped_stale_route"], 0);
2647    }
2648
2649    /// Drain whatever the module connection's queue holds right now, the way a
2650    /// module's reader would before it stalls.
2651    fn drain_now(rx: &mut mpsc::Receiver<OutboundFrame>) {
2652        while rx.try_recv().is_ok() {}
2653    }
2654
2655    /// A module that stops reading for a moment when a client closes one of
2656    /// its routes still learns the route is gone: the GOODBYE its full egress
2657    /// queue refused is delivered as soon as it reads again, rather than
2658    /// dropped, which would leave the module holding the route for the rest of
2659    /// its connection.
2660    #[tokio::test]
2661    async fn route_goodbye_refused_by_stalled_module_is_delivered_when_it_resumes_reading() {
2662        const BUDGET: usize = 4_096;
2663        let forwarding = Arc::new(ForwardingTable::default());
2664        let control = Arc::new(ControlHandler::with_forwarding(
2665            Arc::new(Registry::default()),
2666            Arc::clone(&forwarding),
2667        ));
2668        let router = Router::with_control_handler(control);
2669        let module_connection = ConnectionId::new(80);
2670        let client_connection = ConnectionId::new(81);
2671        let (module_tx, mut module_rx) = mpsc::channel(64);
2672        let module_sink = FrameSink::with_byte_budget(module_tx, BUDGET);
2673        forwarding
2674            .register_module_connection(
2675                module_connection,
2676                "stalled-provider".into(),
2677                2,
2678                Concurrency::ModuleManaged,
2679                module_sink.clone(),
2680            )
2681            .unwrap();
2682        let (client_tx, mut client_rx) = mpsc::channel(8);
2683        let client_sink = FrameSink::new(client_tx);
2684        let pending = forwarding
2685            .begin_route_bind_relay_for_test(
2686                client_connection,
2687                client_sink.clone(),
2688                1_100,
2689                "stalled-provider",
2690            )
2691            .unwrap();
2692        forwarding
2693            .complete_pending_relay(
2694                module_connection,
2695                pending.corr,
2696                RouteBindRelayOutcome::Accepted,
2697            )
2698            .unwrap();
2699        let _ = client_rx.recv().await.unwrap();
2700        drain_now(&mut module_rx);
2701
2702        // The module stalls: its queue holds more than the byte budget, so the
2703        // queue refuses anything further.
2704        module_sink
2705            .try_send(stream_frame(9, 1, 0, vec![b'f'; BUDGET]))
2706            .unwrap();
2707        assert!(module_sink
2708            .try_send(stream_frame(9, 1, 1, Vec::new()))
2709            .is_err());
2710
2711        let client_ctx = RouteCtx {
2712            connection_id: client_connection,
2713            egress: client_sink,
2714        };
2715        router
2716            .route_for_connection(
2717                &client_ctx,
2718                route_frame(
2719                    FrameType::Goodbye,
2720                    pending.client_channel,
2721                    pending.client_epoch,
2722                    1_101,
2723                ),
2724            )
2725            .await
2726            .unwrap();
2727        tokio::task::yield_now().await;
2728        assert_eq!(
2729            router.counters.snapshot()["goodbye_relay_module_dropped"],
2730            0,
2731            "a GOODBYE refused by a momentarily full module queue must not be dropped"
2732        );
2733
2734        // The module reads again: the filler comes off, then the GOODBYE.
2735        let filler = module_rx.recv().await.unwrap();
2736        assert_eq!(filler.header.ty, FrameType::StreamData);
2737        drop(filler);
2738        let goodbye = tokio::time::timeout(Duration::from_secs(2), module_rx.recv())
2739            .await
2740            .expect("the refused GOODBYE must be delivered once the module frees room")
2741            .unwrap();
2742        assert_eq!(goodbye.header.ty, FrameType::Goodbye);
2743        assert_eq!(goodbye.header.channel, pending.module_channel);
2744        assert_eq!(goodbye.header.epoch, pending.module_epoch);
2745        assert_eq!(
2746            router.counters.snapshot()["goodbye_relay_module_dropped"],
2747            0
2748        );
2749    }
2750
2751    /// A module still sending on a route the daemon released is told, with a
2752    /// route GOODBYE for exactly the (channel, epoch) it sent on. The same
2753    /// happens for a stale epoch on a channel whose route moved on and for a
2754    /// channel that never had a route; only the first is counted as traffic on
2755    /// a released route.
2756    #[tokio::test]
2757    async fn module_frame_on_route_the_daemon_does_not_hold_is_answered_with_goodbye() {
2758        let (
2759            router,
2760            _forwarding,
2761            client_ctx,
2762            mut client_rx,
2763            module_ctx,
2764            mut module_egress_rx,
2765            mut module_rx,
2766            pending,
2767        ) = dynamic_route_fixture(true);
2768        let _ = client_rx.recv().await.unwrap();
2769        router
2770            .route_for_connection(
2771                &client_ctx,
2772                route_frame(
2773                    FrameType::Goodbye,
2774                    pending.client_channel,
2775                    pending.client_epoch,
2776                    1_200,
2777                ),
2778            )
2779            .await
2780            .unwrap();
2781        drain_now(&mut module_rx);
2782
2783        let cases = [
2784            // Released route: the daemon allocated this (channel, epoch).
2785            (pending.module_channel, pending.module_epoch),
2786            // A channel the daemon never allocated on this connection.
2787            (pending.module_channel + 1, 1),
2788        ];
2789        for (channel, epoch) in cases {
2790            router
2791                .route_for_connection(
2792                    &module_ctx,
2793                    route_frame(FrameType::StreamData, channel, epoch, 1_201),
2794                )
2795                .await
2796                .unwrap();
2797            let reply = module_egress_rx
2798                .try_recv()
2799                .expect("a frame on a route the daemon does not hold is answered");
2800            assert_eq!(reply.header.ty, FrameType::Goodbye);
2801            assert_eq!(reply.header.channel, channel);
2802            assert_eq!(reply.header.epoch, epoch);
2803            assert_eq!(reply.header.corr, 0);
2804            assert!(module_egress_rx.try_recv().is_err());
2805        }
2806
2807        // A GOODBYE from the module for a route the daemon already released
2808        // is not answered: the module is letting go already.
2809        router
2810            .route_for_connection(
2811                &module_ctx,
2812                route_frame(FrameType::Goodbye, pending.module_channel + 2, 1, 0),
2813            )
2814            .await
2815            .unwrap();
2816        assert!(module_egress_rx.try_recv().is_err());
2817
2818        let counters = router.counters.snapshot();
2819        assert_eq!(counters["module_frames_dropped_no_route"], 3);
2820        assert_eq!(counters["module_frames_dropped_released_route"], 1);
2821        assert_eq!(
2822            counters["module_frames_dropped_released_route_by_module"],
2823            serde_json::json!({ "epoch-router": 1 })
2824        );
2825        assert_eq!(counters["module_orphan_route_goodbyes_sent"], 2);
2826    }
2827
2828    /// A chatty orphan (a streaming module still producing on a route it
2829    /// missed the GOODBYE for) is answered once per interval, not once per
2830    /// frame, and answered again once the interval has passed.
2831    #[tokio::test(start_paused = true)]
2832    async fn burst_of_orphan_module_frames_is_answered_once_per_interval() {
2833        let (
2834            router,
2835            _forwarding,
2836            client_ctx,
2837            mut client_rx,
2838            module_ctx,
2839            mut module_egress_rx,
2840            mut module_rx,
2841            pending,
2842        ) = dynamic_route_fixture(true);
2843        let _ = client_rx.recv().await.unwrap();
2844        router
2845            .route_for_connection(
2846                &client_ctx,
2847                route_frame(
2848                    FrameType::Goodbye,
2849                    pending.client_channel,
2850                    pending.client_epoch,
2851                    1_300,
2852                ),
2853            )
2854            .await
2855            .unwrap();
2856        drain_now(&mut module_rx);
2857
2858        let send_burst = |corr: u64| {
2859            route_frame(
2860                FrameType::StreamData,
2861                pending.module_channel,
2862                pending.module_epoch,
2863                corr,
2864            )
2865        };
2866        for corr in 0..20 {
2867            router
2868                .route_for_connection(&module_ctx, send_burst(corr))
2869                .await
2870                .unwrap();
2871        }
2872        let mut replies = 0;
2873        while let Ok(reply) = module_egress_rx.try_recv() {
2874            assert_eq!(reply.header.ty, FrameType::Goodbye);
2875            replies += 1;
2876        }
2877        assert_eq!(replies, 1, "a burst within the interval gets one GOODBYE");
2878
2879        tokio::time::advance(ORPHAN_ROUTE_GOODBYE_INTERVAL).await;
2880        router
2881            .route_for_connection(&module_ctx, send_burst(20))
2882            .await
2883            .unwrap();
2884        let retry = module_egress_rx
2885            .try_recv()
2886            .expect("the first orphan frame after the interval is answered again");
2887        assert_eq!(retry.header.channel, pending.module_channel);
2888        assert_eq!(retry.header.epoch, pending.module_epoch);
2889
2890        let counters = router.counters.snapshot();
2891        assert_eq!(counters["module_frames_dropped_no_route"], 21);
2892        assert_eq!(counters["module_orphan_route_goodbyes_sent"], 2);
2893    }
2894
2895    /// The rate-limit memory is per connection and goes away with it.
2896    #[test]
2897    fn orphan_goodbye_rate_limit_state_is_released_with_the_connection() {
2898        let router = Router::with_default_self_handler();
2899        let connection = router.begin_connection();
2900        let id = connection.id();
2901        assert!(router.orphan_goodbyes.claim(id, 7));
2902        assert!(!router.orphan_goodbyes.claim(id, 7));
2903        assert!(router.orphan_goodbyes.claim(id, 8));
2904        drop(connection);
2905        assert!(router
2906            .orphan_goodbyes
2907            .last_sent
2908            .lock()
2909            .unwrap()
2910            .get(&id)
2911            .is_none());
2912    }
2913
2914    #[test]
2915    fn channel_zero_cannot_be_registered_as_backend() {
2916        let mut router = Router::with_default_self_handler();
2917
2918        let err = router.register_backend(0, EchoBackend).unwrap_err();
2919
2920        assert_eq!(err, RouterError::ReservedChannelZero);
2921    }
2922}