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