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