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