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