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