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,
8    },
9    time::{Duration, Instant},
10};
11
12use subc_protocol::{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/// Per-route context shared with backends besides the frame itself.
479#[derive(Debug, Clone)]
480pub struct RouteCtx {
481    pub connection_id: ConnectionId,
482    pub egress: FrameSink,
483}
484
485/// Closed set of data-plane backends for non-zero channels.
486///
487/// Channel 0 is structurally special and remains [`Router::control`], not an
488/// enum variant. Static/test backends live in [`Router::backends`]; the forwarding
489/// backend is selected dynamically only when a per-connection forwarding binding exists.
490#[derive(Debug, Clone)]
491pub enum Backend {
492    Echo(EchoBackend),
493    Forward(ForwardBackend),
494}
495
496impl From<EchoBackend> for Backend {
497    fn from(backend: EchoBackend) -> Self {
498        Self::Echo(backend)
499    }
500}
501
502impl From<ForwardBackend> for Backend {
503    fn from(backend: ForwardBackend) -> Self {
504        Self::Forward(backend)
505    }
506}
507
508impl Backend {
509    pub async fn handle(&self, ctx: RouteCtx, frame: Frame) -> Result<(), RouterError> {
510        match self {
511            Self::Echo(backend) => backend.handle(ctx, frame).await,
512            Self::Forward(backend) => backend.handle(ctx, frame).await,
513        }
514    }
515}
516
517/// I/O-agnostic splice router keyed by envelope `channel`.
518///
519/// Channel 0 is reserved for subc itself and is always dispatched to the
520/// dedicated control handler. Other channels must be explicitly registered.
521/// Unknown client-originated non-zero channels are translated to canonical JSON `ERROR` frames on
522/// the connection sink so the peer can continue using the same socket. Module-originated frames for
523/// released route channels are logged and dropped as the channel-gone race backstop.
524///
525/// DATA-PLANE BODIES ARE NEVER DECODED HERE, and the consequence is worth stating
526/// because it looks like a guarantee and is not. Additive fields in a request or
527/// response body reach the far side untouched — not because anything permits them,
528/// but because routing reads only the 21-byte header and treats the body as opaque
529/// bytes. That is a performance property, so **nothing prevents it from changing**:
530/// a future reason to inspect a body would convert a wire-transparent path into a
531/// filtering one, and nobody would think of it as a contract change.
532///
533/// So when a peer asks whether subc sees an additive field, the answer is per-path
534/// and this path's zero means WE NEVER LOOK rather than WE LOOK AT EVERYTHING. The
535/// control plane (typed enums at the frame boundary) and the MCP gateway (envelope
536/// unwrap plus a named struct) both narrow; only this one does not.
537pub struct Router {
538    backends: HashMap<u16, Backend>,
539    control: Arc<ControlHandler>,
540    forwarding: Arc<ForwardingTable>,
541    forward_backend: ForwardBackend,
542    counters: DaemonCounters,
543    next_connection_id: AtomicU64,
544}
545
546impl Router {
547    pub fn with_control_handler(control: Arc<ControlHandler>) -> Self {
548        // The handler the router serves is the one a swap must tell when it
549        // promotes a candidate; this is where it first sits behind an `Arc`.
550        control.install_swap_promotion_observer();
551        let forwarding = control.forwarding();
552        let counters = control.counters();
553        Self {
554            backends: HashMap::new(),
555            control,
556            forwarding: Arc::clone(&forwarding),
557            forward_backend: ForwardBackend::new(forwarding),
558            counters,
559            // ConnectionId::LOCAL is 0; real socket ids start at 1 and never collide.
560            next_connection_id: AtomicU64::new(1),
561        }
562    }
563
564    pub fn with_default_self_handler() -> Self {
565        Self::with_control_handler(Arc::new(ControlHandler::default()))
566    }
567
568    pub fn forwarding(&self) -> Arc<ForwardingTable> {
569        Arc::clone(&self.forwarding)
570    }
571
572    pub fn register_backend(
573        &mut self,
574        channel: u16,
575        backend: impl Into<Backend>,
576    ) -> Result<(), RouterError> {
577        self.register_backend_arc(channel, Arc::new(backend.into()))
578    }
579
580    pub(crate) fn register_backend_arc(
581        &mut self,
582        channel: u16,
583        backend: Arc<Backend>,
584    ) -> Result<(), RouterError> {
585        if channel == 0 {
586            return Err(RouterError::ReservedChannelZero);
587        }
588        if self.backends.contains_key(&channel) {
589            return Err(RouterError::DuplicateChannel { channel });
590        }
591        self.backends.insert(channel, backend.as_ref().clone());
592        Ok(())
593    }
594
595    /// Start a connection-scoped routing context. Dropping the guard releases
596    /// any control-plane registrations owned by the connection.
597    fn record_module_frame_drop(&self, connection_id: ConnectionId) -> Result<(), RouterError> {
598        let module_id = self
599            .forwarding
600            .module_id_for_connection(connection_id)
601            .map_err(RouterError::Forwarding)?;
602        self.counters
603            .increment_module_frames_dropped_no_route(module_id.as_deref());
604        Ok(())
605    }
606
607    pub fn begin_connection(&self) -> RouterConnection {
608        let raw = self.next_connection_id.fetch_add(1, Ordering::Relaxed);
609        let id = ConnectionId::new(raw);
610        let close_receiver = self.forwarding.register_connection_close(id);
611        RouterConnection {
612            id,
613            control_handler: Arc::clone(&self.control),
614            forwarding: Arc::clone(&self.forwarding),
615            close_receiver: Some(close_receiver),
616        }
617    }
618
619    pub(crate) fn route_open_target(&self, frame: &Frame) -> Option<String> {
620        self.control.route_open_target(frame)
621    }
622
623    pub(crate) fn route_open_capacity_refusal(
624        &self,
625        ctx: &RouteCtx,
626        frame: &Frame,
627        target_module_id: &str,
628        limit: usize,
629    ) -> Result<Frame, RouterError> {
630        self.control
631            .route_open_capacity_refusal(ctx, frame, target_module_id, limit)
632    }
633
634    pub async fn route_for_connection(
635        &self,
636        ctx: &RouteCtx,
637        frame: Frame,
638    ) -> Result<(), RouterError> {
639        self.route_for_connection_started(ctx, frame, None).await
640    }
641
642    pub(crate) async fn route_for_connection_started(
643        &self,
644        ctx: &RouteCtx,
645        frame: Frame,
646        dispatch_started_at: Option<Instant>,
647    ) -> Result<(), RouterError> {
648        let channel = frame.header.channel;
649        let epoch = frame.header.epoch;
650        let corr = frame.header.corr;
651        if channel == 0 {
652            debug!(
653                connection_id = ctx.connection_id.get(),
654                corr,
655                frame_type = ?frame.header.ty,
656                "routing control frame"
657            );
658            // The connection loop dispatches every control operation directly
659            // except `route.open`. Its task passes a timestamp captured before
660            // spawn, so slow-dispatch timing includes scheduler delay but no
661            // wait in an application-owned queue.
662            let dispatch_started_at = (frame.header.ty == FrameType::Request)
663                .then(|| dispatch_started_at.unwrap_or_else(Instant::now));
664            let responses = self
665                .control
666                .handle_control_frame_timed(ctx, frame, dispatch_started_at)
667                .await?;
668            for response in responses {
669                ctx.egress.send(response).await?;
670            }
671            return Ok(());
672        }
673
674        let data_route = self
675            .forwarding
676            .lookup_data_route(ctx.connection_id, channel, epoch)
677            .map_err(RouterError::Forwarding)?;
678
679        match data_route {
680            DataRoute::Module(DataRouteState::EpochMismatch) => {
681                if frame.header.ty == FrameType::Request {
682                    self.counters
683                        .increment_module_requests_dropped_stale_route();
684                    let err = RouterError::StaleRouteEpoch {
685                        channel,
686                        epoch,
687                        corr,
688                    };
689                    if let Some(error_frame) = err.to_error_frame() {
690                        ctx.egress.send(error_frame).await?;
691                    }
692                } else {
693                    self.record_module_frame_drop(ctx.connection_id)?;
694                }
695                debug!(
696                    connection_id = ctx.connection_id.get(),
697                    channel, epoch, corr, "dropping module frame for stale route epoch"
698                );
699                return Ok(());
700            }
701            DataRoute::Module(DataRouteState::Reserved) => {
702                if frame.header.ty == FrameType::Request {
703                    self.counters
704                        .increment_module_requests_dropped_stale_route();
705                    let err = RouterError::UnknownChannel {
706                        channel,
707                        epoch,
708                        corr,
709                    };
710                    if let Some(error_frame) = err.to_error_frame() {
711                        ctx.egress.send(error_frame).await?;
712                    }
713                } else {
714                    self.record_module_frame_drop(ctx.connection_id)?;
715                }
716                debug!(
717                    connection_id = ctx.connection_id.get(),
718                    channel, epoch, corr, "dropping module frame for reserved route handle"
719                );
720                return Ok(());
721            }
722            DataRoute::Module(DataRouteState::Absent) => {
723                if frame.header.ty == FrameType::Request {
724                    self.counters
725                        .increment_module_requests_dropped_stale_route();
726                    let err = RouterError::UnknownChannel {
727                        channel,
728                        epoch,
729                        corr,
730                    };
731                    if let Some(error_frame) = err.to_error_frame() {
732                        ctx.egress.send(error_frame).await?;
733                    }
734                } else {
735                    self.record_module_frame_drop(ctx.connection_id)?;
736                }
737                debug!(
738                    connection_id = ctx.connection_id.get(),
739                    channel, epoch, corr, "dropping module frame for absent route handle"
740                );
741                return Ok(());
742            }
743            DataRoute::Module(DataRouteState::Bound(route)) => {
744                if frame.header.ty == FrameType::Goodbye {
745                    if let RouteRelease::Removed(target) = self
746                        .forwarding
747                        .release_module_route(ctx.connection_id, channel, epoch)
748                        .map_err(RouterError::Forwarding)?
749                    {
750                        let mut goodbye = frame;
751                        goodbye.header.channel = target.channel;
752                        goodbye.header.epoch = target.epoch;
753                        if let Err(err) = target.sink.try_send(goodbye) {
754                            if target.close_on_delivery_failure()
755                                && self
756                                    .forwarding
757                                    .escalate_client_delivery_failure(
758                                        target.connection_id,
759                                        target.channel,
760                                        target.epoch,
761                                        CloseReason::new(
762                                            "route_goodbye_delivery_failed",
763                                            format!(
764                                                "failed to enqueue route GOODBYE for client channel {}: {err}",
765                                                target.channel
766                                            ),
767                                        ),
768                                        UndeliveredFrame {
769                                            module_id: Some(&route.module_id),
770                                            sink: &target.sink,
771                                        },
772                                    )
773                                    .map_err(RouterError::Forwarding)?
774                            {
775                                self.counters.increment_goodbye_relay_client_failed();
776                            }
777                        }
778                    }
779                    return Ok(());
780                }
781
782                // A terminal frame ends the request at the module whether or not
783                // the client can still take it, so its credit is released before
784                // delivery is attempted. Releasing only after a successful
785                // enqueue would leave a drain counting a finished request until
786                // the client connection's cleanup removes the route.
787                let releases_credit = is_terminal_frame(frame.header.ty);
788                if releases_credit {
789                    route.flow.release_corr(corr);
790                }
791                let mut frame = frame;
792                frame.header.channel = route.client_channel;
793                frame.header.epoch = route.client_epoch;
794                if let Err(err) = route.client_sink.try_send(frame) {
795                    if self
796                        .forwarding
797                        .escalate_client_delivery_failure(
798                            route.client_connection_id,
799                            route.client_channel,
800                            route.client_epoch,
801                            CloseReason::new(
802                                "module_to_client_delivery_failed",
803                                format!(
804                                    "failed to enqueue module frame for client channel {} corr {corr}: {err}",
805                                    route.client_channel
806                                ),
807                            ),
808                            UndeliveredFrame {
809                                module_id: Some(&route.module_id),
810                                sink: &route.client_sink,
811                            },
812                        )
813                        .map_err(RouterError::Forwarding)?
814                    {
815                        self.counters
816                            .increment_client_egress_close_delivery_failed();
817                    }
818                    return Ok(());
819                }
820                return Ok(());
821            }
822            DataRoute::Client(DataRouteState::EpochMismatch) => {
823                if frame.header.ty == FrameType::Request {
824                    self.counters.increment_client_frames_dropped_stale_route();
825                    // Dropped before forwarding; a re-bind retry cannot double-execute this request.
826                    let err = RouterError::StaleRouteEpoch {
827                        channel,
828                        epoch,
829                        corr,
830                    };
831                    if let Some(error_frame) = err.to_error_frame() {
832                        ctx.egress.send(error_frame).await?;
833                    }
834                }
835                debug!(
836                    connection_id = ctx.connection_id.get(),
837                    channel, epoch, corr, "dropping client frame for stale route epoch"
838                );
839                return Ok(());
840            }
841            DataRoute::Client(DataRouteState::Reserved) => {
842                if frame.header.ty == FrameType::Request {
843                    let err = RouterError::UnknownChannel {
844                        channel,
845                        epoch,
846                        corr,
847                    };
848                    if let Some(error_frame) = err.to_error_frame() {
849                        ctx.egress.send(error_frame).await?;
850                    }
851                }
852                return Ok(());
853            }
854            DataRoute::Client(DataRouteState::Bound(route)) => {
855                if frame.header.ty == FrameType::Goodbye {
856                    let _ = self
857                        .control
858                        .handle_route_goodbye(ctx.connection_id, channel, epoch)?;
859                    return Ok(());
860                }
861                return self.forward_backend.handle_bound(frame, route).await;
862            }
863            DataRoute::Client(DataRouteState::Absent) => {}
864        }
865
866        if let Some(backend) = self.backends.get(&channel) {
867            return backend.handle(ctx.clone(), frame).await;
868        }
869        if frame.header.ty == FrameType::Request {
870            let err = RouterError::UnknownChannel {
871                channel,
872                epoch,
873                corr,
874            };
875            if let Some(error_frame) = err.to_error_frame() {
876                ctx.egress.send(error_frame).await?;
877            }
878        }
879        Ok(())
880    }
881}
882
883impl Default for Router {
884    fn default() -> Self {
885        Self::with_default_self_handler()
886    }
887}
888
889/// Connection-scoped cleanup guard returned by [`Router::begin_connection`].
890#[must_use]
891pub struct RouterConnection {
892    id: ConnectionId,
893    control_handler: Arc<ControlHandler>,
894    forwarding: Arc<ForwardingTable>,
895    close_receiver: Option<ConnectionCloseReceiver>,
896}
897
898impl RouterConnection {
899    pub fn id(&self) -> ConnectionId {
900        self.id
901    }
902
903    pub(crate) fn take_close_receiver(&mut self) -> ConnectionCloseReceiver {
904        self.close_receiver
905            .take()
906            .expect("connection close receiver can only be taken once")
907    }
908}
909
910impl Drop for RouterConnection {
911    fn drop(&mut self) {
912        self.forwarding.unregister_connection_close(self.id);
913        // GOODBYE (explicit) and connection-drop cleanup both call the same
914        // idempotent deregistration path.
915        let _ = self.control_handler.cleanup_connection(self.id);
916    }
917}
918
919/// Minimal in-memory backend used by tests and early wiring: it emits a
920/// `RESPONSE` on the same channel/correlation id with the exact same body bytes.
921#[derive(Debug, Default, Clone, Copy)]
922pub struct EchoBackend;
923
924impl EchoBackend {
925    pub async fn handle(&self, ctx: RouteCtx, frame: Frame) -> Result<(), RouterError> {
926        let response = Frame::build_with_version(
927            frame.header.ver,
928            FrameType::Response,
929            frame.header.flags,
930            frame.header.channel,
931            frame.header.epoch,
932            frame.header.corr,
933            frame.body,
934        )
935        .map_err(RouterError::FrameBuild)?;
936        ctx.egress.send(response).await
937    }
938}
939
940/// Data-plane backend that splices client frames to the module connection bound at attach time.
941#[derive(Debug, Clone)]
942pub struct ForwardBackend {
943    forwarding: Arc<ForwardingTable>,
944}
945
946impl ForwardBackend {
947    pub fn new(forwarding: Arc<ForwardingTable>) -> Self {
948        Self { forwarding }
949    }
950
951    pub async fn handle(&self, ctx: RouteCtx, frame: Frame) -> Result<(), RouterError> {
952        let channel = frame.header.channel;
953        let corr = frame.header.corr;
954        let route = match self
955            .forwarding
956            .lookup_data_route(ctx.connection_id, channel, frame.header.epoch)
957            .map_err(RouterError::Forwarding)?
958        {
959            DataRoute::Client(DataRouteState::Bound(route)) => route,
960            DataRoute::Client(_) | DataRoute::Module(_) => {
961                return Err(RouterError::UnknownChannel {
962                    channel,
963                    epoch: frame.header.epoch,
964                    corr,
965                });
966            }
967        };
968        self.handle_bound(frame, route).await
969    }
970
971    pub(crate) async fn handle_bound(
972        &self,
973        frame: Frame,
974        route: Arc<RouteBinding>,
975    ) -> Result<(), RouterError> {
976        let channel = frame.header.channel;
977        let corr = frame.header.corr;
978        let frame_type = frame.header.ty;
979
980        // CANCEL and other non-REQUEST frames bypass the request-credit window;
981        // the original request's credit is freed only by the module's terminal frame.
982        let acquired_credit = frame_type == FrameType::Request;
983        if acquired_credit {
984            if let Err(err) = route
985                .flow
986                .acquire_tagged(corr, frame.header.flags.is_subscription())
987                .await
988            {
989                // `module_reloading` here is answered BEFORE the frame is
990                // forwarded, so the module never sees the request and callers
991                // may re-dispatch it after reopening the route. That is a wire
992                // guarantee documented on `error_codes::MODULE_RELOADING`; do
993                // not emit this code for a request that already reached
994                // `module_sink.send` below.
995                if self
996                    .forwarding
997                    .endpoint_is_draining(route.module_endpoint)
998                    .map_err(RouterError::Forwarding)?
999                {
1000                    return Err(RouterError::route_error_with_epoch(
1001                        channel,
1002                        frame.header.epoch,
1003                        corr,
1004                        "module_reloading",
1005                        format!("module endpoint for route channel {channel} is reloading"),
1006                    ));
1007                }
1008                return Err(RouterError::backend_with_epoch(
1009                    channel,
1010                    frame.header.epoch,
1011                    corr,
1012                    format!("{err} for route channel {channel}"),
1013                ));
1014            }
1015        }
1016
1017        let mut frame = frame;
1018        frame.header.channel = route.module_channel;
1019        frame.header.epoch = route.module_epoch;
1020        let result = route.module_sink.send(frame).await.map_err(|err| {
1021            RouterError::backend_with_epoch(channel, route.client_epoch, corr, err.to_string())
1022        });
1023        if acquired_credit && result.is_err() {
1024            route.flow.release_corr(corr);
1025        }
1026        result
1027    }
1028}
1029
1030fn is_terminal_frame(frame_type: FrameType) -> bool {
1031    matches!(
1032        frame_type,
1033        FrameType::Response | FrameType::Error | FrameType::StreamEnd
1034    )
1035}
1036
1037/// Typed router errors. Routable failures can be translated to canonical JSON
1038/// `ERROR` frames with [`RouterError::to_error_frame`].
1039#[derive(Debug, Clone, PartialEq, Eq)]
1040pub enum RouterError {
1041    ReservedChannelZero,
1042    DuplicateChannel {
1043        channel: u16,
1044    },
1045    UnknownChannel {
1046        channel: u16,
1047        epoch: u32,
1048        corr: u64,
1049    },
1050    StaleRouteEpoch {
1051        channel: u16,
1052        epoch: u32,
1053        corr: u64,
1054    },
1055    Backend {
1056        channel: u16,
1057        epoch: u32,
1058        corr: u64,
1059        message: String,
1060    },
1061    RouteError {
1062        channel: u16,
1063        epoch: u32,
1064        corr: u64,
1065        code: String,
1066        message: String,
1067    },
1068    FrameBuild(FrameBuildError),
1069    Forwarding(ForwardingError),
1070}
1071
1072impl RouterError {
1073    pub fn backend(channel: u16, corr: u64, message: impl Into<String>) -> Self {
1074        Self::backend_with_epoch(channel, 0, corr, message)
1075    }
1076
1077    pub fn backend_with_epoch(
1078        channel: u16,
1079        epoch: u32,
1080        corr: u64,
1081        message: impl Into<String>,
1082    ) -> Self {
1083        Self::Backend {
1084            channel,
1085            epoch,
1086            corr,
1087            message: message.into(),
1088        }
1089    }
1090
1091    pub fn route_error(
1092        channel: u16,
1093        corr: u64,
1094        code: impl Into<String>,
1095        message: impl Into<String>,
1096    ) -> Self {
1097        Self::route_error_with_epoch(channel, 0, corr, code, message)
1098    }
1099
1100    pub fn route_error_with_epoch(
1101        channel: u16,
1102        epoch: u32,
1103        corr: u64,
1104        code: impl Into<String>,
1105        message: impl Into<String>,
1106    ) -> Self {
1107        Self::RouteError {
1108            channel,
1109            epoch,
1110            corr,
1111            code: code.into(),
1112            message: message.into(),
1113        }
1114    }
1115
1116    /// Translate route failures that belong on the wire into an `ERROR` frame.
1117    pub fn to_error_frame(&self) -> Option<Frame> {
1118        match self {
1119            Self::UnknownChannel {
1120                channel,
1121                epoch,
1122                corr,
1123            } => error_frame(
1124                *channel,
1125                *epoch,
1126                *corr,
1127                "unknown_channel",
1128                format!("unknown channel {channel}"),
1129            ),
1130            Self::StaleRouteEpoch {
1131                channel,
1132                epoch,
1133                corr,
1134            } => error_frame(
1135                *channel,
1136                *epoch,
1137                *corr,
1138                "stale_route_epoch",
1139                format!("stale route epoch for channel {channel}"),
1140            ),
1141            Self::Backend {
1142                channel,
1143                epoch,
1144                corr,
1145                message,
1146            } => error_frame(*channel, *epoch, *corr, "backend_error", message.clone()),
1147            Self::RouteError {
1148                channel,
1149                epoch,
1150                corr,
1151                code,
1152                message,
1153            } => error_frame(*channel, *epoch, *corr, code, message.clone()),
1154            Self::ReservedChannelZero
1155            | Self::DuplicateChannel { .. }
1156            | Self::FrameBuild(_)
1157            | Self::Forwarding(_) => None,
1158        }
1159    }
1160}
1161
1162fn error_frame(channel: u16, epoch: u32, corr: u64, code: &str, message: String) -> Option<Frame> {
1163    let body = serde_json::to_vec(&ErrorBody {
1164        code: code.to_string(),
1165        message,
1166        detail: None,
1167    })
1168    .ok()?;
1169
1170    Frame::build(
1171        FrameType::Error,
1172        Flags::new(false, Priority::Passive, false),
1173        channel,
1174        epoch,
1175        corr,
1176        body,
1177    )
1178    .ok()
1179}
1180
1181impl fmt::Display for RouterError {
1182    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1183        match self {
1184            Self::ReservedChannelZero => write!(f, "channel 0 is reserved for subc"),
1185            Self::DuplicateChannel { channel } => {
1186                write!(f, "backend already registered for channel {channel}")
1187            }
1188            Self::UnknownChannel { channel, corr, .. } => {
1189                write!(f, "unknown channel {channel} for corr {corr}")
1190            }
1191            Self::StaleRouteEpoch { channel, corr, .. } => {
1192                write!(f, "stale route epoch for channel {channel} corr {corr}")
1193            }
1194            Self::Backend {
1195                channel,
1196                corr,
1197                message,
1198                ..
1199            } => write!(
1200                f,
1201                "backend error on channel {channel} corr {corr}: {message}"
1202            ),
1203            Self::RouteError {
1204                channel,
1205                corr,
1206                code,
1207                message,
1208                ..
1209            } => write!(
1210                f,
1211                "route error {code} on channel {channel} corr {corr}: {message}"
1212            ),
1213            Self::FrameBuild(err) => write!(f, "failed to build routed frame: {err}"),
1214            Self::Forwarding(err) => write!(f, "forwarding error: {err}"),
1215        }
1216    }
1217}
1218
1219impl Error for RouterError {
1220    fn source(&self) -> Option<&(dyn Error + 'static)> {
1221        match self {
1222            Self::FrameBuild(err) => Some(err),
1223            Self::Forwarding(err) => Some(err),
1224            Self::ReservedChannelZero
1225            | Self::DuplicateChannel { .. }
1226            | Self::UnknownChannel { .. }
1227            | Self::StaleRouteEpoch { .. }
1228            | Self::Backend { .. }
1229            | Self::RouteError { .. } => None,
1230        }
1231    }
1232}
1233
1234#[cfg(test)]
1235mod tests {
1236    use super::*;
1237    use crate::{
1238        forwarding::RouteBindRelayOutcome,
1239        supervise::{ModuleSpec, RestartPolicy, Supervisor, SupervisorHandle},
1240        ControlHandler, Registry,
1241    };
1242    use std::{
1243        sync::{mpsc as std_mpsc, Arc},
1244        time::Duration,
1245    };
1246    use subc_control::ModuleProtocol;
1247    use subc_protocol::{manifest::Concurrency, ErrorBody, Flags, FrameType, Priority};
1248    use tokio::sync::mpsc;
1249
1250    pub(crate) use crate::router::test_log::{captured_logs, log_capture};
1251
1252    fn logged_millis(logs: &str, field: &str) -> u64 {
1253        logs.split_whitespace()
1254            .find_map(|part| part.strip_prefix(field))
1255            .and_then(|value| value.parse().ok())
1256            .unwrap_or_else(|| panic!("missing numeric {field} in logs: {logs}"))
1257    }
1258
1259    fn request(channel: u16, corr: u64, body: &[u8]) -> Frame {
1260        Frame::build(
1261            FrameType::Request,
1262            Flags::new(true, Priority::Interactive, false),
1263            channel,
1264            0,
1265            corr,
1266            body.to_vec(),
1267        )
1268        .unwrap()
1269    }
1270
1271    fn ping(corr: u64) -> Frame {
1272        Frame::build(
1273            FrameType::Ping,
1274            Flags::new(false, Priority::Passive, false),
1275            0,
1276            0,
1277            corr,
1278            Vec::new(),
1279        )
1280        .unwrap()
1281    }
1282
1283    fn route_ctx() -> (RouteCtx, mpsc::Receiver<crate::router::OutboundFrame>) {
1284        let (tx, rx) = mpsc::channel(8);
1285        (
1286            RouteCtx {
1287                connection_id: ConnectionId::LOCAL,
1288                egress: FrameSink::new(tx),
1289            },
1290            rx,
1291        )
1292    }
1293
1294    #[tokio::test]
1295    async fn echo_backend_returns_response_with_byte_identical_body() {
1296        let mut router = Router::with_default_self_handler();
1297        router.register_backend(7, EchoBackend).unwrap();
1298        let (ctx, mut rx) = route_ctx();
1299        let body = b"{not parsed}\0\xff";
1300
1301        router
1302            .route_for_connection(&ctx, request(7, 123, body))
1303            .await
1304            .unwrap();
1305        let response = rx.recv().await.unwrap();
1306
1307        assert_eq!(response.header.ty, FrameType::Response);
1308        assert_eq!(response.header.channel, 7);
1309        assert_eq!(response.header.corr, 123);
1310        assert_eq!(response.body, body);
1311        assert!(rx.try_recv().is_err());
1312    }
1313
1314    #[tokio::test]
1315    async fn unknown_channel_emits_canonical_error_frame() {
1316        let router = Router::with_default_self_handler();
1317        let (ctx, mut rx) = route_ctx();
1318
1319        router
1320            .route_for_connection(&ctx, request(99, 5, b"payload"))
1321            .await
1322            .unwrap();
1323        let error_frame = rx.recv().await.unwrap();
1324
1325        assert_eq!(error_frame.header.ty, FrameType::Error);
1326        assert_eq!(error_frame.header.channel, 99);
1327        assert_eq!(error_frame.header.corr, 5);
1328        let body: ErrorBody = serde_json::from_slice(&error_frame.body).unwrap();
1329        assert_eq!(body.code, "unknown_channel");
1330        assert_eq!(body.message, "unknown channel 99");
1331    }
1332
1333    #[tokio::test]
1334    async fn channel_zero_uses_control_handler_not_backend_registry() {
1335        let mut router = Router::with_default_self_handler();
1336        router.register_backend(1, EchoBackend).unwrap();
1337        let (ctx, mut rx) = route_ctx();
1338
1339        router.route_for_connection(&ctx, ping(77)).await.unwrap();
1340        let response = rx.recv().await.unwrap();
1341
1342        assert_eq!(response.header.ty, FrameType::Pong);
1343        assert_eq!(response.header.channel, 0);
1344        assert_eq!(response.header.corr, 77);
1345        assert!(response.body.is_empty());
1346    }
1347
1348    #[tokio::test]
1349    async fn slow_control_dispatch_logs_decoded_op_and_elapsed_time() {
1350        let control = Arc::new(
1351            ControlHandler::new(Arc::new(Registry::default()))
1352                .with_control_dispatch_delay(Duration::from_millis(1050)),
1353        );
1354        let router = Router::with_control_handler(control);
1355        let (ctx, mut rx) = route_ctx();
1356        let (output, guard) = log_capture(tracing::Level::WARN);
1357
1358        router
1359            .route_for_connection(&ctx, request(0, 41, br#"{"op":"server.describe"}"#))
1360            .await
1361            .expect("slow request routes");
1362        assert!(rx.recv().await.is_some(), "request receives a response");
1363        drop(guard);
1364
1365        let logs = captured_logs(&output);
1366        assert!(logs.contains("slow control dispatch"));
1367        assert!(logs.contains("op=server.describe"));
1368        assert!(logs.contains("connection_id=0"));
1369        assert!(logs.contains("corr=41"));
1370        assert!(
1371            logged_millis(&logs, "elapsed_ms=") >= 1050,
1372            "elapsed must include the injected handler delay: {logs}"
1373        );
1374    }
1375
1376    #[tokio::test]
1377    async fn fast_control_dispatch_emits_arrival_without_slow_warning() {
1378        let router = Router::with_default_self_handler();
1379        let (ctx, mut rx) = route_ctx();
1380        let (output, guard) = log_capture(tracing::Level::DEBUG);
1381
1382        router
1383            .route_for_connection(&ctx, request(0, 42, br#"{"op":"server.describe"}"#))
1384            .await
1385            .expect("fast request routes");
1386        assert!(rx.recv().await.is_some(), "request receives a response");
1387        drop(guard);
1388
1389        let logs = captured_logs(&output);
1390        assert!(logs.contains("control dispatch op=server.describe connection_id=0 corr=42"));
1391        assert!(!logs.contains("slow control dispatch"));
1392    }
1393
1394    #[tokio::test]
1395    async fn control_dispatch_arrival_is_hidden_at_info() {
1396        let router = Router::with_default_self_handler();
1397        let (ctx, mut rx) = route_ctx();
1398        let (output, guard) = log_capture(tracing::Level::INFO);
1399
1400        router
1401            .route_for_connection(&ctx, request(0, 43, br#"{"op":"server.describe"}"#))
1402            .await
1403            .expect("fast request routes");
1404        assert!(rx.recv().await.is_some(), "request receives a response");
1405        drop(guard);
1406
1407        assert!(
1408            !captured_logs(&output).contains("control dispatch"),
1409            "arrival logging must stay hidden at INFO"
1410        );
1411    }
1412
1413    #[tokio::test]
1414    async fn supervisor_list_logs_contended_snapshot_lock_only() {
1415        let registry = Arc::new(Registry::default());
1416        let handle = SupervisorHandle::new();
1417        let supervisor = Supervisor::new(Arc::clone(&registry), RestartPolicy::default())
1418            .with_handle(handle.clone());
1419        let module = supervisor
1420            .supervise_configured(
1421                ModuleSpec {
1422                    module_id: "held-module".to_string(),
1423                    program: "test-module".into(),
1424                    args: Vec::new(),
1425                    env: Vec::new(),
1426                    reserved: false,
1427                    reserved_prefixes: Vec::new(),
1428                    protocol: ModuleProtocol::Subc,
1429                    overlap: Default::default(),
1430                },
1431                false,
1432            )
1433            .expect("disabled test module is supervised");
1434        let router = Router::with_control_handler(Arc::new(
1435            ControlHandler::new(Arc::clone(&registry)).with_supervisor(handle),
1436        ));
1437        let (ctx, mut rx) = route_ctx();
1438        let (acquired, ready) = std_mpsc::channel();
1439        let holder = module.hold_snapshot_for_test(acquired, Duration::from_millis(400));
1440        ready.recv().expect("holder acquired snapshot lock");
1441        let (output, guard) = log_capture(tracing::Level::WARN);
1442
1443        router
1444            .route_for_connection(&ctx, request(0, 44, br#"{"op":"supervisor.list"}"#))
1445            .await
1446            .expect("list request routes after the lock releases");
1447        assert!(
1448            rx.recv().await.is_some(),
1449            "list request receives a response"
1450        );
1451        holder.join().expect("snapshot holder exits cleanly");
1452        drop(guard);
1453
1454        let logs = captured_logs(&output);
1455        assert!(logs.contains("slow snapshot lock"));
1456        assert!(logs.contains("module_id=held-module"));
1457        assert!(logs.contains("caller=list"));
1458        assert!(
1459            logged_millis(&logs, "waited_ms=") >= 250,
1460            "wait must exceed the slow-lock threshold: {logs}"
1461        );
1462
1463        let (output, guard) = log_capture(tracing::Level::WARN);
1464        router
1465            .route_for_connection(&ctx, request(0, 45, br#"{"op":"supervisor.list"}"#))
1466            .await
1467            .expect("uncontended list request routes");
1468        assert!(
1469            rx.recv().await.is_some(),
1470            "uncontended list receives a response"
1471        );
1472        drop(guard);
1473        assert!(
1474            !captured_logs(&output).contains("slow snapshot lock"),
1475            "uncontended list acquisition must not warn"
1476        );
1477    }
1478
1479    #[tokio::test]
1480    async fn full_module_to_client_sink_requests_client_close_without_erroring_module() {
1481        let forwarding = Arc::new(ForwardingTable::default());
1482        let control = Arc::new(ControlHandler::with_forwarding(
1483            Arc::new(crate::Registry::default()),
1484            Arc::clone(&forwarding),
1485        ));
1486        let router = Router::with_control_handler(control);
1487        let module_connection = ConnectionId::new(10);
1488        let client_connection = ConnectionId::new(20);
1489        let mut close_receiver = forwarding.register_connection_close(client_connection);
1490        let (module_tx, _module_rx) = mpsc::channel(1);
1491        forwarding
1492            .register_module_connection(
1493                module_connection,
1494                "full-sink-provider".to_string(),
1495                1,
1496                Concurrency::ModuleManaged,
1497                FrameSink::new(module_tx),
1498            )
1499            .unwrap();
1500        let (client_tx, mut client_rx) = mpsc::channel(1);
1501        let pending = forwarding
1502            .begin_route_bind_relay_for_test(
1503                client_connection,
1504                FrameSink::new(client_tx),
1505                700,
1506                "full-sink-provider",
1507            )
1508            .unwrap();
1509        forwarding
1510            .complete_pending_relay(
1511                module_connection,
1512                pending.corr,
1513                RouteBindRelayOutcome::Accepted,
1514            )
1515            .unwrap();
1516
1517        let (module_egress_tx, _module_egress_rx) = mpsc::channel(1);
1518        let module_ctx = RouteCtx {
1519            connection_id: module_connection,
1520            egress: FrameSink::new(module_egress_tx),
1521        };
1522        let terminal = Frame::build(
1523            FrameType::Response,
1524            Flags::new(false, Priority::Interactive, true),
1525            pending.module_channel,
1526            pending.module_epoch,
1527            701,
1528            b"terminal".to_vec(),
1529        )
1530        .unwrap();
1531
1532        router
1533            .route_for_connection(&module_ctx, terminal)
1534            .await
1535            .unwrap();
1536        let reason = tokio::time::timeout(Duration::from_secs(1), &mut close_receiver)
1537            .await
1538            .expect("close request should be sent for the full client sink")
1539            .expect("close sender should include a reason");
1540        assert!(
1541            reason
1542                .to_string()
1543                .contains("module_to_client_delivery_failed"),
1544            "unexpected close reason: {reason}"
1545        );
1546        assert_eq!(client_rx.try_recv().unwrap().header.corr, 700);
1547        assert!(client_rx.try_recv().is_err());
1548        assert_eq!(
1549            router.counters.snapshot()["client_egress_close_delivery_failed"],
1550            1
1551        );
1552    }
1553
1554    /// A terminal frame ends the request at the module even when the client
1555    /// cannot take it, so the drain must stop counting it at once rather than
1556    /// when the client connection's cleanup later removes the route.
1557    #[tokio::test]
1558    async fn terminal_frame_releases_its_credit_even_when_client_delivery_fails() {
1559        let forwarding = Arc::new(ForwardingTable::default());
1560        let control = Arc::new(ControlHandler::with_forwarding(
1561            Arc::new(crate::Registry::default()),
1562            Arc::clone(&forwarding),
1563        ));
1564        let router = Router::with_control_handler(control);
1565        let module_connection = ConnectionId::new(11);
1566        let client_connection = ConnectionId::new(21);
1567        let _close_receiver = forwarding.register_connection_close(client_connection);
1568        let (module_tx, _module_rx) = mpsc::channel(1);
1569        forwarding
1570            .register_module_connection(
1571                module_connection,
1572                "credit-provider".to_string(),
1573                1,
1574                Concurrency::ModuleManaged,
1575                FrameSink::new(module_tx),
1576            )
1577            .unwrap();
1578        // Capacity one, filled by the route.open response, so the terminal
1579        // frame below cannot be enqueued for the client.
1580        let (client_tx, _client_rx) = mpsc::channel(1);
1581        let pending = forwarding
1582            .begin_route_bind_relay_for_test(
1583                client_connection,
1584                FrameSink::new(client_tx),
1585                800,
1586                "credit-provider",
1587            )
1588            .unwrap();
1589        forwarding
1590            .complete_pending_relay(
1591                module_connection,
1592                pending.corr,
1593                RouteBindRelayOutcome::Accepted,
1594            )
1595            .unwrap();
1596        let DataRoute::Client(DataRouteState::Bound(route)) = forwarding
1597            .lookup_data_route(
1598                client_connection,
1599                pending.client_channel,
1600                pending.client_epoch,
1601            )
1602            .unwrap()
1603        else {
1604            panic!("expected a bound client route");
1605        };
1606        route.flow.acquire_tagged(801, false).await.unwrap();
1607        assert_eq!(route.flow.drain_in_flight(), 1);
1608
1609        let (module_egress_tx, _module_egress_rx) = mpsc::channel(1);
1610        let module_ctx = RouteCtx {
1611            connection_id: module_connection,
1612            egress: FrameSink::new(module_egress_tx),
1613        };
1614        let terminal = Frame::build(
1615            FrameType::Response,
1616            Flags::new(false, Priority::Interactive, true),
1617            pending.module_channel,
1618            pending.module_epoch,
1619            801,
1620            b"terminal".to_vec(),
1621        )
1622        .unwrap();
1623        router
1624            .route_for_connection(&module_ctx, terminal)
1625            .await
1626            .unwrap();
1627
1628        assert_eq!(
1629            router.counters.snapshot()["client_egress_close_delivery_failed"],
1630            1,
1631            "the client delivery must have failed for this test to mean anything"
1632        );
1633        assert_eq!(
1634            route.flow.drain_in_flight(),
1635            0,
1636            "the module's terminal frame must release its credit even though the client could not take it"
1637        );
1638    }
1639
1640    #[tokio::test]
1641    async fn full_route_goodbye_sink_requests_target_close_without_erroring_module() {
1642        let forwarding = Arc::new(ForwardingTable::default());
1643        let control = Arc::new(ControlHandler::with_forwarding(
1644            Arc::new(crate::Registry::default()),
1645            Arc::clone(&forwarding),
1646        ));
1647        let router = Router::with_control_handler(control);
1648        let module_connection = ConnectionId::new(30);
1649        let client_connection = ConnectionId::new(40);
1650        let mut close_receiver = forwarding.register_connection_close(client_connection);
1651        let (module_tx, _module_rx) = mpsc::channel(1);
1652        forwarding
1653            .register_module_connection(
1654                module_connection,
1655                "goodbye-full-provider".to_string(),
1656                1,
1657                Concurrency::ModuleManaged,
1658                FrameSink::new(module_tx),
1659            )
1660            .unwrap();
1661        let (client_tx, mut client_rx) = mpsc::channel(1);
1662        let pending = forwarding
1663            .begin_route_bind_relay_for_test(
1664                client_connection,
1665                FrameSink::new(client_tx),
1666                800,
1667                "goodbye-full-provider",
1668            )
1669            .unwrap();
1670        forwarding
1671            .complete_pending_relay(
1672                module_connection,
1673                pending.corr,
1674                RouteBindRelayOutcome::Accepted,
1675            )
1676            .unwrap();
1677
1678        let (module_egress_tx, _module_egress_rx) = mpsc::channel(1);
1679        let module_ctx = RouteCtx {
1680            connection_id: module_connection,
1681            egress: FrameSink::new(module_egress_tx),
1682        };
1683        let goodbye = Frame::build(
1684            FrameType::Goodbye,
1685            Flags::new(false, Priority::Passive, true),
1686            pending.module_channel,
1687            pending.module_epoch,
1688            801,
1689            Vec::new(),
1690        )
1691        .unwrap();
1692
1693        router
1694            .route_for_connection(&module_ctx, goodbye)
1695            .await
1696            .unwrap();
1697        let reason = tokio::time::timeout(Duration::from_secs(1), &mut close_receiver)
1698            .await
1699            .expect("close request should be sent for the full GOODBYE sink")
1700            .expect("close sender should include a reason");
1701        assert!(
1702            reason.to_string().contains("route_goodbye_delivery_failed"),
1703            "unexpected close reason: {reason}"
1704        );
1705        assert_eq!(client_rx.try_recv().unwrap().header.corr, 800);
1706        assert!(client_rx.try_recv().is_err());
1707        assert_eq!(router.counters.snapshot()["goodbye_relay_client_failed"], 1);
1708        assert_eq!(router.counters.snapshot()["route_released_epoch_fenced"], 1);
1709    }
1710
1711    /// One module and one client connection with `routes` routes bound between
1712    /// them. The client uses a real connection egress queue (the same byte
1713    /// budget and frame-count backstop as a live connection), and its route.open
1714    /// responses are drained so the queue starts empty.
1715    async fn multi_route_client(
1716        module_id: &str,
1717        module_connection: ConnectionId,
1718        client_connection: ConnectionId,
1719        routes: usize,
1720    ) -> (
1721        Router,
1722        FrameSink,
1723        mpsc::Receiver<OutboundFrame>,
1724        RouteCtx,
1725        Vec<crate::forwarding::PendingRouteBindRelay>,
1726        ConnectionCloseReceiver,
1727    ) {
1728        let forwarding = Arc::new(ForwardingTable::default());
1729        let control = Arc::new(ControlHandler::with_forwarding(
1730            Arc::new(crate::Registry::default()),
1731            Arc::clone(&forwarding),
1732        ));
1733        let router = Router::with_control_handler(control);
1734        let close_receiver = forwarding.register_connection_close(client_connection);
1735        let (module_tx, _module_rx) = mpsc::channel(8);
1736        forwarding
1737            .register_module_connection(
1738                module_connection,
1739                module_id.to_string(),
1740                1,
1741                Concurrency::ModuleManaged,
1742                FrameSink::new(module_tx),
1743            )
1744            .unwrap();
1745        let (client_sink, mut client_rx) = crate::server::connection_egress();
1746        let mut bound = Vec::with_capacity(routes);
1747        for index in 0..routes {
1748            let pending = forwarding
1749                .begin_route_bind_relay_for_test(
1750                    client_connection,
1751                    client_sink.clone(),
1752                    900 + index as u64,
1753                    module_id,
1754                )
1755                .unwrap();
1756            forwarding
1757                .complete_pending_relay(
1758                    module_connection,
1759                    pending.corr,
1760                    RouteBindRelayOutcome::Accepted,
1761                )
1762                .unwrap();
1763            assert_eq!(
1764                client_rx.recv().await.unwrap().header.corr,
1765                900 + index as u64
1766            );
1767            bound.push(pending);
1768        }
1769        let (module_egress_tx, _module_egress_rx) = mpsc::channel(8);
1770        let module_ctx = RouteCtx {
1771            connection_id: module_connection,
1772            egress: FrameSink::new(module_egress_tx),
1773        };
1774        (
1775            router,
1776            client_sink,
1777            client_rx,
1778            module_ctx,
1779            bound,
1780            close_receiver,
1781        )
1782    }
1783
1784    /// Per-frame cost of the egress sink's admission and release accounting:
1785    /// one million 200-byte frames enqueued with `try_send` and taken off the
1786    /// queue the way the connection writer does, in batches of 1,000 so the
1787    /// queue stays well inside its budget. Prints nanoseconds per frame; it is
1788    /// a measurement, not a gate. Run with
1789    /// `cargo test --release -p subc-daemon --lib egress_sink_per_frame_cost -- --ignored --nocapture`.
1790    #[test]
1791    #[ignore = "timing measurement, run on demand"]
1792    fn egress_sink_per_frame_cost() {
1793        const FRAMES: usize = 1_000_000;
1794        const BATCH: usize = 1_000;
1795        let (sink, mut rx) = crate::server::connection_egress();
1796        let template = stream_frame(9, 1, 0, vec![b't'; 200]);
1797        let started = Instant::now();
1798        for _ in 0..FRAMES / BATCH {
1799            for _ in 0..BATCH {
1800                sink.try_send(template.clone()).unwrap();
1801            }
1802            for _ in 0..BATCH {
1803                let outbound = rx.try_recv().unwrap();
1804                if let Some(charge) = &outbound.charge {
1805                    charge.taken_by_writer();
1806                }
1807                drop(std::hint::black_box(outbound));
1808            }
1809        }
1810        let elapsed = started.elapsed();
1811        assert_eq!(sink.backlog().queued_bytes, 0);
1812        println!(
1813            "egress sink: {FRAMES} frames in {elapsed:?}, {:.1} ns/frame",
1814            elapsed.as_nanos() as f64 / FRAMES as f64
1815        );
1816    }
1817
1818    fn stream_frame(channel: u16, epoch: u32, corr: u64, body: Vec<u8>) -> Frame {
1819        Frame::build(
1820            FrameType::StreamData,
1821            Flags::new(false, Priority::Interactive, false),
1822            channel,
1823            epoch,
1824            corr,
1825            body,
1826        )
1827        .unwrap()
1828    }
1829
1830    /// An awaited send (a control or error reply) is never refused by the byte
1831    /// budget: it parks until the writer frees bytes. If a release could miss a
1832    /// parked sender, that reply would hang for the connection's lifetime, so
1833    /// this pins the wakeup: the send stays parked while the queue is full and
1834    /// completes as soon as one frame leaves it.
1835    #[tokio::test]
1836    async fn awaited_send_parked_behind_a_full_byte_budget_wakes_when_bytes_free() {
1837        let (tx, mut rx) = mpsc::channel(1024);
1838        let sink = FrameSink::with_byte_budget(tx, 2_000);
1839        let mut queued = 0u64;
1840        while sink
1841            .try_send(stream_frame(7, 1, queued, vec![b'x'; 200]))
1842            .is_ok()
1843        {
1844            queued += 1;
1845        }
1846        assert!(queued > 0, "the budget admitted nothing");
1847
1848        let parked = tokio::spawn({
1849            let sink = sink.clone();
1850            async move { sink.send(stream_frame(0, 0, 999, vec![b'r'; 200])).await }
1851        });
1852        tokio::time::sleep(Duration::from_millis(100)).await;
1853        assert!(
1854            !parked.is_finished(),
1855            "the awaited send must wait while the byte budget is full"
1856        );
1857
1858        // The writer takes one frame and drops it, releasing its bytes.
1859        drop(rx.recv().await.expect("a queued frame"));
1860        tokio::time::timeout(Duration::from_secs(2), parked)
1861            .await
1862            .expect("the parked send was never woken after bytes were freed")
1863            .unwrap()
1864            .unwrap();
1865    }
1866
1867    /// A client multiplexing several token streams pauses its reader while the
1868    /// module keeps producing small frames. Far more frames than the old
1869    /// 64-frame queue allowed, but far fewer bytes than the budget, must all be
1870    /// held without closing the connection and delivered in order afterwards.
1871    #[tokio::test]
1872    async fn paused_client_reader_keeps_connection_through_small_frame_burst() {
1873        const ROUTES: usize = 4;
1874        const FRAMES_PER_ROUTE: usize = 250;
1875        let (router, client_sink, mut client_rx, module_ctx, routes, mut close_receiver) =
1876            multi_route_client(
1877                "burst-provider",
1878                ConnectionId::new(60),
1879                ConnectionId::new(61),
1880                ROUTES,
1881            )
1882            .await;
1883
1884        // The reader is paused: nothing is received until every frame is sent.
1885        for seq in 0..FRAMES_PER_ROUTE as u64 {
1886            for (index, route) in routes.iter().enumerate() {
1887                let body = format!("route-{index}-token-{seq:05}-{}", "t".repeat(170));
1888                router
1889                    .route_for_connection(
1890                        &module_ctx,
1891                        stream_frame(
1892                            route.module_channel,
1893                            route.module_epoch,
1894                            seq,
1895                            body.into_bytes(),
1896                        ),
1897                    )
1898                    .await
1899                    .unwrap();
1900            }
1901        }
1902
1903        let backlog = client_sink.backlog();
1904        assert_eq!(backlog.queued_frames, ROUTES * FRAMES_PER_ROUTE);
1905        assert!(backlog.queued_bytes < crate::server::CONNECTION_EGRESS_BYTE_BUDGET);
1906        assert!(
1907            close_receiver.try_recv().is_err(),
1908            "a paused reader under the byte budget must not be closed"
1909        );
1910        assert_eq!(
1911            router.counters.snapshot()["client_egress_close_delivery_failed"],
1912            0
1913        );
1914
1915        // The reader resumes: every frame arrives, in order within each route.
1916        let mut next_seq = vec![0u64; ROUTES];
1917        for _ in 0..ROUTES * FRAMES_PER_ROUTE {
1918            let frame = client_rx.try_recv().expect("every queued frame arrives");
1919            let index = routes
1920                .iter()
1921                .position(|route| route.client_channel == frame.header.channel)
1922                .expect("frame arrives on one of the bound client channels");
1923            assert_eq!(frame.header.corr, next_seq[index], "route {index} order");
1924            let expected_prefix = format!("route-{index}-token-{:05}-", next_seq[index]);
1925            assert!(frame.body.starts_with(expected_prefix.as_bytes()));
1926            next_seq[index] += 1;
1927        }
1928        assert!(client_rx.try_recv().is_err());
1929        assert_eq!(next_seq, vec![FRAMES_PER_ROUTE as u64; ROUTES]);
1930        assert_eq!(client_sink.backlog().queued_bytes, 0);
1931    }
1932
1933    /// A client that never reads is closed once the module's frames exceed
1934    /// the byte budget, and that close is reported once at WARN with what an
1935    /// operator needs to find the stuck reader.
1936    #[tokio::test]
1937    async fn never_reading_client_is_closed_at_byte_budget_with_warn_diagnosis() {
1938        let (logs, _guard) = test_log::log_capture(tracing::Level::WARN);
1939        const BODY: usize = 16 * 1024;
1940        let (router, client_sink, _client_rx, module_ctx, routes, mut close_receiver) =
1941            multi_route_client(
1942                "stuck-reader-provider",
1943                ConnectionId::new(70),
1944                ConnectionId::new(71),
1945                2,
1946            )
1947            .await;
1948
1949        let mut admitted = 0usize;
1950        let mut sent = 0u64;
1951        while router.counters.snapshot()["client_egress_close_delivery_failed"] == 0 {
1952            assert!(sent < 1_000, "the byte budget never refused a frame");
1953            // Other tests running in parallel hit the same WARN call site with
1954            // no subscriber installed; if one of them registers that call site
1955            // while this test's capture subscriber is being installed, tracing
1956            // can cache the call site as disabled. Recomputing the cache just
1957            // before each frame that may trigger the WARN keeps the capture
1958            // from silently missing it.
1959            tracing::callsite::rebuild_interest_cache();
1960            let route = &routes[(sent % 2) as usize];
1961            router
1962                .route_for_connection(
1963                    &module_ctx,
1964                    stream_frame(
1965                        route.module_channel,
1966                        route.module_epoch,
1967                        sent,
1968                        vec![b'z'; BODY],
1969                    ),
1970                )
1971                .await
1972                .unwrap();
1973            sent += 1;
1974            admitted = client_sink.backlog().queued_frames;
1975        }
1976        // The budget, not the frame-count backstop, did the refusing.
1977        let frame_bytes = subc_protocol::HEADER_LEN + BODY;
1978        assert_eq!(
1979            admitted,
1980            crate::server::CONNECTION_EGRESS_BYTE_BUDGET / frame_bytes
1981        );
1982        let reason = close_receiver
1983            .try_recv()
1984            .expect("the client connection must be asked to close");
1985        assert!(reason
1986            .to_string()
1987            .contains("module_to_client_delivery_failed"));
1988
1989        // A second refused frame for the same connection adds no second WARN.
1990        router
1991            .route_for_connection(
1992                &module_ctx,
1993                stream_frame(
1994                    routes[0].module_channel,
1995                    routes[0].module_epoch,
1996                    sent,
1997                    vec![b'z'; BODY],
1998                ),
1999            )
2000            .await
2001            .unwrap();
2002
2003        let captured = test_log::captured_logs(&logs);
2004        let warn_lines = captured
2005            .lines()
2006            .filter(|line| {
2007                line.contains("closing client connection: its egress queue could not take a frame")
2008            })
2009            .collect::<Vec<_>>();
2010        assert_eq!(warn_lines.len(), 1, "exactly one WARN, got: {captured}");
2011        let line = warn_lines[0];
2012        assert!(line.contains("WARN"), "{line}");
2013        assert!(line.contains("connection_id=71"), "{line}");
2014        assert!(
2015            line.contains("module_id=\"stuck-reader-provider\""),
2016            "{line}"
2017        );
2018        assert!(line.contains("client_channel="), "{line}");
2019        assert!(line.contains("principals=direct"), "{line}");
2020        let queued_bytes: usize = line
2021            .split("queued_bytes=")
2022            .nth(1)
2023            .and_then(|rest| rest.split_whitespace().next())
2024            .and_then(|value| value.parse().ok())
2025            .expect("queued_bytes is logged");
2026        assert_eq!(queued_bytes, admitted * frame_bytes);
2027        assert!(
2028            line.contains(&format!("queued_frames={admitted}")),
2029            "{line}"
2030        );
2031        assert!(line.contains("oldest_queued_ms="), "{line}");
2032    }
2033
2034    fn route_frame(ty: FrameType, channel: u16, epoch: u32, corr: u64) -> Frame {
2035        Frame::build(
2036            ty,
2037            Flags::new(false, Priority::Interactive, false),
2038            channel,
2039            epoch,
2040            corr,
2041            if ty == FrameType::Request || ty == FrameType::Response {
2042                b"route-body".to_vec()
2043            } else {
2044                Vec::new()
2045            },
2046        )
2047        .unwrap()
2048    }
2049
2050    type DynamicRouteFixture = (
2051        Router,
2052        Arc<ForwardingTable>,
2053        RouteCtx,
2054        mpsc::Receiver<crate::router::OutboundFrame>,
2055        RouteCtx,
2056        mpsc::Receiver<crate::router::OutboundFrame>,
2057        mpsc::Receiver<crate::router::OutboundFrame>,
2058        crate::forwarding::PendingRouteBindRelay,
2059    );
2060
2061    fn dynamic_route_fixture(commit: bool) -> DynamicRouteFixture {
2062        let forwarding = Arc::new(ForwardingTable::default());
2063        let control = Arc::new(crate::ControlHandler::with_forwarding(
2064            Arc::new(crate::Registry::default()),
2065            Arc::clone(&forwarding),
2066        ));
2067        let router = Router::with_control_handler(control);
2068        let module_connection = ConnectionId::new(500);
2069        let client_connection = ConnectionId::new(501);
2070        let (module_tx, module_rx) = mpsc::channel(8);
2071        forwarding
2072            .register_module_connection(
2073                module_connection,
2074                "epoch-router".into(),
2075                2,
2076                Concurrency::ModuleManaged,
2077                FrameSink::new(module_tx),
2078            )
2079            .unwrap();
2080        let (client_tx, client_rx) = mpsc::channel(8);
2081        let client_sink = FrameSink::new(client_tx);
2082        let pending = forwarding
2083            .begin_route_bind_relay_for_test(
2084                client_connection,
2085                client_sink.clone(),
2086                700,
2087                "epoch-router",
2088            )
2089            .unwrap();
2090        if commit {
2091            forwarding
2092                .complete_pending_relay(
2093                    module_connection,
2094                    pending.corr,
2095                    RouteBindRelayOutcome::Accepted,
2096                )
2097                .unwrap();
2098        }
2099        let (module_egress_tx, module_egress_rx) = mpsc::channel(8);
2100        (
2101            router,
2102            forwarding,
2103            RouteCtx {
2104                connection_id: client_connection,
2105                egress: client_sink,
2106            },
2107            client_rx,
2108            RouteCtx {
2109                connection_id: module_connection,
2110                egress: FrameSink::new(module_egress_tx),
2111            },
2112            module_egress_rx,
2113            module_rx,
2114            pending,
2115        )
2116    }
2117
2118    #[tokio::test]
2119    async fn route_epochs_validate_both_directions_and_rewrite_to_peer_handle() {
2120        let (
2121            router,
2122            _forwarding,
2123            client_ctx,
2124            mut client_rx,
2125            module_ctx,
2126            _module_egress_rx,
2127            mut module_rx,
2128            pending,
2129        ) = dynamic_route_fixture(true);
2130        let route_open = client_rx.recv().await.unwrap();
2131        assert_eq!(route_open.header.corr, 700);
2132
2133        router
2134            .route_for_connection(
2135                &client_ctx,
2136                route_frame(
2137                    FrameType::Request,
2138                    pending.client_channel,
2139                    pending.client_epoch,
2140                    701,
2141                ),
2142            )
2143            .await
2144            .unwrap();
2145        let forwarded = module_rx.recv().await.unwrap();
2146        assert_eq!(forwarded.header.channel, pending.module_channel);
2147        assert_eq!(forwarded.header.epoch, pending.module_epoch);
2148
2149        router
2150            .route_for_connection(
2151                &module_ctx,
2152                route_frame(
2153                    FrameType::Response,
2154                    pending.module_channel,
2155                    pending.module_epoch,
2156                    701,
2157                ),
2158            )
2159            .await
2160            .unwrap();
2161        let delivered = client_rx.recv().await.unwrap();
2162        assert_eq!(delivered.header.channel, pending.client_channel);
2163        assert_eq!(delivered.header.epoch, pending.client_epoch);
2164
2165        router
2166            .route_for_connection(
2167                &client_ctx,
2168                route_frame(
2169                    FrameType::Request,
2170                    pending.client_channel,
2171                    pending.client_epoch + 1,
2172                    702,
2173                ),
2174            )
2175            .await
2176            .unwrap();
2177        router
2178            .route_for_connection(
2179                &module_ctx,
2180                route_frame(
2181                    FrameType::Response,
2182                    pending.module_channel,
2183                    pending.module_epoch + 1,
2184                    703,
2185                ),
2186            )
2187            .await
2188            .unwrap();
2189        let stale_error = client_rx.recv().await.unwrap();
2190        assert_eq!(stale_error.header.ty, FrameType::Error);
2191        assert_eq!(stale_error.header.channel, pending.client_channel);
2192        assert_eq!(stale_error.header.epoch, pending.client_epoch + 1);
2193        assert_eq!(stale_error.header.corr, 702);
2194        let body: ErrorBody = serde_json::from_slice(&stale_error.body).unwrap();
2195        assert_eq!(body.code, "stale_route_epoch");
2196        assert!(module_rx.try_recv().is_err());
2197        assert!(client_rx.try_recv().is_err());
2198        let counters = router.counters.snapshot();
2199        assert_eq!(counters["client_frames_dropped_stale_route"], 1);
2200        assert_eq!(counters["module_frames_dropped_no_route"], 1);
2201    }
2202
2203    #[tokio::test]
2204    async fn accepted_route_publishes_route_open_before_immediate_reverse_request() {
2205        let (
2206            router,
2207            _,
2208            _client_ctx,
2209            mut client_rx,
2210            module_ctx,
2211            _module_egress_rx,
2212            _module_rx,
2213            pending,
2214        ) = dynamic_route_fixture(true);
2215        router
2216            .route_for_connection(
2217                &module_ctx,
2218                route_frame(
2219                    FrameType::Request,
2220                    pending.module_channel,
2221                    pending.module_epoch,
2222                    800,
2223                ),
2224            )
2225            .await
2226            .unwrap();
2227
2228        let first = client_rx.recv().await.unwrap();
2229        let second = client_rx.recv().await.unwrap();
2230        assert_eq!(first.header.channel, 0);
2231        assert_eq!(first.header.corr, 700);
2232        assert_eq!(second.header.channel, pending.client_channel);
2233        assert_eq!(second.header.epoch, pending.client_epoch);
2234        assert_eq!(second.header.corr, 800);
2235    }
2236
2237    #[tokio::test]
2238    async fn reserved_slot_ingress_errors_only_matching_client_requests() {
2239        let (
2240            router,
2241            _forwarding,
2242            client_ctx,
2243            mut client_rx,
2244            _module_ctx,
2245            _module_egress_rx,
2246            mut module_rx,
2247            pending,
2248        ) = dynamic_route_fixture(false);
2249        router
2250            .route_for_connection(
2251                &client_ctx,
2252                route_frame(
2253                    FrameType::Request,
2254                    pending.client_channel,
2255                    pending.client_epoch,
2256                    900,
2257                ),
2258            )
2259            .await
2260            .unwrap();
2261        let error = client_rx.recv().await.unwrap();
2262        assert_eq!(error.header.ty, FrameType::Error);
2263        assert_eq!(error.header.channel, pending.client_channel);
2264        assert_eq!(error.header.epoch, pending.client_epoch);
2265        assert_eq!(error.header.corr, 900);
2266
2267        router
2268            .route_for_connection(
2269                &client_ctx,
2270                route_frame(
2271                    FrameType::Response,
2272                    pending.client_channel,
2273                    pending.client_epoch,
2274                    901,
2275                ),
2276            )
2277            .await
2278            .unwrap();
2279        router
2280            .route_for_connection(
2281                &client_ctx,
2282                route_frame(
2283                    FrameType::Request,
2284                    pending.client_channel,
2285                    pending.client_epoch + 1,
2286                    902,
2287                ),
2288            )
2289            .await
2290            .unwrap();
2291        let stale_error = client_rx.recv().await.unwrap();
2292        assert_eq!(stale_error.header.ty, FrameType::Error);
2293        assert_eq!(stale_error.header.channel, pending.client_channel);
2294        assert_eq!(stale_error.header.epoch, pending.client_epoch + 1);
2295        assert_eq!(stale_error.header.corr, 902);
2296        let body: ErrorBody = serde_json::from_slice(&stale_error.body).unwrap();
2297        assert_eq!(body.code, "stale_route_epoch");
2298        assert!(module_rx.try_recv().is_err());
2299        let counters = router.counters.snapshot();
2300        assert_eq!(counters["client_frames_dropped_stale_route"], 1);
2301        assert_eq!(counters["module_frames_dropped_no_route"], 0);
2302    }
2303
2304    #[tokio::test]
2305    async fn dropped_module_route_goodbye_increments_counter() {
2306        let (
2307            router,
2308            _forwarding,
2309            client_ctx,
2310            mut client_rx,
2311            _module_ctx,
2312            _module_egress_rx,
2313            mut module_rx,
2314            pending,
2315        ) = dynamic_route_fixture(true);
2316        let _ = client_rx.recv().await;
2317        module_rx.close();
2318
2319        router
2320            .route_for_connection(
2321                &client_ctx,
2322                route_frame(
2323                    FrameType::Goodbye,
2324                    pending.client_channel,
2325                    pending.client_epoch,
2326                    999,
2327                ),
2328            )
2329            .await
2330            .unwrap();
2331
2332        let counters = router.counters.snapshot();
2333        assert_eq!(counters["goodbye_relay_module_dropped"], 1);
2334        assert_eq!(
2335            counters["goodbye_relay_module_dropped_by_module"],
2336            serde_json::json!({ "epoch-router": 1 })
2337        );
2338        assert_eq!(counters["route_released_epoch_fenced"], 1);
2339    }
2340
2341    #[tokio::test]
2342    async fn module_request_on_stale_epoch_receives_stale_route_epoch() {
2343        let (
2344            router,
2345            _forwarding,
2346            _client_ctx,
2347            _client_rx,
2348            module_ctx,
2349            mut module_egress_rx,
2350            mut module_rx,
2351            pending,
2352        ) = dynamic_route_fixture(true);
2353
2354        router
2355            .route_for_connection(
2356                &module_ctx,
2357                route_frame(
2358                    FrameType::Request,
2359                    pending.module_channel,
2360                    pending.module_epoch + 1,
2361                    1_000,
2362                ),
2363            )
2364            .await
2365            .unwrap();
2366
2367        let error = module_egress_rx.try_recv().unwrap();
2368        assert_eq!(error.header.ty, FrameType::Error);
2369        assert_eq!(error.header.channel, pending.module_channel);
2370        assert_eq!(error.header.epoch, pending.module_epoch + 1);
2371        assert_eq!(error.header.corr, 1_000);
2372        let body: ErrorBody = serde_json::from_slice(&error.body).unwrap();
2373        assert_eq!(body.code, "stale_route_epoch");
2374        assert!(module_rx.try_recv().is_err());
2375        let counters = router.counters.snapshot();
2376        assert_eq!(counters["module_requests_dropped_stale_route"], 1);
2377        assert_eq!(counters["module_frames_dropped_no_route"], 0);
2378    }
2379
2380    #[tokio::test]
2381    async fn module_request_on_reserved_or_absent_route_receives_unknown_channel() {
2382        let (
2383            reserved_router,
2384            _forwarding,
2385            _client_ctx,
2386            _client_rx,
2387            reserved_module_ctx,
2388            mut reserved_module_egress_rx,
2389            _module_rx,
2390            reserved,
2391        ) = dynamic_route_fixture(false);
2392        reserved_router
2393            .route_for_connection(
2394                &reserved_module_ctx,
2395                route_frame(
2396                    FrameType::Request,
2397                    reserved.module_channel,
2398                    reserved.module_epoch,
2399                    1_001,
2400                ),
2401            )
2402            .await
2403            .unwrap();
2404        let reserved_error = reserved_module_egress_rx.try_recv().unwrap();
2405        let reserved_body: ErrorBody = serde_json::from_slice(&reserved_error.body).unwrap();
2406        assert_eq!(reserved_error.header.ty, FrameType::Error);
2407        assert_eq!(reserved_error.header.channel, reserved.module_channel);
2408        assert_eq!(reserved_error.header.epoch, reserved.module_epoch);
2409        assert_eq!(reserved_error.header.corr, 1_001);
2410        assert_eq!(reserved_body.code, "unknown_channel");
2411        assert_eq!(
2412            reserved_router.counters.snapshot()["module_requests_dropped_stale_route"],
2413            1
2414        );
2415
2416        let (
2417            absent_router,
2418            _forwarding,
2419            _client_ctx,
2420            _client_rx,
2421            absent_module_ctx,
2422            mut absent_module_egress_rx,
2423            _module_rx,
2424            absent,
2425        ) = dynamic_route_fixture(false);
2426        absent_router
2427            .route_for_connection(
2428                &absent_module_ctx,
2429                route_frame(
2430                    FrameType::Request,
2431                    absent.module_channel + 1,
2432                    absent.module_epoch,
2433                    1_002,
2434                ),
2435            )
2436            .await
2437            .unwrap();
2438        let absent_error = absent_module_egress_rx.try_recv().unwrap();
2439        let absent_body: ErrorBody = serde_json::from_slice(&absent_error.body).unwrap();
2440        assert_eq!(absent_error.header.ty, FrameType::Error);
2441        assert_eq!(absent_error.header.channel, absent.module_channel + 1);
2442        assert_eq!(absent_error.header.epoch, absent.module_epoch);
2443        assert_eq!(absent_error.header.corr, 1_002);
2444        assert_eq!(absent_body.code, "unknown_channel");
2445        assert_eq!(
2446            absent_router.counters.snapshot()["module_requests_dropped_stale_route"],
2447            1
2448        );
2449    }
2450
2451    #[tokio::test]
2452    async fn non_request_module_frame_on_dead_route_is_counted_without_error() {
2453        let (
2454            router,
2455            forwarding,
2456            client_ctx,
2457            mut client_rx,
2458            module_ctx,
2459            mut module_egress_rx,
2460            mut module_rx,
2461            pending,
2462        ) = dynamic_route_fixture(true);
2463        let (other_module_tx, _other_module_rx) = mpsc::channel(8);
2464        forwarding
2465            .register_module_connection(
2466                ConnectionId::new(502),
2467                "other-module".into(),
2468                2,
2469                Concurrency::ModuleManaged,
2470                FrameSink::new(other_module_tx),
2471            )
2472            .unwrap();
2473        let _ = client_rx.recv().await.unwrap();
2474
2475        router
2476            .route_for_connection(
2477                &client_ctx,
2478                route_frame(
2479                    FrameType::Goodbye,
2480                    pending.client_channel,
2481                    pending.client_epoch,
2482                    1_003,
2483                ),
2484            )
2485            .await
2486            .unwrap();
2487        let _ = module_rx.recv().await.unwrap();
2488
2489        router
2490            .route_for_connection(
2491                &module_ctx,
2492                route_frame(
2493                    FrameType::StreamData,
2494                    pending.module_channel,
2495                    pending.module_epoch,
2496                    1_004,
2497                ),
2498            )
2499            .await
2500            .unwrap();
2501
2502        assert!(module_egress_rx.try_recv().is_err());
2503        let counters = router.counters.snapshot();
2504        assert_eq!(counters["module_frames_dropped_no_route"], 1);
2505        assert_eq!(
2506            counters["module_frames_dropped_no_route_by_module"],
2507            serde_json::json!({ "epoch-router": 1 })
2508        );
2509        assert_eq!(counters["module_requests_dropped_stale_route"], 0);
2510    }
2511
2512    #[test]
2513    fn channel_zero_cannot_be_registered_as_backend() {
2514        let mut router = Router::with_default_self_handler();
2515
2516        let err = router.register_backend(0, EchoBackend).unwrap_err();
2517
2518        assert_eq!(err, RouterError::ReservedChannelZero);
2519    }
2520}