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