1use std::{
2 collections::{BTreeMap, HashMap, HashSet},
3 error::Error,
4 fmt,
5 sync::{Arc, Mutex, MutexGuard, RwLock, RwLockReadGuard, RwLockWriteGuard},
6 time::Duration,
7};
8
9use subc_control::{ClientControlResponse, RouteCloseReason};
10use subc_protocol::{
11 manifest::Concurrency,
12 session::{LiveRoot, ModuleControlResponse, ModuleControlResponseToModule},
13 ErrorBody, Flags, FrameType, Principal, Priority,
14};
15use tokio::sync::{mpsc, oneshot, Semaphore};
16use tokio::time::Instant;
17use tracing::{debug, info, warn};
18
19use crate::{
20 control::{RouteBindBreakers, RouteBindConcurrency},
21 observability::DaemonCounters,
22 registry::ConnectionId,
23 router::FrameSink,
24 Frame, ProjectRootId,
25};
26
27const DEFAULT_MODULE_MANAGED_WINDOW: usize = 32;
29
30const STATELESS_PARALLEL_WINDOW: usize = 1024;
32
33const HEALTH_PROBE_TOMBSTONE_TTL: Duration = Duration::from_secs(5 * 60);
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
43pub struct ModuleEndpointId {
44 pub connection_id: ConnectionId,
45 pub generation: u64,
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
50pub(crate) struct ClientRouteKey {
51 pub connection_id: ConnectionId,
52 pub channel: u16,
53}
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
57pub(crate) struct ModuleRouteKey {
58 pub endpoint: ModuleEndpointId,
59 pub channel: u16,
60}
61
62#[derive(Debug)]
63pub(crate) struct RouteBinding {
64 pub client_connection_id: ConnectionId,
65 pub client_sink: FrameSink,
66 pub client_negotiated_ver: u8,
67 pub client_channel: u16,
68 pub client_epoch: u32,
69 pub module_id: String,
70 pub module_endpoint: ModuleEndpointId,
71 pub module_sink: FrameSink,
72 pub module_negotiated_ver: u8,
73 pub module_channel: u16,
74 pub module_epoch: u32,
75 pub principal: Principal,
76 pub project_root: Option<ProjectRootId>,
77 pub bound_at: Instant,
78 pub flow: Arc<ChannelFlow>,
79}
80
81#[derive(Debug, Clone)]
82pub(crate) enum DataRoute {
83 Client(DataRouteState),
84 Module(DataRouteState),
85}
86
87#[derive(Debug, Clone)]
88pub(crate) enum DataRouteState {
89 Bound(Arc<RouteBinding>),
90 Reserved,
91 EpochMismatch,
92 Absent,
93}
94
95#[derive(Debug, Clone, Copy, PartialEq, Eq)]
122pub(crate) enum GoodbyeTargetKind {
123 Client,
124 Module,
125}
126
127#[derive(Debug, Clone)]
128pub(crate) struct GoodbyeTarget {
129 pub connection_id: ConnectionId,
130 pub sink: FrameSink,
131 pub negotiated_ver: u8,
132 pub channel: u16,
133 pub epoch: u32,
134 pub kind: GoodbyeTargetKind,
135 pub module_id: Option<String>,
138}
139
140impl GoodbyeTarget {
141 pub(crate) fn close_on_delivery_failure(&self) -> bool {
144 matches!(self.kind, GoodbyeTargetKind::Client)
145 }
146}
147
148#[derive(Debug, Clone)]
154pub(crate) struct EndpointRoute {
155 pub goodbye_target: GoodbyeTarget,
156 pub principal: Principal,
157 pub bound_at: Instant,
158 pub draining: bool,
159 pub drain_reason: Option<RouteCloseReason>,
163}
164
165#[derive(Debug)]
166pub(crate) struct PendingRouteBindRelay {
167 pub endpoint: ModuleEndpointId,
168 pub module_sink: FrameSink,
169 pub negotiated_ver: u8,
170 pub client_channel: u16,
171 pub client_epoch: u32,
172 pub module_channel: u16,
173 pub module_epoch: u32,
174 pub corr: u64,
175 pub receiver: oneshot::Receiver<RouteBindRelayOutcome>,
176}
177
178#[derive(Debug, Clone)]
179pub(crate) struct ModuleDrainTarget {
180 pub endpoint: ModuleEndpointId,
181 pub sink: FrameSink,
182 pub negotiated_ver: u8,
183 pub abandoned_bindings: Vec<GoodbyeTarget>,
184 pub excluded_subscriptions: u32,
185}
186
187#[derive(Debug, Clone)]
188pub(crate) enum RouteBindRelayOutcome {
189 Accepted,
190 Rejected(ErrorBody),
191 ModuleGone(String),
192}
193
194#[derive(Debug, Clone, Copy, PartialEq, Eq)]
196pub(crate) struct ForwardingCutover {
197 pub promoted: ModuleEndpointId,
199 pub incumbent: Option<ModuleEndpointId>,
202}
203
204#[derive(Debug, Clone)]
205pub(crate) struct PendingRelayCompletion {
206 pub settled: bool,
207 pub abandoned: Option<GoodbyeTarget>,
208}
209
210#[derive(Debug)]
211pub(crate) struct PendingModuleControlRpc {
212 pub endpoint: ModuleEndpointId,
213 pub module_sink: FrameSink,
214 pub negotiated_ver: u8,
215 pub corr: u64,
216 pub receiver: oneshot::Receiver<ModuleControlRpcOutcome>,
217}
218
219#[derive(Debug, Clone)]
220pub(crate) enum ModuleControlRpcOutcome {
221 Response(ModuleControlResponse),
222 Rejected(ErrorBody),
223 ModuleGone(String),
224 MalformedResponse(String),
225 UnexpectedOp { expected: String, actual: String },
226 DeadlineElapsed,
227}
228
229#[derive(Debug, Clone, PartialEq, Eq)]
230pub(crate) enum ModuleControlRpcCompletion {
231 Unknown,
232 Settled,
233 LateHealthAnswer {
234 module_id: String,
235 latency: Duration,
236 },
237}
238
239#[derive(Debug)]
240struct PendingModuleControlRpcEntry {
241 expected_op: String,
242 deadline: Instant,
243 health_probe_started_at: Option<Instant>,
244 sender: oneshot::Sender<ModuleControlRpcOutcome>,
245}
246
247#[derive(Debug)]
248struct HealthProbeTombstone {
249 expected_op: String,
250 module_id: String,
251 probe_started_at: Instant,
252 expires_at: Instant,
253}
254
255#[derive(Debug, Clone)]
256struct RouteReservation {
257 client_key: ClientRouteKey,
258 module_key: ModuleRouteKey,
259 client_epoch: u32,
260 module_epoch: u32,
261 project_root: Option<ProjectRootId>,
262}
263
264#[derive(Debug)]
265struct PendingRouteBindRelayEntry {
266 reservation: RouteReservation,
267 client_sink: FrameSink,
268 client_negotiated_ver: u8,
269 client_permit: mpsc::OwnedPermit<crate::router::OutboundFrame>,
270 route_open_frame: Frame,
271 principal: Principal,
272 deadline: Instant,
273 relay_enqueued: bool,
274 sender: oneshot::Sender<RouteBindRelayOutcome>,
275}
276
277#[derive(Debug, Clone)]
278pub(crate) enum RouteRelease {
279 Removed(GoodbyeTarget),
280 Stale,
281 Absent,
282}
283
284#[derive(Debug, Clone)]
285pub(crate) enum RoutePollSnapshot {
286 Bound {
287 module_id: String,
288 status: Option<String>,
289 },
290 Absent,
291}
292
293#[derive(Debug, Clone)]
294struct ModuleConnection {
295 endpoint: ModuleEndpointId,
296 sink: FrameSink,
297 negotiated_ver: u8,
298 concurrency: Concurrency,
299}
300
301#[derive(Debug, Default)]
302struct ForwardingInner {
303 daemon_draining: bool,
304 modules_by_id: HashMap<String, ModuleConnection>,
308 candidates_by_id: HashMap<String, ModuleConnection>,
314 superseded_endpoints: HashMap<ModuleEndpointId, ModuleConnection>,
320 endpoint_by_connection: HashMap<ConnectionId, ModuleEndpointId>,
321 module_id_by_endpoint: HashMap<ModuleEndpointId, String>,
322 draining_endpoints: HashMap<ModuleEndpointId, RouteCloseReason>,
326 closing_connections: HashSet<ConnectionId>,
327 next_generation: u64,
328 reserved_client: HashMap<ClientRouteKey, ModuleRouteKey>,
329 reserved_module: HashMap<ModuleRouteKey, ClientRouteKey>,
330 next_client_channel: HashMap<ConnectionId, u16>,
331 next_module_channel: HashMap<ModuleEndpointId, u16>,
332 client_slot_epochs: HashMap<ClientRouteKey, u32>,
333 module_slot_epochs: HashMap<ModuleRouteKey, u32>,
334 last_published_epoch: HashMap<ClientRouteKey, u32>,
335 client_to_module: HashMap<ClientRouteKey, Arc<RouteBinding>>,
336 module_to_client: HashMap<ModuleRouteKey, Arc<RouteBinding>>,
337 status: HashMap<(ClientRouteKey, u32), String>,
338 pending_relays: HashMap<(ModuleEndpointId, u64), PendingRouteBindRelayEntry>,
339 next_control_corr: HashMap<ModuleEndpointId, u64>,
340 pending_control_rpcs: HashMap<(ModuleEndpointId, u64), PendingModuleControlRpcEntry>,
341 health_probe_tombstones: HashMap<(ModuleEndpointId, u64), HealthProbeTombstone>,
342}
343
344#[derive(Debug, Clone)]
345pub(crate) struct CloseReason {
346 code: &'static str,
347 message: String,
348}
349
350impl CloseReason {
351 pub(crate) fn new(code: &'static str, message: impl Into<String>) -> Self {
352 Self {
353 code,
354 message: message.into(),
355 }
356 }
357}
358
359impl fmt::Display for CloseReason {
360 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
361 write!(f, "{}: {}", self.code, self.message)
362 }
363}
364
365pub(crate) type ConnectionCloseReceiver = oneshot::Receiver<CloseReason>;
366
367#[derive(Debug, Default)]
369pub struct ForwardingTable {
370 inner: Arc<RwLock<ForwardingInner>>,
371 close_registry: Mutex<HashMap<ConnectionId, oneshot::Sender<CloseReason>>>,
372 counters: DaemonCounters,
373 route_bind_breakers: RouteBindBreakers,
378 route_bind_concurrency: RouteBindConcurrency,
381}
382
383impl ForwardingTable {
384 pub(crate) fn counters(&self) -> DaemonCounters {
385 self.counters.clone()
386 }
387
388 pub(crate) fn route_bind_breakers(&self) -> RouteBindBreakers {
389 self.route_bind_breakers.clone()
390 }
391
392 pub(crate) fn route_bind_concurrency(&self) -> RouteBindConcurrency {
393 self.route_bind_concurrency.clone()
394 }
395
396 pub(crate) fn register_connection_close(
397 &self,
398 connection_id: ConnectionId,
399 ) -> ConnectionCloseReceiver {
400 let (sender, receiver) = oneshot::channel();
401 let replaced = self
402 .lock_close_registry()
403 .insert(connection_id, sender)
404 .is_some();
405 if replaced {
406 warn!(
407 connection_id = connection_id.get(),
408 "replaced existing connection close registration"
409 );
410 }
411 receiver
412 }
413
414 pub(crate) fn unregister_connection_close(&self, connection_id: ConnectionId) {
415 self.lock_close_registry().remove(&connection_id);
416 }
417
418 #[cfg(unix)]
424 pub(crate) fn close_all_connections(&self, reason: &CloseReason) -> usize {
425 let senders: Vec<_> = self.lock_close_registry().drain().collect();
426 let count = senders.len();
427 for (_, sender) in senders {
428 let _ = sender.send(reason.clone());
429 }
430 count
431 }
432
433 pub(crate) fn request_connection_close(
434 &self,
435 connection_id: ConnectionId,
436 reason: CloseReason,
437 ) {
438 let sender = self.lock_close_registry().remove(&connection_id);
439 if let Some(sender) = sender {
440 debug!(
441 connection_id = connection_id.get(),
442 close_reason = %reason,
443 "requesting connection close"
444 );
445 let _ = sender.send(reason);
446 } else {
447 debug!(
448 connection_id = connection_id.get(),
449 close_reason = %reason,
450 "connection close request ignored for inactive connection"
451 );
452 }
453 }
454
455 pub fn register_module_connection(
456 &self,
457 connection_id: ConnectionId,
458 module_id: String,
459 negotiated_ver: u8,
460 concurrency: Concurrency,
461 sink: FrameSink,
462 ) -> Result<ModuleEndpointId, ForwardingError> {
463 let mut inner = self.write_inner()?;
464 if inner.daemon_draining || inner.closing_connections.contains(&connection_id) {
465 return Err(ForwardingError::ConnectionClosing { connection_id });
466 }
467 if let Some(old_endpoint) = inner.endpoint_by_connection.remove(&connection_id) {
468 let _ = remove_module_connection_locked(&mut inner, old_endpoint);
469 }
470
471 inner.next_generation = inner.next_generation.checked_add(1).unwrap_or(1);
472 let endpoint = ModuleEndpointId {
473 connection_id,
474 generation: inner.next_generation,
475 };
476 inner.endpoint_by_connection.insert(connection_id, endpoint);
477 inner
478 .module_id_by_endpoint
479 .insert(endpoint, module_id.clone());
480 inner.next_module_channel.insert(endpoint, 1);
481 inner.next_control_corr.insert(endpoint, 1);
482 inner.modules_by_id.insert(
483 module_id.clone(),
484 ModuleConnection {
485 endpoint,
486 sink,
487 negotiated_ver,
488 concurrency,
489 },
490 );
491 drop(inner);
492
493 if let Some(discarded) = self
506 .route_bind_breakers
507 .reset_for_new_module_connection(&module_id)
508 {
509 info!(
510 module_id = %module_id,
511 discarded_consecutive_timeouts = discarded,
512 "route.bind breaker state discarded: a new module connection replaced the process it described"
513 );
514 }
515 Ok(endpoint)
516 }
517
518 pub(crate) fn register_candidate_module_connection(
528 &self,
529 connection_id: ConnectionId,
530 module_id: String,
531 negotiated_ver: u8,
532 concurrency: Concurrency,
533 sink: FrameSink,
534 ) -> Result<ModuleEndpointId, ForwardingError> {
535 let mut inner = self.write_inner()?;
536 if inner.daemon_draining || inner.closing_connections.contains(&connection_id) {
537 return Err(ForwardingError::ConnectionClosing { connection_id });
538 }
539 if inner.candidates_by_id.contains_key(&module_id) {
540 return Err(ForwardingError::CandidateSlotOccupied { module_id });
541 }
542 if let Some(old_endpoint) = inner.endpoint_by_connection.remove(&connection_id) {
543 let _ = remove_module_connection_locked(&mut inner, old_endpoint);
544 }
545
546 inner.next_generation = inner.next_generation.checked_add(1).unwrap_or(1);
547 let endpoint = ModuleEndpointId {
548 connection_id,
549 generation: inner.next_generation,
550 };
551 inner.endpoint_by_connection.insert(connection_id, endpoint);
552 inner
553 .module_id_by_endpoint
554 .insert(endpoint, module_id.clone());
555 inner.next_module_channel.insert(endpoint, 1);
556 inner.next_control_corr.insert(endpoint, 1);
557 inner.candidates_by_id.insert(
558 module_id,
559 ModuleConnection {
560 endpoint,
561 sink,
562 negotiated_ver,
563 concurrency,
564 },
565 );
566 Ok(endpoint)
567 }
568
569 pub(crate) fn cutover_candidate(
587 &self,
588 module_id: &str,
589 ) -> Result<Option<ForwardingCutover>, ForwardingError> {
590 let mut inner = self.write_inner()?;
591 if inner.daemon_draining {
592 return Err(ForwardingError::ModuleReloading {
593 module_id: module_id.to_string(),
594 });
595 }
596 let Some(candidate) = inner.candidates_by_id.remove(module_id) else {
597 return Ok(None);
598 };
599 let promoted = candidate.endpoint;
600 let incumbent = inner.modules_by_id.insert(module_id.to_string(), candidate);
601 let incumbent = incumbent.map(|incumbent| {
602 let endpoint = incumbent.endpoint;
603 inner.superseded_endpoints.insert(endpoint, incumbent);
604 endpoint
605 });
606 drop(inner);
607
608 if let Some(discarded) = self
612 .route_bind_breakers
613 .reset_for_new_module_connection(module_id)
614 {
615 info!(
616 module_id = %module_id,
617 discarded_consecutive_timeouts = discarded,
618 "route.bind breaker state discarded: a swap candidate was promoted over the process it described"
619 );
620 }
621 Ok(Some(ForwardingCutover {
622 promoted,
623 incumbent,
624 }))
625 }
626
627 #[allow(clippy::too_many_arguments)]
628 pub(crate) async fn begin_route_bind_relay_for(
629 &self,
630 client_connection_id: ConnectionId,
631 client_sink: FrameSink,
632 client_negotiated_ver: u8,
633 client_corr: u64,
634 module_id: &str,
635 principal: Principal,
636 project_root: Option<ProjectRootId>,
637 deadline: Instant,
638 ) -> Result<PendingRouteBindRelay, ForwardingError> {
639 let client_permit =
643 client_sink
644 .reserve_owned()
645 .await
646 .map_err(|_| ForwardingError::ClientEgressClosed {
647 connection_id: client_connection_id,
648 })?;
649 self.begin_route_bind_relay_inner(
650 client_connection_id,
651 client_sink,
652 client_negotiated_ver,
653 client_corr,
654 module_id,
655 principal,
656 project_root,
657 deadline,
658 client_permit,
659 )
660 }
661
662 #[cfg(test)]
663 pub(crate) fn begin_route_bind_relay_for_test(
664 &self,
665 client_connection_id: ConnectionId,
666 client_sink: FrameSink,
667 client_corr: u64,
668 module_id: &str,
669 ) -> Result<PendingRouteBindRelay, ForwardingError> {
670 let permit =
671 client_sink
672 .try_reserve_owned()
673 .map_err(|_| ForwardingError::ClientEgressClosed {
674 connection_id: client_connection_id,
675 })?;
676 self.begin_route_bind_relay_inner(
677 client_connection_id,
678 client_sink,
679 subc_protocol::PROTOCOL_VERSION,
680 client_corr,
681 module_id,
682 Principal::Direct,
683 None,
684 Instant::now() + std::time::Duration::from_secs(60),
685 permit,
686 )
687 }
688
689 pub(crate) fn begin_module_control_rpc_for(
690 &self,
691 module_id: &str,
692 expected_op: &str,
693 deadline: Instant,
694 ) -> Result<PendingModuleControlRpc, ForwardingError> {
695 self.begin_module_control_rpc_inner(module_id, expected_op, deadline, None, false)
696 }
697
698 pub(crate) fn begin_health_probe_rpc_for(
699 &self,
700 module_id: &str,
701 expected_op: &str,
702 probe_started_at: Instant,
703 deadline: Instant,
704 ) -> Result<PendingModuleControlRpc, ForwardingError> {
705 self.begin_module_control_rpc_inner(
706 module_id,
707 expected_op,
708 deadline,
709 Some(probe_started_at),
710 false,
711 )
712 }
713
714 pub(crate) fn begin_drain_health_probe_rpc_for(
715 &self,
716 module_id: &str,
717 expected_op: &str,
718 probe_started_at: Instant,
719 deadline: Instant,
720 ) -> Result<PendingModuleControlRpc, ForwardingError> {
721 self.begin_module_control_rpc_inner(
722 module_id,
723 expected_op,
724 deadline,
725 Some(probe_started_at),
726 true,
727 )
728 }
729
730 pub(crate) fn begin_endpoint_health_probe_rpc_for(
738 &self,
739 endpoint: ModuleEndpointId,
740 expected_op: &str,
741 probe_started_at: Instant,
742 deadline: Instant,
743 ) -> Result<PendingModuleControlRpc, ForwardingError> {
744 let inner = self.write_inner()?;
745 let module = module_connection_for_endpoint_locked(&inner, endpoint)
746 .cloned()
747 .ok_or(ForwardingError::NoModuleConnection)?;
748 let module_id = inner
749 .module_id_by_endpoint
750 .get(&endpoint)
751 .cloned()
752 .unwrap_or_default();
753 self.begin_control_rpc_locked(
756 inner,
757 &module_id,
758 module,
759 expected_op,
760 deadline,
761 Some(probe_started_at),
762 true,
763 )
764 }
765
766 fn begin_module_control_rpc_inner(
767 &self,
768 module_id: &str,
769 expected_op: &str,
770 deadline: Instant,
771 health_probe_started_at: Option<Instant>,
772 allow_draining: bool,
773 ) -> Result<PendingModuleControlRpc, ForwardingError> {
774 let inner = self.write_inner()?;
775 let module = inner
776 .modules_by_id
777 .get(module_id)
778 .cloned()
779 .ok_or(ForwardingError::NoModuleConnection)?;
780 self.begin_control_rpc_locked(
781 inner,
782 module_id,
783 module,
784 expected_op,
785 deadline,
786 health_probe_started_at,
787 allow_draining,
788 )
789 }
790
791 #[allow(clippy::too_many_arguments)]
792 fn begin_control_rpc_locked(
793 &self,
794 mut inner: RwLockWriteGuard<'_, ForwardingInner>,
795 module_id: &str,
796 module: ModuleConnection,
797 expected_op: &str,
798 deadline: Instant,
799 health_probe_started_at: Option<Instant>,
800 allow_draining: bool,
801 ) -> Result<PendingModuleControlRpc, ForwardingError> {
802 if !allow_draining && inner.draining_endpoints.contains_key(&module.endpoint) {
803 return Err(ForwardingError::ModuleReloading {
804 module_id: module_id.to_string(),
805 });
806 }
807 if inner
808 .closing_connections
809 .contains(&module.endpoint.connection_id)
810 {
811 return Err(ForwardingError::ConnectionClosing {
812 connection_id: module.endpoint.connection_id,
813 });
814 }
815 if health_probe_started_at.is_some() {
816 inner
820 .health_probe_tombstones
821 .retain(|(endpoint, _), _| *endpoint != module.endpoint);
822 }
823 let corr = match inner.allocate_control_corr(module.endpoint) {
824 Ok(corr) => corr,
825 Err(err) => {
826 drop(inner);
827 self.request_connection_close(
828 module.endpoint.connection_id,
829 CloseReason::new(
830 "control_correlation_exhausted",
831 "daemon-originated channel-0 correlation space exhausted",
832 ),
833 );
834 return Err(err);
835 }
836 };
837 let (sender, receiver) = oneshot::channel();
838 inner.pending_control_rpcs.insert(
839 (module.endpoint, corr),
840 PendingModuleControlRpcEntry {
841 expected_op: expected_op.to_string(),
842 deadline,
843 health_probe_started_at,
844 sender,
845 },
846 );
847
848 Ok(PendingModuleControlRpc {
849 endpoint: module.endpoint,
850 module_sink: module.sink,
851 negotiated_ver: module.negotiated_ver,
852 corr,
853 receiver,
854 })
855 }
856
857 #[allow(clippy::too_many_arguments)]
858 fn begin_route_bind_relay_inner(
859 &self,
860 client_connection_id: ConnectionId,
861 client_sink: FrameSink,
862 client_negotiated_ver: u8,
863 client_corr: u64,
864 expected_module_id: &str,
865 principal: Principal,
866 project_root: Option<ProjectRootId>,
867 deadline: Instant,
868 client_permit: mpsc::OwnedPermit<crate::router::OutboundFrame>,
869 ) -> Result<PendingRouteBindRelay, ForwardingError> {
870 let mut inner = self.write_inner()?;
871 if inner.closing_connections.contains(&client_connection_id) {
872 return Err(ForwardingError::ConnectionClosing {
873 connection_id: client_connection_id,
874 });
875 }
876 let module = inner
877 .modules_by_id
878 .get(expected_module_id)
879 .cloned()
880 .ok_or(ForwardingError::NoModuleConnection)?;
881 if inner.draining_endpoints.contains_key(&module.endpoint) {
882 return Err(ForwardingError::ModuleReloading {
883 module_id: expected_module_id.to_string(),
884 });
885 }
886 if inner
887 .closing_connections
888 .contains(&module.endpoint.connection_id)
889 {
890 return Err(ForwardingError::ConnectionClosing {
891 connection_id: module.endpoint.connection_id,
892 });
893 }
894
895 let corr = match inner.allocate_control_corr(module.endpoint) {
896 Ok(corr) => corr,
897 Err(err) => {
898 drop(inner);
899 self.request_connection_close(
900 module.endpoint.connection_id,
901 CloseReason::new(
902 "control_correlation_exhausted",
903 "daemon-originated channel-0 correlation space exhausted",
904 ),
905 );
906 return Err(err);
907 }
908 };
909 let (client_channel, client_epoch, module_channel, module_epoch) =
910 inner.allocate_route_slots(client_connection_id, module.endpoint)?;
911 let client_key = ClientRouteKey {
912 connection_id: client_connection_id,
913 channel: client_channel,
914 };
915 let module_key = ModuleRouteKey {
916 endpoint: module.endpoint,
917 channel: module_channel,
918 };
919 let reservation = RouteReservation {
920 client_key,
921 module_key,
922 client_epoch,
923 module_epoch,
924 project_root,
925 };
926 let response_body = serde_json::to_vec(&ClientControlResponse::RouteOpen {
927 route_channel: client_channel,
928 route_epoch: client_epoch,
929 })
930 .map_err(|err| ForwardingError::RouteOpenBuild(err.to_string()))?;
931 let route_open_frame = Frame::build_with_version(
932 client_negotiated_ver,
933 FrameType::Response,
934 Flags::new(false, Priority::Passive, false),
935 0,
936 0,
937 client_corr,
938 response_body,
939 )
940 .map_err(|err| ForwardingError::RouteOpenBuild(err.to_string()))?;
941 let (sender, receiver) = oneshot::channel();
942 inner.reserved_client.insert(client_key, module_key);
943 inner.reserved_module.insert(module_key, client_key);
944 inner.pending_relays.insert(
945 (module.endpoint, corr),
946 PendingRouteBindRelayEntry {
947 reservation,
948 client_sink,
949 client_negotiated_ver,
950 client_permit,
951 route_open_frame,
952 principal,
953 deadline,
954 relay_enqueued: false,
955 sender,
956 },
957 );
958
959 Ok(PendingRouteBindRelay {
960 endpoint: module.endpoint,
961 module_sink: module.sink,
962 negotiated_ver: module.negotiated_ver,
963 client_channel,
964 client_epoch,
965 module_channel,
966 module_epoch,
967 corr,
968 receiver,
969 })
970 }
971
972 pub(crate) fn mark_route_bind_relay_enqueued(
973 &self,
974 endpoint: ModuleEndpointId,
975 corr: u64,
976 ) -> Result<bool, ForwardingError> {
977 let mut inner = self.write_inner()?;
978 let Some(pending) = inner.pending_relays.get_mut(&(endpoint, corr)) else {
979 return Ok(false);
980 };
981 pending.relay_enqueued = true;
982 Ok(true)
983 }
984
985 pub(crate) fn release_client_route(
986 &self,
987 client_connection_id: ConnectionId,
988 client_channel: u16,
989 expected_epoch: u32,
990 ) -> Result<RouteRelease, ForwardingError> {
991 let mut inner = self.write_inner()?;
992 let release = release_client_route_locked(
993 &mut inner,
994 ClientRouteKey {
995 connection_id: client_connection_id,
996 channel: client_channel,
997 },
998 expected_epoch,
999 );
1000 self.record_route_release(&release);
1001 Ok(release)
1002 }
1003
1004 pub(crate) fn release_module_route(
1005 &self,
1006 module_connection_id: ConnectionId,
1007 module_channel: u16,
1008 expected_epoch: u32,
1009 ) -> Result<RouteRelease, ForwardingError> {
1010 let mut inner = self.write_inner()?;
1011 let Some(endpoint) = inner
1012 .endpoint_by_connection
1013 .get(&module_connection_id)
1014 .copied()
1015 else {
1016 return Ok(RouteRelease::Absent);
1017 };
1018 let release = release_module_route_locked(
1019 &mut inner,
1020 ModuleRouteKey {
1021 endpoint,
1022 channel: module_channel,
1023 },
1024 expected_epoch,
1025 );
1026 self.record_route_release(&release);
1027 Ok(release)
1028 }
1029
1030 pub(crate) fn abort_pending_relay(
1031 &self,
1032 endpoint: ModuleEndpointId,
1033 corr: u64,
1034 outcome: RouteBindRelayOutcome,
1035 ) -> Result<Option<GoodbyeTarget>, ForwardingError> {
1036 let mut inner = self.write_inner()?;
1037 let Some(pending) = inner.pending_relays.remove(&(endpoint, corr)) else {
1038 return Ok(None);
1039 };
1040 release_reserved_route_locked(
1041 &mut inner,
1042 pending.reservation.client_key,
1043 pending.reservation.module_key,
1044 );
1045 let target = pending
1046 .relay_enqueued
1047 .then(|| abandoned_route_target(&inner, &pending.reservation));
1048 let _ = pending.sender.send(outcome);
1049 Ok(target.flatten())
1050 }
1051
1052 pub(crate) fn cancel_module_control_rpc(
1053 &self,
1054 endpoint: ModuleEndpointId,
1055 corr: u64,
1056 ) -> Result<(), ForwardingError> {
1057 self.write_inner()?
1058 .pending_control_rpcs
1059 .remove(&(endpoint, corr));
1060 Ok(())
1061 }
1062
1063 pub(crate) fn tombstone_health_probe_rpc(
1064 &self,
1065 endpoint: ModuleEndpointId,
1066 corr: u64,
1067 ) -> Result<bool, ForwardingError> {
1068 let key = (endpoint, corr);
1069 let expires_at = Instant::now() + HEALTH_PROBE_TOMBSTONE_TTL;
1070 {
1071 let mut inner = self.write_inner()?;
1072 let Some(pending) = inner.pending_control_rpcs.remove(&key) else {
1073 return Ok(false);
1074 };
1075 let Some(probe_started_at) = pending.health_probe_started_at else {
1076 inner.pending_control_rpcs.insert(key, pending);
1077 return Ok(false);
1078 };
1079 let module_id = inner
1080 .module_id_by_endpoint
1081 .get(&endpoint)
1082 .cloned()
1083 .unwrap_or_else(|| "unknown".to_string());
1084 inner.health_probe_tombstones.insert(
1085 key,
1086 HealthProbeTombstone {
1087 expected_op: pending.expected_op,
1088 module_id,
1089 probe_started_at,
1090 expires_at,
1091 },
1092 );
1093 }
1094 self.schedule_health_probe_tombstone_expiration(key, expires_at);
1095 Ok(true)
1096 }
1097
1098 fn schedule_health_probe_tombstone_expiration(
1099 &self,
1100 key: (ModuleEndpointId, u64),
1101 expires_at: Instant,
1102 ) {
1103 let inner = Arc::downgrade(&self.inner);
1104 tokio::spawn(async move {
1105 tokio::time::sleep_until(expires_at).await;
1106 let Some(inner) = inner.upgrade() else {
1107 return;
1108 };
1109 let Ok(mut inner) = inner.write() else {
1110 return;
1111 };
1112 let expired = inner
1113 .health_probe_tombstones
1114 .get(&key)
1115 .is_some_and(|tombstone| tombstone.expires_at <= Instant::now());
1116 if expired {
1117 inner.health_probe_tombstones.remove(&key);
1118 }
1119 });
1120 }
1121
1122 pub(crate) fn complete_pending_relay(
1123 &self,
1124 connection_id: ConnectionId,
1125 corr: u64,
1126 outcome: RouteBindRelayOutcome,
1127 ) -> Result<PendingRelayCompletion, ForwardingError> {
1128 let mut inner = self.write_inner()?;
1129 let Some(endpoint) = inner.endpoint_by_connection.get(&connection_id).copied() else {
1130 return Ok(PendingRelayCompletion {
1131 settled: false,
1132 abandoned: None,
1133 });
1134 };
1135 let Some(pending) = inner.pending_relays.remove(&(endpoint, corr)) else {
1136 return Ok(PendingRelayCompletion {
1137 settled: false,
1138 abandoned: None,
1139 });
1140 };
1141
1142 if Instant::now() >= pending.deadline {
1143 release_reserved_route_locked(
1144 &mut inner,
1145 pending.reservation.client_key,
1146 pending.reservation.module_key,
1147 );
1148 let abandoned = matches!(outcome, RouteBindRelayOutcome::Accepted)
1149 .then(|| abandoned_route_target(&inner, &pending.reservation))
1150 .flatten();
1151 let _ = pending
1152 .sender
1153 .send(RouteBindRelayOutcome::Rejected(ErrorBody {
1154 code: "module_timeout".to_string(),
1155 message: "route.bind response arrived after its daemon deadline".to_string(),
1156 detail: None,
1157 }));
1158 return Ok(PendingRelayCompletion {
1159 settled: true,
1160 abandoned,
1161 });
1162 }
1163
1164 match outcome {
1165 RouteBindRelayOutcome::Accepted
1184 if pending.client_sink.is_closed()
1185 || inner
1186 .closing_connections
1187 .contains(&pending.reservation.client_key.connection_id) =>
1188 {
1189 let reason = if pending.client_sink.is_closed() {
1190 "client egress closed before route publication"
1191 } else {
1192 "client connection is closing before route publication"
1193 };
1194 release_reserved_route_locked(
1195 &mut inner,
1196 pending.reservation.client_key,
1197 pending.reservation.module_key,
1198 );
1199 let abandoned = pending
1200 .relay_enqueued
1201 .then(|| abandoned_route_target(&inner, &pending.reservation))
1202 .flatten();
1203 let _ = pending
1204 .sender
1205 .send(RouteBindRelayOutcome::ModuleGone(reason.to_string()));
1206 return Ok(PendingRelayCompletion {
1207 settled: true,
1208 abandoned,
1209 });
1210 }
1211 RouteBindRelayOutcome::Accepted
1228 if inner.superseded_endpoints.contains_key(&endpoint) =>
1229 {
1230 release_reserved_route_locked(
1231 &mut inner,
1232 pending.reservation.client_key,
1233 pending.reservation.module_key,
1234 );
1235 let abandoned = abandoned_route_target(&inner, &pending.reservation);
1238 let module_id = inner
1239 .module_id_by_endpoint
1240 .get(&endpoint)
1241 .cloned()
1242 .unwrap_or_else(|| "unknown".to_string());
1243 let _ = pending
1244 .sender
1245 .send(RouteBindRelayOutcome::Rejected(ErrorBody::new(
1246 "module_reloading",
1247 format!("module_id '{module_id}' is reloading"),
1248 )));
1249 return Ok(PendingRelayCompletion {
1250 settled: true,
1251 abandoned,
1252 });
1253 }
1254 RouteBindRelayOutcome::Accepted => {
1255 let abandoned = commit_route_locked(&mut inner, pending)?;
1256 return Ok(PendingRelayCompletion {
1257 settled: true,
1258 abandoned,
1259 });
1260 }
1261 terminal => {
1262 release_reserved_route_locked(
1263 &mut inner,
1264 pending.reservation.client_key,
1265 pending.reservation.module_key,
1266 );
1267 let _ = pending.sender.send(terminal);
1268 }
1269 }
1270 Ok(PendingRelayCompletion {
1271 settled: true,
1272 abandoned: None,
1273 })
1274 }
1275
1276 pub(crate) fn pending_module_control_op(
1277 &self,
1278 connection_id: ConnectionId,
1279 corr: u64,
1280 ) -> Result<Option<String>, ForwardingError> {
1281 let inner = self.read_inner()?;
1282 let Some(endpoint) = inner.endpoint_by_connection.get(&connection_id).copied() else {
1283 return Ok(None);
1284 };
1285 let key = (endpoint, corr);
1286 Ok(inner
1287 .pending_control_rpcs
1288 .get(&key)
1289 .map(|pending| pending.expected_op.clone())
1290 .or_else(|| {
1291 inner
1292 .health_probe_tombstones
1293 .get(&key)
1294 .filter(|tombstone| tombstone.expires_at > Instant::now())
1295 .map(|tombstone| tombstone.expected_op.clone())
1296 }))
1297 }
1298
1299 pub(crate) fn complete_module_control_rpc(
1300 &self,
1301 connection_id: ConnectionId,
1302 corr: u64,
1303 actual_op: Option<&str>,
1304 outcome: ModuleControlRpcOutcome,
1305 ) -> Result<ModuleControlRpcCompletion, ForwardingError> {
1306 let now = Instant::now();
1307 let mut inner = self.write_inner()?;
1308 let Some(endpoint) = inner.endpoint_by_connection.get(&connection_id).copied() else {
1309 return Ok(ModuleControlRpcCompletion::Unknown);
1310 };
1311 let key = (endpoint, corr);
1312 if let Some(pending) = inner.pending_control_rpcs.remove(&key) {
1313 if now >= pending.deadline {
1314 let late_health_answer = pending.health_probe_started_at.map(|probe_started_at| {
1315 ModuleControlRpcCompletion::LateHealthAnswer {
1316 module_id: inner
1317 .module_id_by_endpoint
1318 .get(&endpoint)
1319 .cloned()
1320 .unwrap_or_else(|| "unknown".to_string()),
1321 latency: now.saturating_duration_since(probe_started_at),
1322 }
1323 });
1324 let _ = pending
1325 .sender
1326 .send(ModuleControlRpcOutcome::DeadlineElapsed);
1327 return Ok(late_health_answer.unwrap_or(ModuleControlRpcCompletion::Settled));
1328 }
1329 let outcome = match actual_op {
1330 Some(actual) if actual != pending.expected_op => {
1331 ModuleControlRpcOutcome::UnexpectedOp {
1332 expected: pending.expected_op,
1333 actual: actual.to_string(),
1334 }
1335 }
1336 _ => outcome,
1337 };
1338 let _ = pending.sender.send(outcome);
1339 return Ok(ModuleControlRpcCompletion::Settled);
1340 }
1341
1342 let Some(tombstone) = inner.health_probe_tombstones.remove(&key) else {
1343 return Ok(ModuleControlRpcCompletion::Unknown);
1344 };
1345 if tombstone.expires_at <= now {
1346 return Ok(ModuleControlRpcCompletion::Unknown);
1347 }
1348 Ok(ModuleControlRpcCompletion::LateHealthAnswer {
1349 module_id: tombstone.module_id,
1350 latency: now.saturating_duration_since(tombstone.probe_started_at),
1351 })
1352 }
1353
1354 #[cfg(test)]
1355 pub(crate) fn health_probe_tombstone_count(&self) -> Result<usize, ForwardingError> {
1356 Ok(self.read_inner()?.health_probe_tombstones.len())
1357 }
1358
1359 #[cfg(test)]
1360 pub(crate) fn closing_connection_count(&self) -> Result<usize, ForwardingError> {
1361 Ok(self.read_inner()?.closing_connections.len())
1362 }
1363
1364 #[cfg(test)]
1367 pub(crate) fn reserved_route_count(&self) -> Result<(usize, usize), ForwardingError> {
1368 let inner = self.read_inner()?;
1369 Ok((inner.reserved_client.len(), inner.reserved_module.len()))
1370 }
1371
1372 pub(crate) fn module_endpoint_for_connection(
1373 &self,
1374 connection_id: ConnectionId,
1375 ) -> Result<Option<ModuleEndpointId>, ForwardingError> {
1376 Ok(self
1377 .read_inner()?
1378 .endpoint_by_connection
1379 .get(&connection_id)
1380 .copied())
1381 }
1382
1383 pub(crate) fn module_id_for_connection(
1386 &self,
1387 connection_id: ConnectionId,
1388 ) -> Result<Option<String>, ForwardingError> {
1389 let inner = self.read_inner()?;
1390 Ok(inner
1391 .endpoint_by_connection
1392 .get(&connection_id)
1393 .and_then(|endpoint| inner.module_id_by_endpoint.get(endpoint))
1394 .cloned())
1395 }
1396
1397 pub(crate) fn has_live_module_connection(
1398 &self,
1399 module_id: &str,
1400 ) -> Result<bool, ForwardingError> {
1401 Ok(self.read_inner()?.modules_by_id.contains_key(module_id))
1402 }
1403
1404 pub(crate) fn lookup_data_route(
1405 &self,
1406 connection_id: ConnectionId,
1407 channel: u16,
1408 epoch: u32,
1409 ) -> Result<DataRoute, ForwardingError> {
1410 let inner = self.read_inner()?;
1411 let state = if let Some(endpoint) =
1412 inner.endpoint_by_connection.get(&connection_id).copied()
1413 {
1414 let key = ModuleRouteKey { endpoint, channel };
1415 match inner.module_to_client.get(&key) {
1416 Some(route) if route.module_epoch == epoch => {
1417 DataRouteState::Bound(Arc::clone(route))
1418 }
1419 Some(_) => DataRouteState::EpochMismatch,
1420 None if inner.reserved_module.contains_key(&key)
1421 && inner.module_slot_epochs.get(&key).copied() == Some(epoch) =>
1422 {
1423 DataRouteState::Reserved
1424 }
1425 None if inner.reserved_module.contains_key(&key) => DataRouteState::EpochMismatch,
1426 None => DataRouteState::Absent,
1427 }
1428 } else {
1429 let key = ClientRouteKey {
1430 connection_id,
1431 channel,
1432 };
1433 match inner.client_to_module.get(&key) {
1434 Some(route) if route.client_epoch == epoch => {
1435 DataRouteState::Bound(Arc::clone(route))
1436 }
1437 Some(_) => DataRouteState::EpochMismatch,
1438 None if inner.reserved_client.contains_key(&key)
1439 && inner.client_slot_epochs.get(&key).copied() == Some(epoch) =>
1440 {
1441 DataRouteState::Reserved
1442 }
1443 None if inner.reserved_client.contains_key(&key) => DataRouteState::EpochMismatch,
1444 None => DataRouteState::Absent,
1445 }
1446 };
1447 Ok(
1448 if inner.endpoint_by_connection.contains_key(&connection_id) {
1449 DataRoute::Module(state)
1450 } else {
1451 DataRoute::Client(state)
1452 },
1453 )
1454 }
1455
1456 #[cfg(test)]
1457 pub(crate) fn inject_client_slot_epoch(
1458 &self,
1459 connection_id: ConnectionId,
1460 channel: u16,
1461 last_epoch: u32,
1462 ) {
1463 let mut inner = self.write_inner().expect("forwarding lock");
1464 inner.client_slot_epochs.insert(
1465 ClientRouteKey {
1466 connection_id,
1467 channel,
1468 },
1469 last_epoch,
1470 );
1471 inner.next_client_channel.insert(connection_id, channel);
1472 }
1473
1474 #[cfg(test)]
1475 pub(crate) fn inject_module_slot_epoch(
1476 &self,
1477 endpoint: ModuleEndpointId,
1478 channel: u16,
1479 last_epoch: u32,
1480 ) {
1481 let mut inner = self.write_inner().expect("forwarding lock");
1482 inner
1483 .module_slot_epochs
1484 .insert(ModuleRouteKey { endpoint, channel }, last_epoch);
1485 inner.next_module_channel.insert(endpoint, channel);
1486 }
1487
1488 #[cfg(test)]
1489 pub(crate) fn inject_control_corr(&self, endpoint: ModuleEndpointId, next_corr: u64) {
1490 self.write_inner()
1491 .expect("forwarding lock")
1492 .next_control_corr
1493 .insert(endpoint, next_corr);
1494 }
1495
1496 pub(crate) fn cache_status(
1497 &self,
1498 endpoint: ModuleEndpointId,
1499 module_channel: u16,
1500 module_epoch: u32,
1501 status: String,
1502 ) -> Result<bool, ForwardingError> {
1503 let mut inner = self.write_inner()?;
1504 if !inner.module_id_by_endpoint.contains_key(&endpoint) {
1505 return Err(ForwardingError::StaleModuleEndpoint);
1506 }
1507
1508 let module_key = ModuleRouteKey {
1509 endpoint,
1510 channel: module_channel,
1511 };
1512 let handle = if let Some(route) = inner.module_to_client.get(&module_key) {
1513 (route.module_epoch == module_epoch).then_some((
1514 ClientRouteKey {
1515 connection_id: route.client_connection_id,
1516 channel: route.client_channel,
1517 },
1518 route.client_epoch,
1519 ))
1520 } else if let Some(client_key) = inner.reserved_module.get(&module_key).copied() {
1521 (inner.module_slot_epochs.get(&module_key).copied() == Some(module_epoch)).then_some((
1522 client_key,
1523 inner
1524 .client_slot_epochs
1525 .get(&client_key)
1526 .copied()
1527 .unwrap_or(0),
1528 ))
1529 } else {
1530 None
1531 };
1532
1533 if let Some(handle) = handle {
1534 inner.status.insert(handle, status);
1535 Ok(true)
1536 } else {
1537 debug!(
1538 module_channel,
1539 module_epoch,
1540 generation = endpoint.generation,
1541 connection_id = endpoint.connection_id.get(),
1542 "dropping stale status update for module route handle"
1543 );
1544 Ok(false)
1545 }
1546 }
1547
1548 pub(crate) fn route_poll_snapshot(
1549 &self,
1550 client_connection_id: ConnectionId,
1551 client_channel: u16,
1552 client_epoch: u32,
1553 ) -> Result<RoutePollSnapshot, ForwardingError> {
1554 let inner = self.read_inner()?;
1555 let client_key = ClientRouteKey {
1556 connection_id: client_connection_id,
1557 channel: client_channel,
1558 };
1559 let Some(route) = inner.client_to_module.get(&client_key) else {
1560 return Ok(RoutePollSnapshot::Absent);
1561 };
1562 if route.client_epoch != client_epoch
1563 || !inner
1564 .module_id_by_endpoint
1565 .contains_key(&route.module_endpoint)
1566 {
1567 return Ok(RoutePollSnapshot::Absent);
1568 }
1569 Ok(RoutePollSnapshot::Bound {
1570 module_id: route.module_id.clone(),
1571 status: inner.status.get(&(client_key, client_epoch)).cloned(),
1572 })
1573 }
1574
1575 pub fn active_binding_count(&self) -> Result<usize, ForwardingError> {
1576 Ok(self.read_inner()?.client_to_module.len())
1577 }
1578
1579 pub fn client_route_concentration(&self) -> Result<(usize, usize), ForwardingError> {
1589 let inner = self.read_inner()?;
1590 let mut per_connection: HashMap<ConnectionId, usize> = HashMap::new();
1591 for key in inner.client_to_module.keys() {
1592 *per_connection.entry(key.connection_id).or_insert(0) += 1;
1593 }
1594 let max = per_connection.values().copied().max().unwrap_or(0);
1595 Ok((per_connection.len(), max))
1596 }
1597
1598 pub fn has_route_channel(&self, route_channel: u16) -> Result<bool, ForwardingError> {
1599 let inner = self.read_inner()?;
1600 Ok(inner
1601 .client_to_module
1602 .keys()
1603 .any(|key| key.channel == route_channel))
1604 }
1605
1606 #[cfg(unix)]
1609 pub(crate) fn begin_daemon_drain(&self) -> Result<Vec<String>, ForwardingError> {
1610 let mut inner = self.write_inner()?;
1611 inner.daemon_draining = true;
1612 let modules = inner
1613 .modules_by_id
1614 .iter()
1615 .map(|(id, module)| (id.clone(), module.endpoint))
1616 .collect::<Vec<_>>();
1617 for (_, endpoint) in &modules {
1618 inner
1619 .draining_endpoints
1620 .insert(*endpoint, RouteCloseReason::Restart);
1621 }
1622 let off_slot_endpoints = inner
1627 .candidates_by_id
1628 .values()
1629 .map(|module| module.endpoint)
1630 .chain(inner.superseded_endpoints.keys().copied())
1631 .collect::<Vec<_>>();
1632 for endpoint in off_slot_endpoints {
1633 inner
1634 .draining_endpoints
1635 .insert(endpoint, RouteCloseReason::Restart);
1636 }
1637 Ok(modules.into_iter().map(|(id, _)| id).collect())
1638 }
1639
1640 pub(crate) fn begin_module_drain(
1647 &self,
1648 module_id: &str,
1649 reason: RouteCloseReason,
1650 ) -> Result<Option<ModuleDrainTarget>, ForwardingError> {
1651 let mut inner = self.write_inner()?;
1652 let Some(module) = inner.modules_by_id.get(module_id).cloned() else {
1653 return Ok(None);
1654 };
1655 Ok(Some(begin_drain_locked(
1656 &mut inner, module_id, module, reason,
1657 )))
1658 }
1659
1660 pub(crate) fn begin_endpoint_drain(
1667 &self,
1668 endpoint: ModuleEndpointId,
1669 reason: RouteCloseReason,
1670 ) -> Result<Option<ModuleDrainTarget>, ForwardingError> {
1671 let mut inner = self.write_inner()?;
1672 let Some(module) = module_connection_for_endpoint_locked(&inner, endpoint).cloned() else {
1673 return Ok(None);
1674 };
1675 let module_id = inner
1676 .module_id_by_endpoint
1677 .get(&endpoint)
1678 .cloned()
1679 .expect("an endpoint resolved to a module connection has a module id");
1680 Ok(Some(begin_drain_locked(
1681 &mut inner, &module_id, module, reason,
1682 )))
1683 }
1684}
1685
1686fn begin_drain_locked(
1690 inner: &mut ForwardingInner,
1691 module_id: &str,
1692 module: ModuleConnection,
1693 reason: RouteCloseReason,
1694) -> ModuleDrainTarget {
1695 {
1696 let endpoint = module.endpoint;
1697 inner.draining_endpoints.insert(endpoint, reason);
1698
1699 let flows = inner
1700 .client_to_module
1701 .values()
1702 .filter(|route| route.module_endpoint == endpoint)
1703 .map(|route| Arc::clone(&route.flow))
1704 .collect::<Vec<_>>();
1705 let excluded_subscriptions = flows
1706 .into_iter()
1707 .map(|flow| flow.begin_drain())
1708 .fold(0u32, u32::saturating_add);
1709
1710 let pending_keys = inner
1711 .pending_relays
1712 .keys()
1713 .filter(|(pending_endpoint, _)| *pending_endpoint == endpoint)
1714 .copied()
1715 .collect::<Vec<_>>();
1716 let mut abandoned_bindings = Vec::new();
1717 for key in pending_keys {
1718 let Some(pending) = inner.pending_relays.remove(&key) else {
1719 continue;
1720 };
1721 release_reserved_route_locked(
1722 inner,
1723 pending.reservation.client_key,
1724 pending.reservation.module_key,
1725 );
1726 if pending.relay_enqueued {
1727 if let Some(target) = abandoned_route_target(inner, &pending.reservation) {
1728 abandoned_bindings.push(target);
1729 }
1730 }
1731 let _ = pending
1732 .sender
1733 .send(RouteBindRelayOutcome::Rejected(ErrorBody::new(
1734 "module_reloading",
1735 format!("module_id '{module_id}' is reloading"),
1736 )));
1737 }
1738
1739 let pending_control_keys = inner
1740 .pending_control_rpcs
1741 .keys()
1742 .filter(|(pending_endpoint, _)| *pending_endpoint == endpoint)
1743 .copied()
1744 .collect::<Vec<_>>();
1745 for key in pending_control_keys {
1746 if let Some(pending) = inner.pending_control_rpcs.remove(&key) {
1747 let _ = pending
1748 .sender
1749 .send(ModuleControlRpcOutcome::ModuleGone(format!(
1750 "module '{module_id}' began draining during module-control RPC"
1751 )));
1752 }
1753 }
1754
1755 ModuleDrainTarget {
1756 endpoint,
1757 sink: module.sink,
1758 negotiated_ver: module.negotiated_ver,
1759 abandoned_bindings,
1760 excluded_subscriptions,
1761 }
1762 }
1763}
1764
1765#[derive(Debug, Default, PartialEq, Eq)]
1768pub(crate) struct DrainHoldouts {
1769 pub(crate) requests: usize,
1772 pub(crate) routes: usize,
1774 pub(crate) total_routes: usize,
1776 pub(crate) top_connections: Vec<(u64, usize)>,
1779 pub(crate) held: Vec<(u16, u64)>,
1788}
1789
1790pub(crate) const DRAIN_HELD_REQUESTS_LISTED: usize = 32;
1792
1793impl ForwardingTable {
1794 pub(crate) fn endpoint_drain_holdouts(
1796 &self,
1797 endpoint: ModuleEndpointId,
1798 ) -> Result<DrainHoldouts, ForwardingError> {
1799 let inner = self.read_inner()?;
1800 let mut holdouts = DrainHoldouts::default();
1801 let mut by_connection: HashMap<u64, usize> = HashMap::new();
1802 for (key, route) in &inner.client_to_module {
1803 if route.module_endpoint != endpoint {
1804 continue;
1805 }
1806 holdouts.total_routes += 1;
1807 let held = route.flow.drain_in_flight();
1808 if held == 0 {
1809 continue;
1810 }
1811 holdouts.requests += held;
1812 holdouts.routes += 1;
1813 *by_connection.entry(key.connection_id.get()).or_default() += held;
1814 holdouts.held.extend(
1815 route
1816 .flow
1817 .drain_held_corrs()
1818 .into_iter()
1819 .map(|corr| (route.module_channel, corr)),
1820 );
1821 }
1822 holdouts.held.sort_unstable();
1823 holdouts.held.truncate(DRAIN_HELD_REQUESTS_LISTED);
1824 let mut connections = by_connection.into_iter().collect::<Vec<_>>();
1825 connections.sort_by(|left, right| right.1.cmp(&left.1).then(left.0.cmp(&right.0)));
1826 connections.truncate(3);
1827 holdouts.top_connections = connections;
1828 Ok(holdouts)
1829 }
1830
1831 pub(crate) fn endpoint_in_flight_count(
1832 &self,
1833 endpoint: ModuleEndpointId,
1834 ) -> Result<usize, ForwardingError> {
1835 let inner = self.read_inner()?;
1836 Ok(inner
1837 .client_to_module
1838 .values()
1839 .filter(|route| route.module_endpoint == endpoint)
1840 .map(|route| route.flow.drain_in_flight())
1841 .sum())
1842 }
1843
1844 pub(crate) fn endpoint_is_draining(
1845 &self,
1846 endpoint: ModuleEndpointId,
1847 ) -> Result<bool, ForwardingError> {
1848 Ok(self
1849 .read_inner()?
1850 .draining_endpoints
1851 .contains_key(&endpoint))
1852 }
1853
1854 pub(crate) fn module_is_draining(&self, module_id: &str) -> Result<bool, ForwardingError> {
1855 let inner = self.read_inner()?;
1856 Ok(inner
1857 .modules_by_id
1858 .get(module_id)
1859 .is_some_and(|module| inner.draining_endpoints.contains_key(&module.endpoint)))
1860 }
1861
1862 pub(crate) fn release_module_endpoint_routes(
1863 &self,
1864 endpoint: ModuleEndpointId,
1865 ) -> Result<Vec<GoodbyeTarget>, ForwardingError> {
1866 let mut inner = self.write_inner()?;
1867 let routes = inner
1868 .module_to_client
1869 .iter()
1870 .filter(|(module_key, _)| module_key.endpoint == endpoint)
1871 .map(|(module_key, route)| (*module_key, route.module_epoch))
1872 .collect::<Vec<_>>();
1873 let mut released = Vec::with_capacity(routes.len());
1874 for (module_key, epoch) in routes {
1875 if let RouteRelease::Removed(target) =
1876 release_module_route_locked(&mut inner, module_key, epoch)
1877 {
1878 released.push(target);
1879 }
1880 }
1881 Ok(released)
1882 }
1883
1884 pub(crate) fn endpoint_routes(
1890 &self,
1891 endpoint: ModuleEndpointId,
1892 ) -> Result<Vec<EndpointRoute>, ForwardingError> {
1893 let inner = self.read_inner()?;
1894 Ok(endpoint_routes_locked(&inner, endpoint))
1895 }
1896
1897 pub(crate) fn route_census(
1899 &self,
1900 module_id: Option<&str>,
1901 ) -> Result<Vec<(String, Vec<EndpointRoute>)>, ForwardingError> {
1902 let inner = self.read_inner()?;
1903 let mut endpoints = inner
1904 .modules_by_id
1905 .iter()
1906 .filter(|(id, _)| module_id.is_none_or(|requested| requested == id.as_str()))
1907 .map(|(id, module)| (id.clone(), module.endpoint))
1908 .collect::<Vec<_>>();
1909 endpoints.sort_by(|left, right| left.0.cmp(&right.0));
1910 Ok(endpoints
1911 .into_iter()
1912 .map(|(id, endpoint)| (id, endpoint_routes_locked(&inner, endpoint)))
1913 .collect())
1914 }
1915
1916 pub(crate) fn live_roots(
1918 &self,
1919 module_id: &str,
1920 ) -> Result<ModuleControlResponseToModule, ForwardingError> {
1921 let inner = self.read_inner()?;
1922 let endpoint = inner
1923 .modules_by_id
1924 .get(module_id)
1925 .map(|module| module.endpoint);
1926 let mut roots = BTreeMap::new();
1927 let mut unknown_root_bindings = 0;
1928 let mut total_bindings = 0;
1929 if let Some(endpoint) = endpoint {
1930 for binding in inner
1931 .module_to_client
1932 .values()
1933 .filter(|binding| binding.module_endpoint == endpoint)
1934 {
1935 total_bindings += 1;
1936 if let Some(root) = &binding.project_root {
1937 let entry = roots.entry(root.as_path().to_path_buf()).or_insert((0, 0));
1938 entry.0 += 1;
1939 } else {
1940 unknown_root_bindings += 1;
1941 }
1942 }
1943 for pending in inner
1944 .pending_relays
1945 .values()
1946 .filter(|pending| pending.reservation.module_key.endpoint == endpoint)
1947 {
1948 total_bindings += 1;
1949 if let Some(root) = &pending.reservation.project_root {
1950 let entry = roots.entry(root.as_path().to_path_buf()).or_insert((0, 0));
1951 entry.1 += 1;
1952 } else {
1953 unknown_root_bindings += 1;
1954 }
1955 }
1956 }
1957 Ok(ModuleControlResponseToModule::LiveRoots {
1958 roots: roots
1959 .into_iter()
1960 .map(|(project_root, (bound, pending))| LiveRoot {
1961 project_root,
1962 bound,
1963 pending,
1964 })
1965 .collect(),
1966 unknown_root_bindings,
1967 total_bindings,
1968 })
1969 }
1970
1971 pub(crate) fn connection_has_client_routes(
1977 &self,
1978 connection_id: ConnectionId,
1979 ) -> Result<bool, ForwardingError> {
1980 let inner = self.read_inner()?;
1981 let has = inner
1982 .client_to_module
1983 .keys()
1984 .any(|key| key.connection_id == connection_id)
1985 || inner
1986 .reserved_client
1987 .keys()
1988 .any(|key| key.connection_id == connection_id);
1989 Ok(has)
1990 }
1991
1992 pub(crate) fn cleanup_connection(
1993 &self,
1994 connection_id: ConnectionId,
1995 ) -> Result<Vec<GoodbyeTarget>, ForwardingError> {
1996 let mut inner = self.write_inner()?;
1997 inner.closing_connections.insert(connection_id);
1998 let released = if let Some(endpoint) = inner.endpoint_by_connection.remove(&connection_id) {
1999 remove_module_connection_locked(&mut inner, endpoint)
2000 } else {
2001 Self::cleanup_client_connection_locked(&mut inner, connection_id)
2002 };
2003 inner.closing_connections.remove(&connection_id);
2012 Ok(released)
2013 }
2014
2015 fn cleanup_client_connection_locked(
2016 inner: &mut ForwardingInner,
2017 connection_id: ConnectionId,
2018 ) -> Vec<GoodbyeTarget> {
2019 let routes = inner
2020 .client_to_module
2021 .iter()
2022 .filter(|(key, _)| key.connection_id == connection_id)
2023 .map(|(key, route)| (*key, route.client_epoch))
2024 .collect::<Vec<_>>();
2025 let mut released = Vec::with_capacity(routes.len());
2026 for (client_key, epoch) in routes {
2027 if let RouteRelease::Removed(target) =
2028 release_client_route_locked(inner, client_key, epoch)
2029 {
2030 released.push(target);
2031 }
2032 }
2033
2034 let pending_keys = inner
2035 .pending_relays
2036 .iter()
2037 .filter(|(_, pending)| pending.reservation.client_key.connection_id == connection_id)
2038 .map(|(key, _)| *key)
2039 .collect::<Vec<_>>();
2040 for key in pending_keys {
2041 let Some(pending) = inner.pending_relays.remove(&key) else {
2042 continue;
2043 };
2044 release_reserved_route_locked(
2045 inner,
2046 pending.reservation.client_key,
2047 pending.reservation.module_key,
2048 );
2049 if pending.relay_enqueued {
2050 if let Some(target) = abandoned_route_target(inner, &pending.reservation) {
2051 released.push(target);
2052 }
2053 }
2054 let _ = pending.sender.send(RouteBindRelayOutcome::ModuleGone(
2055 "client connection closed during route.bind relay".to_string(),
2056 ));
2057 }
2058
2059 let orphaned = inner
2060 .reserved_client
2061 .iter()
2062 .filter(|(key, _)| key.connection_id == connection_id)
2063 .map(|(client, module)| (*client, *module))
2064 .collect::<Vec<_>>();
2065 for (client_key, module_key) in orphaned {
2066 release_reserved_route_locked(inner, client_key, module_key);
2067 }
2068 inner.next_client_channel.remove(&connection_id);
2069 inner
2070 .client_slot_epochs
2071 .retain(|key, _| key.connection_id != connection_id);
2072 inner
2073 .last_published_epoch
2074 .retain(|key, _| key.connection_id != connection_id);
2075 inner
2076 .status
2077 .retain(|(key, _), _| key.connection_id != connection_id);
2078
2079 released
2080 }
2081
2082 pub(crate) fn escalate_client_delivery_failure(
2083 &self,
2084 connection_id: ConnectionId,
2085 channel: u16,
2086 expected_epoch: u32,
2087 reason: CloseReason,
2088 ) -> Result<bool, ForwardingError> {
2089 let should_close = {
2090 let mut inner = self.write_inner()?;
2091 let key = ClientRouteKey {
2092 connection_id,
2093 channel,
2094 };
2095 if inner.last_published_epoch.get(&key).copied() != Some(expected_epoch) {
2096 false
2097 } else {
2098 inner.closing_connections.insert(connection_id);
2099 true
2100 }
2101 };
2102 if should_close {
2103 self.request_connection_close(connection_id, reason);
2104 }
2105 Ok(should_close)
2106 }
2107
2108 fn record_route_release(&self, release: &RouteRelease) {
2109 match release {
2110 RouteRelease::Removed(_) => self.counters.increment_route_released_epoch_fenced(),
2111 RouteRelease::Stale => self.counters.increment_route_release_stale_skipped(),
2112 RouteRelease::Absent => {}
2113 }
2114 }
2115
2116 fn read_inner(&self) -> Result<RwLockReadGuard<'_, ForwardingInner>, ForwardingError> {
2117 self.inner.read().map_err(|_| ForwardingError::Poisoned)
2118 }
2119
2120 fn write_inner(&self) -> Result<RwLockWriteGuard<'_, ForwardingInner>, ForwardingError> {
2121 self.inner.write().map_err(|_| ForwardingError::Poisoned)
2122 }
2123
2124 fn lock_close_registry(
2125 &self,
2126 ) -> MutexGuard<'_, HashMap<ConnectionId, oneshot::Sender<CloseReason>>> {
2127 self.close_registry
2128 .lock()
2129 .unwrap_or_else(|poisoned| poisoned.into_inner())
2130 }
2131}
2132
2133impl ForwardingInner {
2134 fn allocate_route_slots(
2135 &mut self,
2136 connection_id: ConnectionId,
2137 endpoint: ModuleEndpointId,
2138 ) -> Result<(u16, u32, u16, u32), ForwardingError> {
2139 let client_start = *self.next_client_channel.entry(connection_id).or_insert(1);
2140 let mut client_channel = client_start;
2141 let client_channel = loop {
2142 let key = ClientRouteKey {
2143 connection_id,
2144 channel: client_channel,
2145 };
2146 let eligible = !self.client_to_module.contains_key(&key)
2147 && !self.reserved_client.contains_key(&key)
2148 && self.client_slot_epochs.get(&key).copied().unwrap_or(0) < u32::MAX;
2149 if eligible {
2150 break client_channel;
2151 }
2152 client_channel = next_channel(client_channel);
2153 if client_channel == client_start {
2154 return Err(ForwardingError::ClientRouteChannelExhausted { connection_id });
2155 }
2156 };
2157
2158 let module_start = *self.next_module_channel.entry(endpoint).or_insert(1);
2159 let mut module_channel = module_start;
2160 let module_channel = loop {
2161 let key = ModuleRouteKey {
2162 endpoint,
2163 channel: module_channel,
2164 };
2165 let eligible = !self.module_to_client.contains_key(&key)
2166 && !self.reserved_module.contains_key(&key)
2167 && self.module_slot_epochs.get(&key).copied().unwrap_or(0) < u32::MAX;
2168 if eligible {
2169 break module_channel;
2170 }
2171 module_channel = next_channel(module_channel);
2172 if module_channel == module_start {
2173 return Err(ForwardingError::ModuleRouteChannelExhausted { endpoint });
2174 }
2175 };
2176
2177 let client_key = ClientRouteKey {
2178 connection_id,
2179 channel: client_channel,
2180 };
2181 let module_key = ModuleRouteKey {
2182 endpoint,
2183 channel: module_channel,
2184 };
2185 let client_epoch = self
2186 .client_slot_epochs
2187 .get(&client_key)
2188 .copied()
2189 .unwrap_or(0)
2190 + 1;
2191 let module_epoch = self
2192 .module_slot_epochs
2193 .get(&module_key)
2194 .copied()
2195 .unwrap_or(0)
2196 + 1;
2197 self.client_slot_epochs.insert(client_key, client_epoch);
2198 self.module_slot_epochs.insert(module_key, module_epoch);
2199 self.next_client_channel
2200 .insert(connection_id, next_channel(client_channel));
2201 self.next_module_channel
2202 .insert(endpoint, next_channel(module_channel));
2203 Ok((client_channel, client_epoch, module_channel, module_epoch))
2204 }
2205
2206 fn allocate_control_corr(
2207 &mut self,
2208 endpoint: ModuleEndpointId,
2209 ) -> Result<u64, ForwardingError> {
2210 let candidate = self.next_control_corr.get(&endpoint).copied().unwrap_or(1);
2211 if candidate == 0 {
2212 self.closing_connections.insert(endpoint.connection_id);
2213 return Err(ForwardingError::RelayCorrelationExhausted);
2214 }
2215 self.next_control_corr.insert(
2216 endpoint,
2217 if candidate == u64::MAX {
2218 0
2219 } else {
2220 candidate + 1
2221 },
2222 );
2223 Ok(candidate)
2224 }
2225}
2226
2227fn next_channel(channel: u16) -> u16 {
2228 let next = channel.wrapping_add(1);
2229 if next == 0 {
2230 1
2231 } else {
2232 next
2233 }
2234}
2235
2236fn endpoint_routes_locked(
2237 inner: &ForwardingInner,
2238 endpoint: ModuleEndpointId,
2239) -> Vec<EndpointRoute> {
2240 let drain_reason = inner.draining_endpoints.get(&endpoint).copied();
2241 let draining = drain_reason.is_some();
2242 let mut routes = inner
2243 .module_to_client
2244 .iter()
2245 .filter(|(module_key, _)| module_key.endpoint == endpoint)
2246 .map(|(_, route)| EndpointRoute {
2247 goodbye_target: GoodbyeTarget {
2248 connection_id: route.client_connection_id,
2249 sink: route.client_sink.clone(),
2250 negotiated_ver: route.client_negotiated_ver,
2251 channel: route.client_channel,
2252 epoch: route.client_epoch,
2253 kind: GoodbyeTargetKind::Client,
2254 module_id: None,
2255 },
2256 principal: route.principal.clone(),
2257 bound_at: route.bound_at,
2258 draining,
2259 drain_reason,
2260 })
2261 .collect::<Vec<_>>();
2262 routes.sort_by_key(|route| {
2263 (
2264 route.goodbye_target.connection_id.get(),
2265 route.goodbye_target.channel,
2266 route.goodbye_target.epoch,
2267 )
2268 });
2269 routes
2270}
2271
2272fn release_reserved_route_locked(
2273 inner: &mut ForwardingInner,
2274 client_key: ClientRouteKey,
2275 module_key: ModuleRouteKey,
2276) {
2277 if inner.reserved_client.get(&client_key).copied() == Some(module_key) {
2278 inner.reserved_client.remove(&client_key);
2279 }
2280 if inner.reserved_module.get(&module_key).copied() == Some(client_key) {
2281 inner.reserved_module.remove(&module_key);
2282 }
2283 inner.status.retain(|(key, _), _| *key != client_key);
2284}
2285
2286fn release_client_route_locked(
2287 inner: &mut ForwardingInner,
2288 client_key: ClientRouteKey,
2289 expected_epoch: u32,
2290) -> RouteRelease {
2291 let Some(route) = inner.client_to_module.get(&client_key) else {
2292 return RouteRelease::Absent;
2293 };
2294 if route.client_epoch != expected_epoch {
2295 return RouteRelease::Stale;
2296 }
2297 let route = inner
2298 .client_to_module
2299 .remove(&client_key)
2300 .expect("route checked under the same forwarding lock");
2301 route.flow.close();
2302 inner.module_to_client.remove(&ModuleRouteKey {
2303 endpoint: route.module_endpoint,
2304 channel: route.module_channel,
2305 });
2306 inner.status.remove(&(client_key, expected_epoch));
2307 RouteRelease::Removed(GoodbyeTarget {
2308 connection_id: route.module_endpoint.connection_id,
2309 sink: route.module_sink.clone(),
2310 negotiated_ver: route.module_negotiated_ver,
2311 channel: route.module_channel,
2312 epoch: route.module_epoch,
2313 kind: GoodbyeTargetKind::Module,
2314 module_id: Some(route.module_id.clone()),
2315 })
2316}
2317
2318fn release_module_route_locked(
2319 inner: &mut ForwardingInner,
2320 module_key: ModuleRouteKey,
2321 expected_epoch: u32,
2322) -> RouteRelease {
2323 let Some(route) = inner.module_to_client.get(&module_key) else {
2324 return RouteRelease::Absent;
2325 };
2326 if route.module_epoch != expected_epoch {
2327 return RouteRelease::Stale;
2328 }
2329 let route = inner
2330 .module_to_client
2331 .remove(&module_key)
2332 .expect("route checked under the same forwarding lock");
2333 route.flow.close();
2334 let client_key = ClientRouteKey {
2335 connection_id: route.client_connection_id,
2336 channel: route.client_channel,
2337 };
2338 inner.client_to_module.remove(&client_key);
2339 inner.status.remove(&(client_key, route.client_epoch));
2340 RouteRelease::Removed(GoodbyeTarget {
2341 connection_id: route.client_connection_id,
2342 sink: route.client_sink.clone(),
2343 negotiated_ver: route.client_negotiated_ver,
2344 channel: route.client_channel,
2345 epoch: route.client_epoch,
2346 kind: GoodbyeTargetKind::Client,
2347 module_id: None,
2348 })
2349}
2350
2351fn commit_route_locked(
2352 inner: &mut ForwardingInner,
2353 pending: PendingRouteBindRelayEntry,
2354) -> Result<Option<GoodbyeTarget>, ForwardingError> {
2355 let reservation = pending.reservation;
2356 if inner
2357 .closing_connections
2358 .contains(&reservation.client_key.connection_id)
2359 {
2360 return Err(ForwardingError::ConnectionClosing {
2361 connection_id: reservation.client_key.connection_id,
2362 });
2363 }
2364 let module_id = inner
2365 .module_id_by_endpoint
2366 .get(&reservation.module_key.endpoint)
2367 .cloned()
2368 .ok_or(ForwardingError::StaleModuleEndpoint)?;
2369 if inner
2370 .draining_endpoints
2371 .contains_key(&reservation.module_key.endpoint)
2372 {
2373 return Err(ForwardingError::ModuleReloading { module_id });
2374 }
2375 if inner.reserved_client.remove(&reservation.client_key) != Some(reservation.module_key)
2376 || inner.reserved_module.remove(&reservation.module_key) != Some(reservation.client_key)
2377 {
2378 return Err(ForwardingError::UnknownReservation {
2379 client_channel: reservation.client_key.channel,
2380 module_channel: reservation.module_key.channel,
2381 });
2382 }
2383 let module = inner
2384 .modules_by_id
2385 .get(&module_id)
2386 .filter(|module| module.endpoint == reservation.module_key.endpoint)
2387 .cloned()
2388 .ok_or(ForwardingError::StaleModuleEndpoint)?;
2389 let binding = Arc::new(RouteBinding {
2390 client_connection_id: reservation.client_key.connection_id,
2391 client_sink: pending.client_sink,
2392 client_negotiated_ver: pending.client_negotiated_ver,
2393 client_channel: reservation.client_key.channel,
2394 client_epoch: reservation.client_epoch,
2395 module_id,
2396 module_endpoint: reservation.module_key.endpoint,
2397 module_sink: module.sink,
2398 module_negotiated_ver: module.negotiated_ver,
2399 module_channel: reservation.module_key.channel,
2400 module_epoch: reservation.module_epoch,
2401 principal: pending.principal,
2402 project_root: reservation.project_root.clone(),
2403 bound_at: Instant::now(),
2404 flow: Arc::new(ChannelFlow::new(window_for(&module.concurrency))),
2405 });
2406 inner
2407 .client_to_module
2408 .insert(reservation.client_key, Arc::clone(&binding));
2409 inner
2410 .module_to_client
2411 .insert(reservation.module_key, binding);
2412 let previous_published = inner
2413 .last_published_epoch
2414 .insert(reservation.client_key, reservation.client_epoch);
2415
2416 let client_sender = pending.client_permit.send(crate::router::OutboundFrame {
2421 frame: pending.route_open_frame,
2422 enqueued_at: std::time::Instant::now(),
2423 flushed: None,
2424 });
2425 if client_sender.is_closed() {
2426 let abandoned = pending
2427 .relay_enqueued
2428 .then(|| abandoned_route_target(inner, &reservation))
2429 .flatten();
2430 if let Some(route) = inner.client_to_module.remove(&reservation.client_key) {
2431 route.flow.close();
2432 }
2433 inner.module_to_client.remove(&reservation.module_key);
2434 inner
2435 .status
2436 .remove(&(reservation.client_key, reservation.client_epoch));
2437 match previous_published {
2438 Some(epoch) => {
2439 inner
2440 .last_published_epoch
2441 .insert(reservation.client_key, epoch);
2442 }
2443 None => {
2444 inner.last_published_epoch.remove(&reservation.client_key);
2445 }
2446 }
2447 let _ = pending.sender.send(RouteBindRelayOutcome::ModuleGone(
2448 "client egress closed during route publication".to_string(),
2449 ));
2450 return Ok(abandoned);
2451 }
2452
2453 let _ = pending.sender.send(RouteBindRelayOutcome::Accepted);
2454 Ok(None)
2455}
2456
2457fn module_connection_for_endpoint_locked(
2465 inner: &ForwardingInner,
2466 endpoint: ModuleEndpointId,
2467) -> Option<&ModuleConnection> {
2468 let module_id = inner.module_id_by_endpoint.get(&endpoint)?;
2469 inner
2470 .modules_by_id
2471 .get(module_id)
2472 .filter(|module| module.endpoint == endpoint)
2473 .or_else(|| {
2474 inner
2475 .candidates_by_id
2476 .get(module_id)
2477 .filter(|module| module.endpoint == endpoint)
2478 })
2479 .or_else(|| inner.superseded_endpoints.get(&endpoint))
2480}
2481
2482fn abandoned_route_target(
2483 inner: &ForwardingInner,
2484 reservation: &RouteReservation,
2485) -> Option<GoodbyeTarget> {
2486 let module_id = inner
2487 .module_id_by_endpoint
2488 .get(&reservation.module_key.endpoint)?;
2489 let module = module_connection_for_endpoint_locked(inner, reservation.module_key.endpoint)?;
2490 (module.endpoint == reservation.module_key.endpoint).then(|| GoodbyeTarget {
2491 connection_id: module.endpoint.connection_id,
2492 sink: module.sink.clone(),
2493 negotiated_ver: module.negotiated_ver,
2494 channel: reservation.module_key.channel,
2495 epoch: reservation.module_epoch,
2496 kind: GoodbyeTargetKind::Module,
2497 module_id: Some(module_id.clone()),
2498 })
2499}
2500
2501fn remove_module_connection_locked(
2502 inner: &mut ForwardingInner,
2503 endpoint: ModuleEndpointId,
2504) -> Vec<GoodbyeTarget> {
2505 inner.draining_endpoints.remove(&endpoint);
2506 let module_id = inner.module_id_by_endpoint.remove(&endpoint);
2507 if let Some(module_id) = module_id.as_ref() {
2508 if inner
2509 .modules_by_id
2510 .get(module_id)
2511 .is_some_and(|module| module.endpoint == endpoint)
2512 {
2513 inner.modules_by_id.remove(module_id);
2514 }
2515 if inner
2516 .candidates_by_id
2517 .get(module_id)
2518 .is_some_and(|module| module.endpoint == endpoint)
2519 {
2520 inner.candidates_by_id.remove(module_id);
2521 }
2522 }
2523 inner.superseded_endpoints.remove(&endpoint);
2524 inner.endpoint_by_connection.remove(&endpoint.connection_id);
2525 inner.next_module_channel.remove(&endpoint);
2526 inner.next_control_corr.remove(&endpoint);
2527 inner
2528 .health_probe_tombstones
2529 .retain(|(pending_endpoint, _), _| *pending_endpoint != endpoint);
2530 inner
2531 .module_slot_epochs
2532 .retain(|key, _| key.endpoint != endpoint);
2533 let reserved_module_keys: Vec<ModuleRouteKey> = inner
2534 .reserved_module
2535 .keys()
2536 .filter(|module_key| module_key.endpoint == endpoint)
2537 .copied()
2538 .collect();
2539 for module_key in reserved_module_keys {
2540 if let Some(client_key) = inner.reserved_module.get(&module_key).copied() {
2541 release_reserved_route_locked(inner, client_key, module_key);
2542 }
2543 }
2544
2545 let pending_keys: Vec<_> = inner
2546 .pending_relays
2547 .keys()
2548 .filter(|(pending_endpoint, _)| *pending_endpoint == endpoint)
2549 .copied()
2550 .collect();
2551 let pending: Vec<_> = pending_keys
2552 .into_iter()
2553 .filter_map(|key| inner.pending_relays.remove(&key))
2554 .collect();
2555 for pending in pending {
2556 let module_label = module_id.as_deref().unwrap_or("unknown");
2557 let _ = pending
2558 .sender
2559 .send(RouteBindRelayOutcome::ModuleGone(format!(
2560 "module '{module_label}' connection closed during route.bind relay"
2561 )));
2562 }
2563
2564 let pending_control_keys: Vec<_> = inner
2565 .pending_control_rpcs
2566 .keys()
2567 .filter(|(pending_endpoint, _)| *pending_endpoint == endpoint)
2568 .copied()
2569 .collect();
2570 let pending_control: Vec<_> = pending_control_keys
2571 .into_iter()
2572 .filter_map(|key| inner.pending_control_rpcs.remove(&key))
2573 .collect();
2574 for pending in pending_control {
2575 let module_label = module_id.as_deref().unwrap_or("unknown");
2576 let _ = pending
2577 .sender
2578 .send(ModuleControlRpcOutcome::ModuleGone(format!(
2579 "module '{module_label}' connection closed during module-control RPC"
2580 )));
2581 }
2582
2583 let module_routes = inner
2584 .module_to_client
2585 .iter()
2586 .filter(|(module_key, _)| module_key.endpoint == endpoint)
2587 .map(|(module_key, route)| (*module_key, route.module_epoch))
2588 .collect::<Vec<_>>();
2589 let mut released = Vec::with_capacity(module_routes.len());
2590 for (module_key, epoch) in module_routes {
2591 if let RouteRelease::Removed(target) = release_module_route_locked(inner, module_key, epoch)
2592 {
2593 released.push(target);
2594 }
2595 }
2596 released
2597}
2598
2599#[derive(Debug, Clone, Copy)]
2600struct RequestCredit {
2601 subscription: bool,
2602 excluded_from_drain: bool,
2603}
2604
2605#[derive(Debug, Default)]
2606struct CreditLedger {
2607 by_corr: HashMap<u64, Vec<RequestCredit>>,
2608}
2609
2610impl CreditLedger {
2611 fn acquire(&mut self, corr: u64, subscription: bool) {
2612 self.by_corr.entry(corr).or_default().push(RequestCredit {
2613 subscription,
2614 excluded_from_drain: false,
2615 });
2616 }
2617
2618 fn release(&mut self, corr: u64) -> bool {
2619 let Some(credits) = self.by_corr.get_mut(&corr) else {
2620 return false;
2621 };
2622 let released = credits.pop().is_some();
2623 if credits.is_empty() {
2624 self.by_corr.remove(&corr);
2625 }
2626 released
2627 }
2628
2629 fn capture_subscription_exclusions(&mut self) -> u32 {
2630 let mut excluded = 0u32;
2631 for credit in self.by_corr.values_mut().flatten() {
2632 if credit.subscription && !credit.excluded_from_drain {
2633 credit.excluded_from_drain = true;
2634 excluded = excluded.saturating_add(1);
2635 }
2636 }
2637 excluded
2638 }
2639
2640 #[cfg(test)]
2641 fn in_flight(&self) -> usize {
2642 self.by_corr.values().map(Vec::len).sum()
2643 }
2644
2645 fn drain_in_flight(&self) -> usize {
2646 self.by_corr
2647 .values()
2648 .flatten()
2649 .filter(|credit| !credit.excluded_from_drain)
2650 .count()
2651 }
2652
2653 fn drain_held_corrs(&self) -> Vec<u64> {
2656 let mut corrs = self
2657 .by_corr
2658 .iter()
2659 .flat_map(|(corr, credits)| {
2660 credits
2661 .iter()
2662 .filter(|credit| !credit.excluded_from_drain)
2663 .map(move |_| *corr)
2664 })
2665 .collect::<Vec<_>>();
2666 corrs.sort_unstable();
2667 corrs
2668 }
2669}
2670
2671#[derive(Debug, Default)]
2672struct ChannelFlowState {
2673 closed: bool,
2674 credits: CreditLedger,
2675}
2676
2677#[derive(Debug)]
2679pub(crate) struct ChannelFlow {
2680 sem: Semaphore,
2681 window: usize,
2682 state: Mutex<ChannelFlowState>,
2683}
2684
2685impl ChannelFlow {
2686 pub(crate) fn new(window: usize) -> Self {
2687 debug_assert!(window > 0, "flow-control window must be non-zero");
2688 Self {
2689 sem: Semaphore::new(window),
2690 window,
2691 state: Mutex::new(ChannelFlowState::default()),
2692 }
2693 }
2694
2695 #[cfg(test)]
2696 pub(crate) async fn acquire(&self) -> Result<(), ChannelFlowClosed> {
2697 self.acquire_tagged(0, false).await
2698 }
2699
2700 pub(crate) async fn acquire_tagged(
2701 &self,
2702 corr: u64,
2703 subscription: bool,
2704 ) -> Result<(), ChannelFlowClosed> {
2705 let permit = self.sem.acquire().await.map_err(|_| ChannelFlowClosed)?;
2706 let mut state = self
2707 .state
2708 .lock()
2709 .unwrap_or_else(|poisoned| poisoned.into_inner());
2710 if state.closed {
2711 return Err(ChannelFlowClosed);
2712 }
2713 state.credits.acquire(corr, subscription);
2714 permit.forget();
2715 Ok(())
2716 }
2717
2718 #[cfg(test)]
2719 pub(crate) fn release(&self) {
2720 self.release_corr(0);
2721 }
2722
2723 pub(crate) fn release_corr(&self, corr: u64) {
2724 let released = self
2725 .state
2726 .lock()
2727 .unwrap_or_else(|poisoned| poisoned.into_inner())
2728 .credits
2729 .release(corr);
2730 if !released {
2731 warn!(
2735 window = self.window,
2736 available = self.sem.available_permits(),
2737 "flow-control over-release ignored"
2738 );
2739 return;
2740 }
2741 if !self.sem.is_closed() {
2742 self.sem.add_permits(1);
2743 }
2744 }
2745
2746 #[cfg(test)]
2747 pub(crate) fn in_flight(&self) -> usize {
2748 self.state
2749 .lock()
2750 .unwrap_or_else(|poisoned| poisoned.into_inner())
2751 .credits
2752 .in_flight()
2753 }
2754
2755 pub(crate) fn drain_in_flight(&self) -> usize {
2756 self.state
2757 .lock()
2758 .unwrap_or_else(|poisoned| poisoned.into_inner())
2759 .credits
2760 .drain_in_flight()
2761 }
2762
2763 pub(crate) fn drain_held_corrs(&self) -> Vec<u64> {
2764 self.state
2765 .lock()
2766 .unwrap_or_else(|poisoned| poisoned.into_inner())
2767 .credits
2768 .drain_held_corrs()
2769 }
2770
2771 #[cfg(test)]
2772 pub(crate) fn available_permits(&self) -> usize {
2773 self.sem.available_permits()
2774 }
2775
2776 pub(crate) fn begin_drain(&self) -> u32 {
2777 let mut state = self
2778 .state
2779 .lock()
2780 .unwrap_or_else(|poisoned| poisoned.into_inner());
2781 state.closed = true;
2782 self.sem.close();
2783 state.credits.capture_subscription_exclusions()
2784 }
2785
2786 pub(crate) fn close(&self) {
2787 self.state
2788 .lock()
2789 .unwrap_or_else(|poisoned| poisoned.into_inner())
2790 .closed = true;
2791 self.sem.close();
2792 }
2793}
2794
2795#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2796pub(crate) struct ChannelFlowClosed;
2797
2798impl fmt::Display for ChannelFlowClosed {
2799 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2800 write!(f, "flow-control window closed")
2801 }
2802}
2803
2804impl Error for ChannelFlowClosed {}
2805
2806fn window_for(concurrency: &Concurrency) -> usize {
2807 match concurrency {
2808 Concurrency::Serial => 1,
2809 Concurrency::ModuleManaged => DEFAULT_MODULE_MANAGED_WINDOW,
2810 Concurrency::StatelessParallel => STATELESS_PARALLEL_WINDOW,
2811 }
2812}
2813
2814#[derive(Debug, Clone, PartialEq, Eq)]
2815pub enum ForwardingError {
2816 NoModuleConnection,
2817 ModuleReloading {
2818 module_id: String,
2819 },
2820 StaleModuleEndpoint,
2821 UnknownReservation {
2822 client_channel: u16,
2823 module_channel: u16,
2824 },
2825 ClientRouteChannelExhausted {
2826 connection_id: ConnectionId,
2827 },
2828 ModuleRouteChannelExhausted {
2829 endpoint: ModuleEndpointId,
2830 },
2831 RelayCorrelationExhausted,
2832 ConnectionClosing {
2833 connection_id: ConnectionId,
2834 },
2835 ClientEgressClosed {
2836 connection_id: ConnectionId,
2837 },
2838 RouteOpenBuild(String),
2839 CandidateSlotOccupied {
2841 module_id: String,
2842 },
2843 Poisoned,
2844}
2845
2846impl fmt::Display for ForwardingError {
2847 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2848 match self {
2849 Self::NoModuleConnection => write!(f, "no module connection is registered"),
2850 Self::ModuleReloading { module_id } => {
2851 write!(f, "module_id '{module_id}' is reloading")
2852 }
2853 Self::StaleModuleEndpoint => write!(f, "module connection generation is stale"),
2854 Self::UnknownReservation {
2855 client_channel,
2856 module_channel,
2857 } => write!(
2858 f,
2859 "route reservation client channel {client_channel} / module channel {module_channel} was not found"
2860 ),
2861 Self::ClientRouteChannelExhausted { connection_id } => write!(
2862 f,
2863 "no client route channels are available for connection {}",
2864 connection_id.get()
2865 ),
2866 Self::ModuleRouteChannelExhausted { endpoint } => write!(
2867 f,
2868 "no module route channels are available for endpoint generation {} on connection {}",
2869 endpoint.generation,
2870 endpoint.connection_id.get()
2871 ),
2872 Self::RelayCorrelationExhausted => {
2873 write!(f, "module control correlation ids are exhausted")
2874 }
2875 Self::ConnectionClosing { connection_id } => write!(
2876 f,
2877 "connection {} is closing and cannot accept route allocation",
2878 connection_id.get()
2879 ),
2880 Self::ClientEgressClosed { connection_id } => write!(
2881 f,
2882 "client connection {} egress is closed",
2883 connection_id.get()
2884 ),
2885 Self::RouteOpenBuild(message) => {
2886 write!(f, "failed to prebuild route.open response: {message}")
2887 }
2888 Self::CandidateSlotOccupied { module_id } => write!(
2889 f,
2890 "module_id '{module_id}' already has a swap candidate registered"
2891 ),
2892 Self::Poisoned => write!(f, "forwarding table lock was poisoned"),
2893 }
2894 }
2895}
2896
2897impl Error for ForwardingError {}
2898
2899#[cfg(test)]
2900mod tests {
2901 use std::time::Duration;
2902
2903 use super::*;
2904 use tokio::sync::mpsc;
2905
2906 #[test]
2907 fn ordinary_long_running_request_is_not_excluded_from_drain() {
2908 let mut ledger = CreditLedger::default();
2909 ledger.acquire(1, false);
2910
2911 assert_eq!(ledger.capture_subscription_exclusions(), 0);
2912 assert_eq!(ledger.drain_in_flight(), 1);
2913 }
2914
2915 #[test]
2916 fn bit_set_subscription_is_excluded_and_counted() {
2917 let mut ledger = CreditLedger::default();
2918 ledger.acquire(1, true);
2919
2920 assert_eq!(ledger.capture_subscription_exclusions(), 1);
2921 assert_eq!(ledger.drain_in_flight(), 0);
2922 }
2923
2924 #[test]
2925 fn subscription_opened_after_drain_snapshot_is_not_excluded() {
2926 let mut ledger = CreditLedger::default();
2927 ledger.acquire(1, true);
2928 assert_eq!(ledger.capture_subscription_exclusions(), 1);
2929
2930 ledger.acquire(2, true);
2931
2932 assert_eq!(ledger.drain_in_flight(), 1);
2933 }
2934
2935 #[test]
2936 fn drain_with_no_subscriptions_reports_zero_excluded() {
2937 let mut ledger = CreditLedger::default();
2938 assert_eq!(ledger.capture_subscription_exclusions(), 0);
2939 }
2940
2941 #[test]
2942 fn multi_provider_route_limit_reports_per_client_exhaustion_without_affecting_second_client() {
2943 let forwarding = ForwardingTable::default();
2944 let module_connection = ConnectionId::new(10);
2945 let exhausted_client = ConnectionId::new(20);
2946 let second_client = ConnectionId::new(30);
2947 let (module_tx, _module_rx) = mpsc::channel(1);
2948 let endpoint = forwarding
2949 .register_module_connection(
2950 module_connection,
2951 "route-limit-provider".to_string(),
2952 1,
2953 Concurrency::ModuleManaged,
2954 FrameSink::new(module_tx),
2955 )
2956 .unwrap();
2957
2958 {
2959 let mut inner = forwarding.inner.write().unwrap();
2960 for channel in 1..=u16::MAX {
2961 inner.reserved_client.insert(
2962 ClientRouteKey {
2963 connection_id: exhausted_client,
2964 channel,
2965 },
2966 ModuleRouteKey {
2967 endpoint,
2968 channel: 1,
2969 },
2970 );
2971 }
2972 }
2973
2974 let (exhausted_tx, _exhausted_rx) = mpsc::channel(1);
2975 let err = forwarding
2976 .begin_route_bind_relay_for_test(
2977 exhausted_client,
2978 FrameSink::new(exhausted_tx),
2979 1,
2980 "route-limit-provider",
2981 )
2982 .unwrap_err();
2983 assert!(matches!(
2984 err,
2985 ForwardingError::ClientRouteChannelExhausted { connection_id }
2986 if connection_id == exhausted_client
2987 ));
2988
2989 let (second_tx, _second_rx) = mpsc::channel(1);
2990 let pending = forwarding
2991 .begin_route_bind_relay_for_test(
2992 second_client,
2993 FrameSink::new(second_tx),
2994 2,
2995 "route-limit-provider",
2996 )
2997 .unwrap();
2998 assert_eq!(pending.client_channel, 1);
2999 }
3000
3001 #[test]
3002 fn released_module_channels_are_reused_after_wrap_without_slot_leak() {
3003 let forwarding = ForwardingTable::default();
3004 let module_connection = ConnectionId::new(40);
3005 let client = ConnectionId::new(50);
3006 let (module_tx, _module_rx) = mpsc::channel(1);
3007 forwarding
3008 .register_module_connection(
3009 module_connection,
3010 "slot-reuse-provider".to_string(),
3011 1,
3012 Concurrency::ModuleManaged,
3013 FrameSink::new(module_tx),
3014 )
3015 .unwrap();
3016
3017 let (client_tx, _client_rx) = mpsc::channel(1);
3018 let client_sink = FrameSink::new(client_tx);
3019 let mut wrapped_channel = None;
3020 for index in 0..=usize::from(u16::MAX) {
3021 let pending = forwarding
3022 .begin_route_bind_relay_for_test(
3023 client,
3024 client_sink.clone(),
3025 index as u64 + 1,
3026 "slot-reuse-provider",
3027 )
3028 .unwrap();
3029 if index == usize::from(u16::MAX) {
3030 wrapped_channel = Some(pending.module_channel);
3031 }
3032 forwarding
3033 .abort_pending_relay(
3034 pending.endpoint,
3035 pending.corr,
3036 RouteBindRelayOutcome::ModuleGone("test abort".to_string()),
3037 )
3038 .unwrap();
3039 }
3040
3041 assert_eq!(wrapped_channel, Some(1));
3042 }
3043
3044 #[test]
3045 fn cleanup_connection_prunes_stale_next_client_channel_cursor() {
3046 let forwarding = ForwardingTable::default();
3047 let client = ConnectionId::new(60);
3048 forwarding
3049 .inner
3050 .write()
3051 .unwrap()
3052 .next_client_channel
3053 .insert(client, 41);
3054
3055 let released = forwarding.cleanup_connection(client).unwrap();
3056
3057 assert!(released.is_empty());
3058 assert!(!forwarding
3059 .inner
3060 .read()
3061 .unwrap()
3062 .next_client_channel
3063 .contains_key(&client));
3064 }
3065
3066 #[test]
3067 fn stale_module_cleanup_preserves_fast_reconnect_successor() {
3068 let forwarding = ForwardingTable::default();
3069 let module_id = "fast-reconnect-provider";
3070 let first_connection = ConnectionId::new(70);
3071 let second_connection = ConnectionId::new(80);
3072 let (first_tx, _first_rx) = mpsc::channel(1);
3073 let first_endpoint = forwarding
3074 .register_module_connection(
3075 first_connection,
3076 module_id.to_string(),
3077 1,
3078 Concurrency::ModuleManaged,
3079 FrameSink::new(first_tx),
3080 )
3081 .unwrap();
3082 let (second_tx, _second_rx) = mpsc::channel(1);
3083 let second_endpoint = forwarding
3084 .register_module_connection(
3085 second_connection,
3086 module_id.to_string(),
3087 1,
3088 Concurrency::ModuleManaged,
3089 FrameSink::new(second_tx),
3090 )
3091 .unwrap();
3092 assert_ne!(first_endpoint, second_endpoint);
3093
3094 let released = forwarding.cleanup_connection(first_connection).unwrap();
3095
3096 assert!(released.is_empty());
3097 assert_eq!(
3098 forwarding
3099 .inner
3100 .read()
3101 .unwrap()
3102 .modules_by_id
3103 .get(module_id)
3104 .map(|module| module.endpoint),
3105 Some(second_endpoint)
3106 );
3107 assert!(forwarding.has_live_module_connection(module_id).unwrap());
3108 let control_rpc = forwarding
3109 .begin_module_control_rpc_for(
3110 module_id,
3111 "health.check",
3112 Instant::now() + Duration::from_secs(1),
3113 )
3114 .unwrap();
3115 assert_eq!(control_rpc.endpoint, second_endpoint);
3116 }
3117
3118 fn route_fixture(
3119 module_id: &str,
3120 ) -> (
3121 ForwardingTable,
3122 ConnectionId,
3123 ModuleEndpointId,
3124 ConnectionId,
3125 FrameSink,
3126 mpsc::Receiver<crate::router::OutboundFrame>,
3127 ) {
3128 let forwarding = ForwardingTable::default();
3129 let module_connection = ConnectionId::new(100);
3130 let client_connection = ConnectionId::new(200);
3131 let (module_tx, _module_rx) = mpsc::channel(8);
3132 let endpoint = forwarding
3133 .register_module_connection(
3134 module_connection,
3135 module_id.to_string(),
3136 2,
3137 Concurrency::ModuleManaged,
3138 FrameSink::new(module_tx),
3139 )
3140 .unwrap();
3141 let (client_tx, client_rx) = mpsc::channel(8);
3142 (
3143 forwarding,
3144 module_connection,
3145 endpoint,
3146 client_connection,
3147 FrameSink::new(client_tx),
3148 client_rx,
3149 )
3150 }
3151
3152 #[test]
3153 #[cfg(unix)]
3154 fn daemon_drain_gates_current_and_racing_provider_registrations() {
3155 let (forwarding, _, endpoint, _, sink, _) = route_fixture("provider");
3156 assert_eq!(forwarding.begin_daemon_drain().unwrap(), ["provider"]);
3157 assert!(forwarding.endpoint_is_draining(endpoint).unwrap());
3158 assert!(matches!(
3159 forwarding.register_module_connection(
3160 ConnectionId::new(300),
3161 "late-provider".into(),
3162 2,
3163 Concurrency::ModuleManaged,
3164 sink,
3165 ),
3166 Err(ForwardingError::ConnectionClosing { .. })
3167 ));
3168 }
3169
3170 fn test_ping(corr: u64) -> Frame {
3171 Frame::build(
3172 FrameType::Ping,
3173 Flags::new(false, Priority::Passive, false),
3174 0,
3175 0,
3176 corr,
3177 Vec::new(),
3178 )
3179 .unwrap()
3180 }
3181
3182 fn begin_test_route(
3183 forwarding: &ForwardingTable,
3184 client_connection: ConnectionId,
3185 client_sink: FrameSink,
3186 corr: u64,
3187 module_id: &str,
3188 ) -> PendingRouteBindRelay {
3189 forwarding
3190 .begin_route_bind_relay_for_test(client_connection, client_sink, corr, module_id)
3191 .unwrap()
3192 }
3193
3194 #[tokio::test]
3199 async fn drain_holdouts_count_held_requests_and_name_the_connection() {
3200 let (forwarding, module_connection, endpoint, client, sink, mut client_rx) =
3201 route_fixture("holdouts");
3202 let mut bound = |corr| {
3203 let route = begin_test_route(&forwarding, client, sink.clone(), corr, "holdouts");
3204 forwarding
3205 .complete_pending_relay(
3206 module_connection,
3207 route.corr,
3208 RouteBindRelayOutcome::Accepted,
3209 )
3210 .unwrap();
3211 client_rx.try_recv().unwrap();
3212 match forwarding
3213 .lookup_data_route(client, route.client_channel, route.client_epoch)
3214 .unwrap()
3215 {
3216 DataRoute::Client(DataRouteState::Bound(binding)) => binding,
3217 other => panic!("expected live route, got {other:?}"),
3218 }
3219 };
3220 let holding = bound(61);
3221 let _idle = bound(62);
3222 holding.flow.acquire_tagged(7, false).await.unwrap();
3223 holding.flow.acquire_tagged(2, false).await.unwrap();
3224 holding.flow.acquire_tagged(3, true).await.unwrap();
3225 forwarding
3226 .begin_module_drain("holdouts", RouteCloseReason::Restart)
3227 .unwrap();
3228
3229 let holdouts = forwarding.endpoint_drain_holdouts(endpoint).unwrap();
3230 assert_eq!(
3231 holdouts,
3232 DrainHoldouts {
3233 requests: 2,
3234 routes: 1,
3235 total_routes: 2,
3236 top_connections: vec![(client.get(), 2)],
3237 held: vec![(holding.module_channel, 2), (holding.module_channel, 7)],
3240 }
3241 );
3242 }
3243
3244 #[test]
3245 fn endpoint_routes_keep_goodbye_targets_and_mark_draining_routes() {
3246 let (forwarding, module_connection, endpoint, client, sink, _client_rx) =
3247 route_fixture("census");
3248 let pending = begin_test_route(&forwarding, client, sink, 1, "census");
3249 forwarding
3250 .complete_pending_relay(
3251 module_connection,
3252 pending.corr,
3253 RouteBindRelayOutcome::Accepted,
3254 )
3255 .unwrap();
3256
3257 let routes = forwarding.endpoint_routes(endpoint).unwrap();
3258 assert_eq!(routes.len(), 1);
3259 assert!(matches!(routes[0].principal, Principal::Direct));
3260 assert_eq!(routes[0].goodbye_target.connection_id, client);
3261 assert_eq!(routes[0].goodbye_target.channel, pending.client_channel);
3262 assert_eq!(routes[0].goodbye_target.epoch, pending.client_epoch);
3263 assert!(!routes[0].draining);
3264
3265 forwarding
3266 .begin_module_drain("census", RouteCloseReason::Restart)
3267 .unwrap();
3268 let draining_routes = forwarding.endpoint_routes(endpoint).unwrap();
3269 assert_eq!(draining_routes.len(), 1);
3270 assert!(draining_routes[0].draining);
3271 }
3272
3273 #[test]
3274 fn aborted_reservation_consumes_both_epochs_and_reuse_advances_them() {
3275 let (forwarding, _, endpoint, client, sink, _client_rx) = route_fixture("epoch-abort");
3276 let first = begin_test_route(&forwarding, client, sink.clone(), 1, "epoch-abort");
3277 assert_eq!((first.client_epoch, first.module_epoch), (1, 1));
3278 forwarding
3279 .abort_pending_relay(
3280 first.endpoint,
3281 first.corr,
3282 RouteBindRelayOutcome::ModuleGone("abort".into()),
3283 )
3284 .unwrap();
3285 forwarding.inject_client_slot_epoch(client, first.client_channel, first.client_epoch);
3286 forwarding.inject_module_slot_epoch(endpoint, first.module_channel, first.module_epoch);
3287
3288 let second = begin_test_route(&forwarding, client, sink, 2, "epoch-abort");
3289 assert_eq!(second.client_channel, first.client_channel);
3290 assert_eq!(second.module_channel, first.module_channel);
3291 assert_eq!((second.client_epoch, second.module_epoch), (2, 2));
3292 }
3293
3294 #[test]
3295 fn stale_release_cannot_remove_reused_successor_and_status_is_epoch_fenced() {
3296 let (forwarding, module_connection, endpoint, client, sink, mut client_rx) =
3297 route_fixture("epoch-release");
3298 let first = begin_test_route(&forwarding, client, sink.clone(), 10, "epoch-release");
3299 forwarding
3300 .complete_pending_relay(
3301 module_connection,
3302 first.corr,
3303 RouteBindRelayOutcome::Accepted,
3304 )
3305 .unwrap();
3306 assert_eq!(client_rx.try_recv().unwrap().header.corr, 10);
3307 assert!(matches!(
3308 forwarding
3309 .release_client_route(client, first.client_channel, first.client_epoch)
3310 .unwrap(),
3311 RouteRelease::Removed(_)
3312 ));
3313 forwarding.inject_client_slot_epoch(client, first.client_channel, first.client_epoch);
3314 forwarding.inject_module_slot_epoch(endpoint, first.module_channel, first.module_epoch);
3315
3316 let second = begin_test_route(&forwarding, client, sink, 11, "epoch-release");
3317 forwarding
3318 .complete_pending_relay(
3319 module_connection,
3320 second.corr,
3321 RouteBindRelayOutcome::Accepted,
3322 )
3323 .unwrap();
3324 assert_eq!(client_rx.try_recv().unwrap().header.corr, 11);
3325 assert!(matches!(
3326 forwarding
3327 .release_client_route(client, second.client_channel, first.client_epoch)
3328 .unwrap(),
3329 RouteRelease::Stale
3330 ));
3331 assert!(!forwarding
3332 .cache_status(
3333 endpoint,
3334 second.module_channel,
3335 first.module_epoch,
3336 "stale".into(),
3337 )
3338 .unwrap());
3339 assert!(forwarding
3340 .cache_status(
3341 endpoint,
3342 second.module_channel,
3343 second.module_epoch,
3344 "current".into(),
3345 )
3346 .unwrap());
3347 match forwarding
3348 .route_poll_snapshot(client, second.client_channel, second.client_epoch)
3349 .unwrap()
3350 {
3351 RoutePollSnapshot::Bound { status, .. } => {
3352 assert_eq!(status.as_deref(), Some("current"));
3353 }
3354 RoutePollSnapshot::Absent => panic!("successor binding was removed"),
3355 }
3356 let counters = forwarding.counters().snapshot();
3357 assert_eq!(counters["route_released_epoch_fenced"], 1);
3358 assert_eq!(counters["route_release_stale_skipped"], 1);
3359 }
3360
3361 #[test]
3362 fn max_epoch_reservation_retires_only_that_slot() {
3363 let (forwarding, _, endpoint, client, sink, _client_rx) = route_fixture("epoch-max");
3364 forwarding.inject_client_slot_epoch(client, 7, u32::MAX - 1);
3365 forwarding.inject_module_slot_epoch(endpoint, 9, u32::MAX - 1);
3366 let final_use = begin_test_route(&forwarding, client, sink.clone(), 20, "epoch-max");
3367 assert_eq!(
3368 (final_use.client_channel, final_use.client_epoch),
3369 (7, u32::MAX)
3370 );
3371 assert_eq!(
3372 (final_use.module_channel, final_use.module_epoch),
3373 (9, u32::MAX)
3374 );
3375 forwarding
3376 .abort_pending_relay(
3377 endpoint,
3378 final_use.corr,
3379 RouteBindRelayOutcome::ModuleGone("abort".into()),
3380 )
3381 .unwrap();
3382 forwarding.inject_client_slot_epoch(client, 7, u32::MAX);
3383 forwarding.inject_module_slot_epoch(endpoint, 9, u32::MAX);
3384 let next = begin_test_route(&forwarding, client, sink, 21, "epoch-max");
3385 assert_ne!(next.client_channel, 7);
3386 assert_ne!(next.module_channel, 9);
3387 assert_eq!((next.client_epoch, next.module_epoch), (1, 1));
3388 }
3389
3390 #[test]
3391 fn bind_and_module_control_share_monotonic_corr_and_deadline_arbitration() {
3392 let (forwarding, module_connection, endpoint, client, sink, _client_rx) =
3393 route_fixture("corr-shared");
3394 let bind = begin_test_route(&forwarding, client, sink, 30, "corr-shared");
3395 assert_eq!(bind.corr, 1);
3396 forwarding
3397 .abort_pending_relay(
3398 endpoint,
3399 bind.corr,
3400 RouteBindRelayOutcome::ModuleGone("abort".into()),
3401 )
3402 .unwrap();
3403 let rpc = forwarding
3404 .begin_module_control_rpc_for(
3405 "corr-shared",
3406 "health.check",
3407 Instant::now() - Duration::from_millis(1),
3408 )
3409 .unwrap();
3410 assert_eq!(rpc.corr, 2);
3411 assert_eq!(
3412 forwarding
3413 .complete_module_control_rpc(
3414 module_connection,
3415 rpc.corr,
3416 Some("health.check"),
3417 ModuleControlRpcOutcome::Response(ModuleControlResponse::HealthCheck {
3418 status: subc_protocol::session::HealthStatus::Ok,
3419 detail: None,
3420 metrics: None,
3421 }),
3422 )
3423 .unwrap(),
3424 ModuleControlRpcCompletion::Settled
3425 );
3426 assert!(matches!(
3427 rpc.receiver.blocking_recv().unwrap(),
3428 ModuleControlRpcOutcome::DeadlineElapsed
3429 ));
3430 }
3431
3432 #[tokio::test(start_paused = true)]
3433 async fn health_probe_tombstone_ttl_removes_an_endpoint_that_stops_probing() {
3434 let (forwarding, _, endpoint, _, _, _) = route_fixture("tombstone-ttl");
3435 let probe_started_at = Instant::now();
3436 let rpc = forwarding
3437 .begin_health_probe_rpc_for(
3438 "tombstone-ttl",
3439 "health.check",
3440 probe_started_at,
3441 probe_started_at + Duration::from_secs(5),
3442 )
3443 .unwrap();
3444 assert!(forwarding
3445 .tombstone_health_probe_rpc(endpoint, rpc.corr)
3446 .unwrap());
3447 assert_eq!(forwarding.health_probe_tombstone_count().unwrap(), 1);
3448
3449 tokio::time::advance(HEALTH_PROBE_TOMBSTONE_TTL).await;
3450 tokio::task::yield_now().await;
3451
3452 assert_eq!(forwarding.health_probe_tombstone_count().unwrap(), 0);
3453 }
3454
3455 #[test]
3456 fn correlation_exhaustion_emits_max_once_then_closes_endpoint() {
3457 let (forwarding, _, endpoint, _, _, _) = route_fixture("corr-max");
3458 let mut close = forwarding.register_connection_close(endpoint.connection_id);
3459 forwarding.inject_control_corr(endpoint, u64::MAX);
3460 let final_rpc = forwarding
3461 .begin_module_control_rpc_for(
3462 "corr-max",
3463 "health.check",
3464 Instant::now() + Duration::from_secs(1),
3465 )
3466 .unwrap();
3467 assert_eq!(final_rpc.corr, u64::MAX);
3468 forwarding
3469 .cancel_module_control_rpc(endpoint, final_rpc.corr)
3470 .unwrap();
3471 assert!(matches!(
3472 forwarding.begin_module_control_rpc_for(
3473 "corr-max",
3474 "health.check",
3475 Instant::now() + Duration::from_secs(1),
3476 ),
3477 Err(ForwardingError::RelayCorrelationExhausted)
3478 ));
3479 assert!(close.try_recv().is_ok());
3480 }
3481
3482 #[test]
3483 fn publication_epoch_controls_delivery_failure_escalation() {
3484 fn setup_successor(
3485 commit_successor: Option<bool>,
3486 ) -> (ForwardingTable, ConnectionId, u16, u32) {
3487 let (forwarding, module_connection, endpoint, client, sink, mut client_rx) =
3488 route_fixture("escalation");
3489 let first = begin_test_route(&forwarding, client, sink.clone(), 40, "escalation");
3490 forwarding
3491 .complete_pending_relay(
3492 module_connection,
3493 first.corr,
3494 RouteBindRelayOutcome::Accepted,
3495 )
3496 .unwrap();
3497 client_rx.try_recv().unwrap();
3498 assert!(matches!(
3499 forwarding
3500 .release_client_route(client, first.client_channel, first.client_epoch)
3501 .unwrap(),
3502 RouteRelease::Removed(_)
3503 ));
3504 if let Some(commit_successor) = commit_successor {
3505 forwarding.inject_client_slot_epoch(
3506 client,
3507 first.client_channel,
3508 first.client_epoch,
3509 );
3510 forwarding.inject_module_slot_epoch(
3511 endpoint,
3512 first.module_channel,
3513 first.module_epoch,
3514 );
3515 let successor = begin_test_route(&forwarding, client, sink, 41, "escalation");
3516 if commit_successor {
3517 forwarding
3518 .complete_pending_relay(
3519 module_connection,
3520 successor.corr,
3521 RouteBindRelayOutcome::Accepted,
3522 )
3523 .unwrap();
3524 client_rx.try_recv().unwrap();
3525 } else {
3526 forwarding
3527 .abort_pending_relay(
3528 endpoint,
3529 successor.corr,
3530 RouteBindRelayOutcome::ModuleGone("abort".into()),
3531 )
3532 .unwrap();
3533 }
3534 }
3535 (forwarding, client, first.client_channel, first.client_epoch)
3536 }
3537
3538 let (no_successor, client, channel, epoch) = setup_successor(None);
3539 let mut close = no_successor.register_connection_close(client);
3540 assert!(no_successor
3541 .escalate_client_delivery_failure(
3542 client,
3543 channel,
3544 epoch,
3545 CloseReason::new("delivery", "failed"),
3546 )
3547 .unwrap());
3548 assert!(close.try_recv().is_ok());
3549
3550 let (aborted, client, channel, epoch) = setup_successor(Some(false));
3551 let mut close = aborted.register_connection_close(client);
3552 assert!(aborted
3553 .escalate_client_delivery_failure(
3554 client,
3555 channel,
3556 epoch,
3557 CloseReason::new("delivery", "failed"),
3558 )
3559 .unwrap());
3560 assert!(close.try_recv().is_ok());
3561
3562 let (published, client, channel, epoch) = setup_successor(Some(true));
3563 let mut close = published.register_connection_close(client);
3564 assert!(!published
3565 .escalate_client_delivery_failure(
3566 client,
3567 channel,
3568 epoch,
3569 CloseReason::new("delivery", "stale failure"),
3570 )
3571 .unwrap());
3572 assert!(close.try_recv().is_err());
3573 }
3574
3575 #[test]
3576 fn route_concentration_separates_client_count_from_routes_per_client() {
3577 let (forwarding, module_connection, _, client, sink, _client_rx) =
3581 route_fixture("concentration");
3582 assert_eq!(forwarding.client_route_concentration().unwrap(), (0, 0));
3583
3584 for corr in [70_u64, 71] {
3585 let pending =
3586 begin_test_route(&forwarding, client, sink.clone(), corr, "concentration");
3587 forwarding
3588 .complete_pending_relay(
3589 module_connection,
3590 pending.corr,
3591 RouteBindRelayOutcome::Accepted,
3592 )
3593 .unwrap();
3594 }
3595
3596 assert_eq!(forwarding.active_binding_count().unwrap(), 2);
3598 assert_eq!(forwarding.client_route_concentration().unwrap(), (1, 2));
3599 }
3600
3601 #[test]
3602 fn cleanup_and_accepted_resolution_have_one_lock_winner() {
3603 let (forwarding, module_connection, _, client, sink, mut client_rx) =
3604 route_fixture("cleanup-race");
3605 let pending = begin_test_route(&forwarding, client, sink, 45, "cleanup-race");
3606 forwarding
3607 .mark_route_bind_relay_enqueued(pending.endpoint, pending.corr)
3608 .unwrap();
3609 let released = forwarding.cleanup_connection(client).unwrap();
3610 assert_eq!(released.len(), 1);
3611 let completion = forwarding
3612 .complete_pending_relay(
3613 module_connection,
3614 pending.corr,
3615 RouteBindRelayOutcome::Accepted,
3616 )
3617 .unwrap();
3618 assert!(!completion.settled);
3619 assert!(client_rx.try_recv().is_err());
3620 assert_eq!(forwarding.active_binding_count().unwrap(), 0);
3621
3622 let (forwarding, module_connection, _, client, sink, mut client_rx) =
3623 route_fixture("accepted-race");
3624 let pending = begin_test_route(&forwarding, client, sink, 46, "accepted-race");
3625 forwarding
3626 .complete_pending_relay(
3627 module_connection,
3628 pending.corr,
3629 RouteBindRelayOutcome::Accepted,
3630 )
3631 .unwrap();
3632 assert_eq!(client_rx.try_recv().unwrap().header.corr, 46);
3633 let released = forwarding.cleanup_connection(client).unwrap();
3634 assert_eq!(released.len(), 1);
3635 assert_eq!(forwarding.active_binding_count().unwrap(), 0);
3636 }
3637
3638 #[test]
3639 fn drain_marks_block_reservation_commit_and_live_request_admission_until_phase_two() {
3640 let (forwarding, module_connection, _, client, sink, mut client_rx) =
3641 route_fixture("drain-gap");
3642 let live = begin_test_route(&forwarding, client, sink.clone(), 47, "drain-gap");
3643 forwarding
3644 .complete_pending_relay(
3645 module_connection,
3646 live.corr,
3647 RouteBindRelayOutcome::Accepted,
3648 )
3649 .unwrap();
3650 client_rx.try_recv().unwrap();
3651 let binding = match forwarding
3652 .lookup_data_route(client, live.client_channel, live.client_epoch)
3653 .unwrap()
3654 {
3655 DataRoute::Client(DataRouteState::Bound(binding)) => binding,
3656 other => panic!("expected live route, got {other:?}"),
3657 };
3658
3659 let pending = begin_test_route(&forwarding, client, sink.clone(), 48, "drain-gap");
3660 forwarding
3661 .mark_route_bind_relay_enqueued(pending.endpoint, pending.corr)
3662 .unwrap();
3663 let control_rpc = forwarding
3664 .begin_module_control_rpc_for(
3665 "drain-gap",
3666 "health.check",
3667 Instant::now() + Duration::from_secs(1),
3668 )
3669 .unwrap();
3670 let target = forwarding
3671 .begin_module_drain("drain-gap", RouteCloseReason::Reload)
3672 .unwrap()
3673 .unwrap();
3674 assert!(matches!(
3675 control_rpc.receiver.blocking_recv().unwrap(),
3676 ModuleControlRpcOutcome::ModuleGone(_)
3677 ));
3678 assert_eq!(target.abandoned_bindings.len(), 1);
3679 assert!(binding.flow.sem.is_closed());
3680 assert!(
3681 !forwarding
3682 .complete_pending_relay(
3683 module_connection,
3684 pending.corr,
3685 RouteBindRelayOutcome::Accepted,
3686 )
3687 .unwrap()
3688 .settled
3689 );
3690 assert!(matches!(
3691 forwarding.begin_route_bind_relay_for_test(client, sink, 49, "drain-gap"),
3692 Err(ForwardingError::ModuleReloading { .. })
3693 ));
3694 let released = forwarding
3695 .release_module_endpoint_routes(target.endpoint)
3696 .unwrap();
3697 assert_eq!(released.len(), 1);
3698 assert_eq!(forwarding.active_binding_count().unwrap(), 0);
3699 }
3700
3701 #[test]
3709 fn accepted_bind_for_a_closing_client_releases_the_route_instead_of_failing_the_module() {
3710 let (forwarding, module_connection, endpoint, client, sink, mut client_rx) =
3711 route_fixture("closing-client");
3712
3713 let live = begin_test_route(&forwarding, client, sink.clone(), 60, "closing-client");
3716 forwarding
3717 .complete_pending_relay(
3718 module_connection,
3719 live.corr,
3720 RouteBindRelayOutcome::Accepted,
3721 )
3722 .unwrap();
3723 client_rx.try_recv().unwrap();
3724
3725 let pending = begin_test_route(&forwarding, client, sink.clone(), 61, "closing-client");
3727 forwarding
3728 .mark_route_bind_relay_enqueued(pending.endpoint, pending.corr)
3729 .unwrap();
3730
3731 assert!(forwarding
3733 .escalate_client_delivery_failure(
3734 client,
3735 live.client_channel,
3736 live.client_epoch,
3737 CloseReason::new(
3738 "module_to_client_delivery_failed",
3739 "client egress refused a module frame",
3740 ),
3741 )
3742 .unwrap());
3743 assert!(!sink.is_closed());
3744
3745 let completion = forwarding
3746 .complete_pending_relay(
3747 module_connection,
3748 pending.corr,
3749 RouteBindRelayOutcome::Accepted,
3750 )
3751 .expect("a closing client must not turn a module's ack into an error");
3752
3753 assert!(completion.settled);
3754 let abandoned = completion
3755 .abandoned
3756 .expect("the module must be told to drop the binding it just created");
3757 assert_eq!(abandoned.connection_id, module_connection);
3758 assert_eq!(abandoned.channel, pending.module_channel);
3759 assert_eq!(abandoned.epoch, pending.module_epoch);
3760 assert!(matches!(abandoned.kind, GoodbyeTargetKind::Module));
3761 assert!(matches!(
3762 pending.receiver.blocking_recv().unwrap(),
3763 RouteBindRelayOutcome::ModuleGone(_)
3764 ));
3765 assert!(client_rx.try_recv().is_err());
3768 assert_eq!(forwarding.active_binding_count().unwrap(), 1);
3769
3770 assert!(forwarding
3773 .has_live_module_connection("closing-client")
3774 .unwrap());
3775 let cotenant = ConnectionId::new(201);
3776 let (cotenant_tx, mut cotenant_rx) = mpsc::channel(8);
3777 let cotenant_route = begin_test_route(
3778 &forwarding,
3779 cotenant,
3780 FrameSink::new(cotenant_tx),
3781 62,
3782 "closing-client",
3783 );
3784 assert_eq!(cotenant_route.endpoint, endpoint);
3785 forwarding
3786 .complete_pending_relay(
3787 module_connection,
3788 cotenant_route.corr,
3789 RouteBindRelayOutcome::Accepted,
3790 )
3791 .unwrap();
3792 assert_eq!(cotenant_rx.try_recv().unwrap().header.corr, 62);
3793 assert_eq!(forwarding.active_binding_count().unwrap(), 2);
3794 }
3795
3796 #[test]
3797 fn pending_route_permit_is_released_on_rejection_and_abort() {
3798 let forwarding = ForwardingTable::default();
3799 let module_connection = ConnectionId::new(300);
3800 let client = ConnectionId::new(301);
3801 let (module_tx, _module_rx) = mpsc::channel(1);
3802 let endpoint = forwarding
3803 .register_module_connection(
3804 module_connection,
3805 "permit".into(),
3806 2,
3807 Concurrency::ModuleManaged,
3808 FrameSink::new(module_tx),
3809 )
3810 .unwrap();
3811 let (client_tx, mut client_rx) = mpsc::channel(1);
3812 let sink = FrameSink::new(client_tx);
3813 let rejected = begin_test_route(&forwarding, client, sink.clone(), 50, "permit");
3814 assert!(sink.try_send(test_ping(999)).is_err());
3815 forwarding
3816 .complete_pending_relay(
3817 module_connection,
3818 rejected.corr,
3819 RouteBindRelayOutcome::Rejected(ErrorBody {
3820 code: "no".into(),
3821 message: "rejected".into(),
3822 detail: None,
3823 }),
3824 )
3825 .unwrap();
3826 sink.try_send(test_ping(1000)).unwrap();
3827 assert_eq!(client_rx.try_recv().unwrap().header.corr, 1000);
3828
3829 let aborted = begin_test_route(&forwarding, client, sink.clone(), 51, "permit");
3830 assert!(sink.try_send(test_ping(1001)).is_err());
3831 forwarding
3832 .abort_pending_relay(
3833 endpoint,
3834 aborted.corr,
3835 RouteBindRelayOutcome::ModuleGone("abort".into()),
3836 )
3837 .unwrap();
3838 sink.try_send(test_ping(1002)).unwrap();
3839 assert_eq!(client_rx.try_recv().unwrap().header.corr, 1002);
3840
3841 let receiver_closed = begin_test_route(&forwarding, client, sink, 52, "permit");
3842 forwarding
3843 .mark_route_bind_relay_enqueued(endpoint, receiver_closed.corr)
3844 .unwrap();
3845 drop(client_rx);
3846 let completion = forwarding
3847 .complete_pending_relay(
3848 module_connection,
3849 receiver_closed.corr,
3850 RouteBindRelayOutcome::Accepted,
3851 )
3852 .unwrap();
3853 assert!(completion.abandoned.is_some());
3854 assert_eq!(forwarding.active_binding_count().unwrap(), 0);
3855 }
3856
3857 #[test]
3863 fn cleaned_up_connections_do_not_stay_in_the_closing_set() {
3864 let (forwarding, module_connection, _endpoint, _fixture_client, _sink, _rx) =
3865 route_fixture("closing-set-leak");
3866
3867 const CONNECTIONS: u64 = 32;
3868 for index in 0..CONNECTIONS {
3869 let client = ConnectionId::new(1000 + index);
3870 let (client_tx, _client_rx) = mpsc::channel(8);
3871 let route = begin_test_route(
3872 &forwarding,
3873 client,
3874 FrameSink::new(client_tx),
3875 index + 1,
3876 "closing-set-leak",
3877 );
3878 forwarding
3879 .complete_pending_relay(
3880 module_connection,
3881 route.corr,
3882 RouteBindRelayOutcome::Accepted,
3883 )
3884 .unwrap();
3885 forwarding.cleanup_connection(client).unwrap();
3886 }
3887 forwarding.cleanup_connection(module_connection).unwrap();
3888
3889 assert_eq!(forwarding.closing_connection_count().unwrap(), 0);
3890 }
3891
3892 #[test]
3899 fn closing_connection_is_refused_new_work_until_cleanup_completes() {
3900 let (forwarding, module_connection, _endpoint, client, sink, mut client_rx) =
3901 route_fixture("closing-gate");
3902
3903 let live = begin_test_route(&forwarding, client, sink.clone(), 80, "closing-gate");
3906 forwarding
3907 .complete_pending_relay(
3908 module_connection,
3909 live.corr,
3910 RouteBindRelayOutcome::Accepted,
3911 )
3912 .unwrap();
3913 client_rx.try_recv().unwrap();
3914
3915 assert!(forwarding
3918 .escalate_client_delivery_failure(
3919 client,
3920 live.client_channel,
3921 live.client_epoch,
3922 CloseReason::new(
3923 "module_to_client_delivery_failed",
3924 "client egress refused a module frame",
3925 ),
3926 )
3927 .unwrap());
3928 assert_eq!(forwarding.closing_connection_count().unwrap(), 1);
3929
3930 assert!(matches!(
3932 forwarding.begin_route_bind_relay_for_test(client, sink, 81, "closing-gate"),
3933 Err(ForwardingError::ConnectionClosing { connection_id })
3934 if connection_id == client
3935 ));
3936 let (late_tx, _late_rx) = mpsc::channel(1);
3938 assert!(matches!(
3939 forwarding.register_module_connection(
3940 client,
3941 "late-module".into(),
3942 2,
3943 Concurrency::ModuleManaged,
3944 FrameSink::new(late_tx),
3945 ),
3946 Err(ForwardingError::ConnectionClosing { connection_id })
3947 if connection_id == client
3948 ));
3949
3950 forwarding.cleanup_connection(client).unwrap();
3954 assert_eq!(forwarding.closing_connection_count().unwrap(), 0);
3955 }
3956}
3957
3958#[cfg(test)]
3961mod swap_slot_tests {
3962 use std::time::Duration;
3963
3964 use super::*;
3965 use tokio::sync::mpsc;
3966
3967 const MODULE_ID: &str = "swapped";
3968
3969 struct SwapFixture {
3970 forwarding: ForwardingTable,
3971 incumbent_connection: ConnectionId,
3972 incumbent: ModuleEndpointId,
3973 candidate_connection: ConnectionId,
3974 candidate: ModuleEndpointId,
3975 _module_rxs: Vec<mpsc::Receiver<crate::router::OutboundFrame>>,
3976 }
3977
3978 fn swap_fixture() -> SwapFixture {
3979 let forwarding = ForwardingTable::default();
3980 let incumbent_connection = ConnectionId::new(100);
3981 let candidate_connection = ConnectionId::new(110);
3982 let (incumbent_tx, incumbent_rx) = mpsc::channel(8);
3983 let incumbent = forwarding
3984 .register_module_connection(
3985 incumbent_connection,
3986 MODULE_ID.to_string(),
3987 2,
3988 Concurrency::ModuleManaged,
3989 FrameSink::new(incumbent_tx),
3990 )
3991 .unwrap();
3992 let (candidate_tx, candidate_rx) = mpsc::channel(8);
3993 let candidate = forwarding
3994 .register_candidate_module_connection(
3995 candidate_connection,
3996 MODULE_ID.to_string(),
3997 2,
3998 Concurrency::ModuleManaged,
3999 FrameSink::new(candidate_tx),
4000 )
4001 .unwrap();
4002 SwapFixture {
4003 forwarding,
4004 incumbent_connection,
4005 incumbent,
4006 candidate_connection,
4007 candidate,
4008 _module_rxs: vec![incumbent_rx, candidate_rx],
4009 }
4010 }
4011
4012 fn client(
4013 raw: u64,
4014 ) -> (
4015 ConnectionId,
4016 FrameSink,
4017 mpsc::Receiver<crate::router::OutboundFrame>,
4018 ) {
4019 let (tx, rx) = mpsc::channel(8);
4020 (ConnectionId::new(raw), FrameSink::new(tx), rx)
4021 }
4022
4023 fn committed_endpoints(forwarding: &ForwardingTable) -> Vec<ModuleEndpointId> {
4024 forwarding
4025 .read_inner()
4026 .unwrap()
4027 .client_to_module
4028 .values()
4029 .map(|route| route.module_endpoint)
4030 .collect()
4031 }
4032
4033 #[test]
4034 fn candidate_is_unroutable_until_cutover_and_by_id_lookups_resolve_the_active_slot() {
4035 let fixture = swap_fixture();
4036 let forwarding = &fixture.forwarding;
4037 assert_ne!(fixture.incumbent, fixture.candidate);
4038
4039 assert!(forwarding.has_live_module_connection(MODULE_ID).unwrap());
4041 assert!(!forwarding.module_is_draining(MODULE_ID).unwrap());
4042 let (client_connection, client_sink, _client_rx) = client(200);
4043 let pending = forwarding
4044 .begin_route_bind_relay_for_test(client_connection, client_sink, 1, MODULE_ID)
4045 .unwrap();
4046 assert_eq!(pending.endpoint, fixture.incumbent);
4047 let rpc = forwarding
4048 .begin_module_control_rpc_for(
4049 MODULE_ID,
4050 "health.check",
4051 Instant::now() + Duration::from_secs(1),
4052 )
4053 .unwrap();
4054 assert_eq!(rpc.endpoint, fixture.incumbent);
4055 let census = forwarding.route_census(Some(MODULE_ID)).unwrap();
4056 assert_eq!(census.len(), 1, "the census lists one endpoint per id");
4057
4058 assert_eq!(
4060 forwarding
4061 .module_endpoint_for_connection(fixture.candidate_connection)
4062 .unwrap(),
4063 Some(fixture.candidate)
4064 );
4065 assert_eq!(
4066 forwarding
4067 .module_id_for_connection(fixture.candidate_connection)
4068 .unwrap()
4069 .as_deref(),
4070 Some(MODULE_ID)
4071 );
4072
4073 let (other_tx, _other_rx) = mpsc::channel(1);
4075 assert_eq!(
4076 forwarding.register_candidate_module_connection(
4077 ConnectionId::new(120),
4078 MODULE_ID.to_string(),
4079 2,
4080 Concurrency::ModuleManaged,
4081 FrameSink::new(other_tx),
4082 ),
4083 Err(ForwardingError::CandidateSlotOccupied {
4084 module_id: MODULE_ID.to_string()
4085 })
4086 );
4087 }
4088
4089 #[test]
4093 fn relay_reserved_before_cutover_never_commits_and_later_relays_land_on_the_candidate() {
4094 let fixture = swap_fixture();
4095 let forwarding = &fixture.forwarding;
4096 let (early_client, early_sink, _early_rx) = client(200);
4097 let mut early = forwarding
4098 .begin_route_bind_relay_for_test(early_client, early_sink, 1, MODULE_ID)
4099 .unwrap();
4100 assert_eq!(early.endpoint, fixture.incumbent);
4101 assert!(forwarding
4102 .mark_route_bind_relay_enqueued(early.endpoint, early.corr)
4103 .unwrap());
4104
4105 let cutover = forwarding.cutover_candidate(MODULE_ID).unwrap().unwrap();
4106 assert_eq!(
4107 cutover,
4108 ForwardingCutover {
4109 promoted: fixture.candidate,
4110 incumbent: Some(fixture.incumbent),
4111 }
4112 );
4113
4114 let (late_client, late_sink, _late_rx) = client(201);
4116 let late = forwarding
4117 .begin_route_bind_relay_for_test(late_client, late_sink, 2, MODULE_ID)
4118 .unwrap();
4119 assert_eq!(
4120 late.endpoint, fixture.candidate,
4121 "a route.open after cutover was reserved on the incumbent"
4122 );
4123
4124 let completion = forwarding
4126 .complete_pending_relay(
4127 fixture.incumbent_connection,
4128 early.corr,
4129 RouteBindRelayOutcome::Accepted,
4130 )
4131 .expect("a superseded endpoint's ack is not an error on its connection");
4132 assert!(completion.settled);
4133 assert!(
4134 !committed_endpoints(forwarding).contains(&fixture.incumbent),
4135 "a relay reserved before cutover committed a route on the incumbent"
4136 );
4137 let goodbye = completion
4138 .abandoned
4139 .expect("the incumbent is told to drop the binding it just created");
4140 assert_eq!(goodbye.connection_id, fixture.incumbent_connection);
4141 assert_eq!(goodbye.channel, early.module_channel);
4142 assert_eq!(goodbye.epoch, early.module_epoch);
4143 assert_eq!(goodbye.kind, GoodbyeTargetKind::Module);
4144 match early.receiver.try_recv() {
4145 Ok(RouteBindRelayOutcome::Rejected(body)) => assert_eq!(body.code, "module_reloading"),
4146 other => panic!("expected a retryable module_reloading answer, got {other:?}"),
4147 }
4148 assert!(matches!(
4149 forwarding
4150 .lookup_data_route(early_client, early.client_channel, early.client_epoch)
4151 .unwrap(),
4152 DataRoute::Client(DataRouteState::Absent)
4153 ));
4154
4155 assert_eq!(forwarding.reserved_route_count().unwrap(), (1, 1));
4158 forwarding
4159 .complete_pending_relay(
4160 fixture.candidate_connection,
4161 late.corr,
4162 RouteBindRelayOutcome::Accepted,
4163 )
4164 .unwrap();
4165 assert_eq!(forwarding.reserved_route_count().unwrap(), (0, 0));
4166 assert_eq!(committed_endpoints(forwarding), vec![fixture.candidate]);
4167 }
4168
4169 #[test]
4170 fn endpoint_drain_after_cutover_drains_the_incumbent_not_the_promoted_candidate() {
4171 let fixture = swap_fixture();
4172 let forwarding = &fixture.forwarding;
4173 let (bound_client, bound_sink, _bound_rx) = client(200);
4175 let bound = forwarding
4176 .begin_route_bind_relay_for_test(bound_client, bound_sink, 1, MODULE_ID)
4177 .unwrap();
4178 forwarding
4179 .complete_pending_relay(
4180 fixture.incumbent_connection,
4181 bound.corr,
4182 RouteBindRelayOutcome::Accepted,
4183 )
4184 .unwrap();
4185 let (pending_client, pending_sink, _pending_rx) = client(201);
4186 let mut in_flight = forwarding
4187 .begin_route_bind_relay_for_test(pending_client, pending_sink, 2, MODULE_ID)
4188 .unwrap();
4189 forwarding
4190 .mark_route_bind_relay_enqueued(in_flight.endpoint, in_flight.corr)
4191 .unwrap();
4192
4193 let incumbent = forwarding
4194 .cutover_candidate(MODULE_ID)
4195 .unwrap()
4196 .unwrap()
4197 .incumbent
4198 .unwrap();
4199 let target = forwarding
4200 .begin_endpoint_drain(incumbent, RouteCloseReason::Restart)
4201 .unwrap()
4202 .expect("the superseded incumbent is still registered");
4203
4204 assert_eq!(target.endpoint, fixture.incumbent);
4205 assert!(forwarding.endpoint_is_draining(fixture.incumbent).unwrap());
4206 assert!(!forwarding.endpoint_is_draining(fixture.candidate).unwrap());
4207 assert!(!forwarding.module_is_draining(MODULE_ID).unwrap());
4208 assert_eq!(target.abandoned_bindings.len(), 1);
4209 assert_eq!(
4210 target.abandoned_bindings[0].channel,
4211 in_flight.module_channel
4212 );
4213 assert!(matches!(
4214 in_flight.receiver.try_recv(),
4215 Ok(RouteBindRelayOutcome::Rejected(body)) if body.code == "module_reloading"
4216 ));
4217 assert_eq!(
4218 forwarding.endpoint_routes(fixture.incumbent).unwrap().len(),
4219 1,
4220 "the incumbent's bound route stays until its drain finishes"
4221 );
4222
4223 let (next_client, next_sink, _next_rx) = client(202);
4224 let next = forwarding
4225 .begin_route_bind_relay_for_test(next_client, next_sink, 3, MODULE_ID)
4226 .expect("the promoted candidate keeps accepting routes");
4227 assert_eq!(next.endpoint, fixture.candidate);
4228 }
4229
4230 #[test]
4236 fn stale_endpoint_ack_without_a_promotion_still_fails_as_before() {
4237 let forwarding = ForwardingTable::default();
4238 let first_connection = ConnectionId::new(70);
4239 let (first_tx, _first_rx) = mpsc::channel(8);
4240 forwarding
4241 .register_module_connection(
4242 first_connection,
4243 MODULE_ID.to_string(),
4244 2,
4245 Concurrency::ModuleManaged,
4246 FrameSink::new(first_tx),
4247 )
4248 .unwrap();
4249 let (client_connection, client_sink, _client_rx) = client(200);
4250 let mut pending = forwarding
4251 .begin_route_bind_relay_for_test(client_connection, client_sink, 1, MODULE_ID)
4252 .unwrap();
4253 let (second_tx, _second_rx) = mpsc::channel(8);
4254 forwarding
4255 .register_module_connection(
4256 ConnectionId::new(80),
4257 MODULE_ID.to_string(),
4258 2,
4259 Concurrency::ModuleManaged,
4260 FrameSink::new(second_tx),
4261 )
4262 .unwrap();
4263
4264 assert_eq!(
4265 forwarding
4266 .complete_pending_relay(
4267 first_connection,
4268 pending.corr,
4269 RouteBindRelayOutcome::Accepted
4270 )
4271 .unwrap_err(),
4272 ForwardingError::StaleModuleEndpoint
4273 );
4274 assert!(committed_endpoints(&forwarding).is_empty());
4275 assert_eq!(forwarding.reserved_route_count().unwrap(), (0, 0));
4276 assert!(matches!(
4277 pending.receiver.try_recv(),
4278 Err(oneshot::error::TryRecvError::Closed)
4279 ));
4280 }
4281
4282 #[test]
4283 fn cleanup_releases_candidate_and_superseded_slots_without_touching_the_active_one() {
4284 let fixture = swap_fixture();
4286 let forwarding = &fixture.forwarding;
4287 assert!(forwarding
4288 .cleanup_connection(fixture.candidate_connection)
4289 .unwrap()
4290 .is_empty());
4291 assert_eq!(forwarding.cutover_candidate(MODULE_ID).unwrap(), None);
4292 let (client_connection, client_sink, _client_rx) = client(200);
4293 assert_eq!(
4294 forwarding
4295 .begin_route_bind_relay_for_test(client_connection, client_sink, 1, MODULE_ID)
4296 .unwrap()
4297 .endpoint,
4298 fixture.incumbent
4299 );
4300
4301 let fixture = swap_fixture();
4304 let forwarding = &fixture.forwarding;
4305 let (bound_client, bound_sink, _bound_rx) = client(200);
4306 let bound = forwarding
4307 .begin_route_bind_relay_for_test(bound_client, bound_sink, 1, MODULE_ID)
4308 .unwrap();
4309 forwarding
4310 .complete_pending_relay(
4311 fixture.incumbent_connection,
4312 bound.corr,
4313 RouteBindRelayOutcome::Accepted,
4314 )
4315 .unwrap();
4316 forwarding.cutover_candidate(MODULE_ID).unwrap().unwrap();
4317 let released = forwarding
4318 .cleanup_connection(fixture.incumbent_connection)
4319 .unwrap();
4320 assert_eq!(released.len(), 1);
4321 assert_eq!(released[0].connection_id, bound_client);
4322 assert!(forwarding
4323 .read_inner()
4324 .unwrap()
4325 .superseded_endpoints
4326 .is_empty());
4327 assert!(forwarding.has_live_module_connection(MODULE_ID).unwrap());
4328 let (next_client, next_sink, _next_rx) = client(201);
4329 assert_eq!(
4330 forwarding
4331 .begin_route_bind_relay_for_test(next_client, next_sink, 2, MODULE_ID)
4332 .unwrap()
4333 .endpoint,
4334 fixture.candidate
4335 );
4336 }
4337}