1use crate::diagnostics::{Diagnostics, RecoveryFailure, RecoveryPhase};
2use crate::endpoint::{RecoveryWake, RelayEndpoint};
3use crate::relay::CarrierId;
4pub mod error;
5mod stream;
6
7use std::fmt::Debug;
8use std::sync::{Arc, Mutex};
9use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
10
11use snafu::ResultExt;
12use tokio::task::JoinSet;
13use tokio::time::MissedTickBehavior;
14use tokio_util::sync::CancellationToken;
15use tracing::instrument;
16
17use self::error::{
18 ControlIoTimeoutSnafu, DecodeRegisterRespSnafu, DecodeStreamReqSnafu, EncodePingMsgSnafu,
19 EncodeRegisterReqSnafu, EncodeStreamAckMsgSnafu, ReadRegisterRespSnafu, ReadStreamReqSnafu,
20 RegisterRespNotMatchSnafu, SendRegisterReqSnafu, WritePingMsgSnafu, WriteStreamAckMsgSnafu,
21};
22use self::stream::{StreamConnect, handle_stream_on_endpoint};
23use crate::addr::resolve_tunnel_ends;
24use crate::recovery::{RecoveryTiming, jitter};
25use pb_mapper_core::checksum::{Credential, get_process_credential};
26use pb_mapper_core::config::{
27 ResolvedAddrs, control_conn_pool_size, control_heartbeat_interval, control_heartbeat_tolerance,
28 control_io_timeout, control_suspect_grace, registration_probe_timeout,
29 registration_reject_backoff,
30};
31use pb_mapper_core::timeout::RetryBackoff;
32use pb_mapper_core::{
33 snafu_error_get_or_continue, snafu_error_get_or_return, snafu_error_get_or_return_ok,
34 snafu_error_handle,
35};
36use pb_mapper_protocol::command::{
37 CONTROL_PROTOCOL_V2, LocalServer, MessageSerializer, PbConnRequest, PbConnResponse,
38 PbConnStatusReq, PbConnStatusResp, PbServerRequest,
39};
40use pb_mapper_protocol::forward::StreamForward;
41use pb_mapper_protocol::secure::ClientHeaderSession;
42use pb_mapper_protocol::{MessageReader, MessageWriter};
43use uni_stream::addr::ToSocketAddrs;
44use uni_stream::stream::StreamProvider;
45
46fn get_ping_message(protocol_version: u16, seq: u64) -> error::Result<Vec<u8>> {
47 if protocol_version >= CONTROL_PROTOCOL_V2 {
48 PbServerRequest::PingV2 { seq }
49 .encode()
50 .context(EncodePingMsgSnafu)
51 } else {
52 PbServerRequest::Ping.encode().context(EncodePingMsgSnafu)
53 }
54}
55
56#[derive(Debug)]
68enum Status {
69 ReadMsg,
70 SendPing,
71 ConnectRemote,
72 Resolve,
73 Timeout,
74 NetworkChanged,
75 Cancelled,
76 Rejected(String),
81 RejectedRetryable(String),
86}
87
88impl Status {
89 fn rejected(error: pb_mapper_protocol::command::PbErrorResponse) -> Self {
90 let reason = format!("{}: {}", error.code, error.message);
91 if error.retryable {
92 Self::RejectedRetryable(reason)
93 } else {
94 Self::Rejected(reason)
95 }
96 }
97}
98
99#[derive(Debug)]
106struct ControlBackoff {
107 transport: RetryBackoff,
110 reject: RetryBackoff,
113 timing: RecoveryTiming,
114}
115
116impl ControlBackoff {
117 fn new() -> Self {
118 let (reject_min, reject_max) = registration_reject_backoff();
119 Self {
120 transport: RetryBackoff::new(Duration::from_millis(100), Duration::from_secs(2)),
121 timing: RecoveryTiming::default(),
122 reject: RetryBackoff::new(reject_min, reject_max),
123 }
124 }
125
126 fn reset(&mut self) {
129 self.transport.reset();
130 self.reject.reset();
131 }
132}
133
134pub(crate) enum LocalControlWrite {
135 #[cfg(feature = "p2p")]
137 Direct(Box<pb_mapper_protocol::direct::DirectProviderMessage>),
138 Ping {
139 seq: u64,
140 },
141 StreamAck {
142 client_id: u32,
143 server_generation: u64,
144 },
145}
146
147#[derive(Debug, Clone, Copy)]
148struct ControlRegistration {
149 conn_id: u32,
150 generation: u64,
151 protocol_version: u16,
152 lease_ttl_ms: u64,
153}
154
155#[derive(Debug)]
156struct ControlLeaseState {
157 last_rx_at: Instant,
158 last_pong_at: Option<Instant>,
159}
160
161impl ControlLeaseState {
162 fn new() -> Self {
163 Self {
164 last_rx_at: Instant::now(),
165 last_pong_at: None,
166 }
167 }
168
169 fn record_rx(&mut self) {
170 self.last_rx_at = Instant::now();
171 }
172
173 fn record_pong(&mut self) {
174 let now = Instant::now();
175 self.last_rx_at = now;
176 self.last_pong_at = Some(now);
177 }
178
179 fn last_rx_age(&self) -> Duration {
180 self.last_rx_at.elapsed()
181 }
182}
183
184#[derive(Debug)]
185enum RegistrationProbeResult {
186 Present,
187 Missing,
188 Failed(String),
189}
190
191pub type StatusCallback = Box<dyn Fn(&str) + Send + Sync>;
193
194struct PoolStatus {
213 callback: StatusCallback,
214 workers: Mutex<PoolStatusState>,
215}
216
217struct PoolStatusState {
219 workers: Vec<WorkerStatus>,
220 published: Option<WorkerStatus>,
221}
222
223#[derive(Clone, Debug, Eq, PartialEq)]
225enum WorkerStatus {
226 Retrying,
228 Connected,
230 Failed(String),
232}
233
234impl PoolStatus {
235 fn new(callback: StatusCallback, pool_size: usize) -> Self {
236 Self {
237 callback,
238 workers: Mutex::new(PoolStatusState {
239 workers: vec![WorkerStatus::Retrying; pool_size],
240 published: None,
241 }),
242 }
243 }
244
245 fn report(&self, worker_index: usize, status: WorkerStatus) {
248 let next = {
249 let mut state = self
250 .workers
251 .lock()
252 .unwrap_or_else(|poisoned| poisoned.into_inner());
253 if let Some(slot) = state.workers.get_mut(worker_index) {
254 *slot = status;
255 }
256 let aggregate = Self::aggregate(&state.workers);
257 if state.published.as_ref() == Some(&aggregate) {
258 return;
259 }
260 state.published = Some(aggregate.clone());
261 aggregate
262 };
263 match next {
266 WorkerStatus::Connected => (self.callback)("connected"),
267 WorkerStatus::Retrying => (self.callback)("retrying"),
268 WorkerStatus::Failed(reason) => (self.callback)(&format!("failed: {reason}")),
269 }
270 }
271
272 fn aggregate(workers: &[WorkerStatus]) -> WorkerStatus {
277 let mut retrying = false;
278 let mut first_failure = None;
279 for status in workers {
280 match status {
281 WorkerStatus::Connected => return WorkerStatus::Connected,
282 WorkerStatus::Retrying => retrying = true,
283 WorkerStatus::Failed(reason) => {
284 first_failure.get_or_insert(reason);
285 }
286 }
287 }
288 match first_failure {
289 Some(reason) if !retrying => WorkerStatus::Failed(reason.clone()),
292 _ => WorkerStatus::Retrying,
293 }
294 }
295}
296
297#[derive(Clone, Copy, Debug)]
304pub struct ServerTunnelOptions {
305 pub need_codec: bool,
306 pub is_datagram: bool,
307 pub keep_alive: bool,
308 pub namespace: Option<u64>,
309 pub force_namespace: bool,
310}
311
312#[derive(Clone)]
313struct ServerCliRunConfig {
314 local_addr: ResolvedAddrs,
315 remote_addr: RelayEndpoint,
316 diagnostics: Diagnostics,
317 key: Arc<str>,
318 options: ServerTunnelOptions,
319 worker_index: usize,
320 credential: Credential,
321 idle_timeout: Option<Duration>,
322 #[cfg(feature = "p2p")]
323 direct: Option<Arc<crate::direct::Provider>>,
324}
325
326impl Debug for ServerCliRunConfig {
327 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
328 formatter
329 .debug_struct("ServerCliRunConfig")
330 .field("local_addr", &self.local_addr)
331 .field("remote_addr", &self.remote_addr)
332 .field("key", &self.key)
333 .field("options", &self.options)
334 .field("worker_index", &self.worker_index)
335 .field("credential_key_id", &self.credential.key_id())
336 .finish()
337 }
338}
339
340fn duration_to_millis(duration: Duration) -> u64 {
341 duration.as_millis().min(u128::from(u64::MAX)) as u64
342}
343
344fn new_client_instance_id(worker_index: usize) -> String {
345 let now_ms = SystemTime::now()
346 .duration_since(UNIX_EPOCH)
347 .map(duration_to_millis)
348 .unwrap_or_default();
349 format!("{}-{worker_index}-{now_ms}", std::process::id())
350}
351
352async fn probe_remote_registration(
353 remote_addr: ResolvedAddrs,
354 endpoint: RelayEndpoint,
355 key: Arc<str>,
356 registration: ControlRegistration,
357 namespace: Option<u64>,
358 credential: Credential,
359) -> RegistrationProbeResult {
360 let timeout = registration_probe_timeout();
361 let result = tokio::time::timeout(timeout, async {
362 let mut stream = endpoint
363 .connect_addresses(&remote_addr, credential, &mut None)
364 .await
365 .map_err(|e| format!("connect remote status stream failed: {e}"))?;
366 crate::client::status::get_status_with_credential(
367 &mut stream,
368 PbConnStatusReq::Service {
369 direct_query: None,
370 key: key.to_string(),
371 },
372 namespace,
373 &credential,
374 )
375 .await
376 .map_err(|e| {
377 format!(
378 "get service status failed: {}",
379 snafu::Report::from_error(e)
380 )
381 })
382 })
383 .await;
384
385 let status = match result {
386 Ok(Ok(status)) => status,
387 Ok(Err(reason)) => return RegistrationProbeResult::Failed(reason),
388 Err(_) => {
389 return RegistrationProbeResult::Failed(format!(
390 "status probe timed out after {timeout:?}"
391 ));
392 }
393 };
394
395 match status {
396 PbConnStatusResp::Service { connections, .. } => {
397 let present = connections.iter().any(|conn| {
398 conn.conn_id == registration.conn_id
399 && conn.generation == registration.generation
400 && conn.healthy
401 });
402 if present {
403 RegistrationProbeResult::Present
404 } else {
405 RegistrationProbeResult::Missing
406 }
407 }
408 PbConnStatusResp::Keys(keys) => {
409 if keys.iter().any(|candidate| candidate == key.as_ref()) {
410 RegistrationProbeResult::Present
411 } else {
412 RegistrationProbeResult::Missing
413 }
414 }
415 other => RegistrationProbeResult::Failed(format!(
416 "unexpected status response while probing registration: {other:?}"
417 )),
418 }
419}
420
421pub async fn run_server_side_cli<LocalStream, A>(
422 local_addr: A,
423 remote_addr: A,
424 key: Arc<str>,
425 options: ServerTunnelOptions,
426) where
427 LocalStream: StreamProvider + Send + 'static,
428 LocalStream::Item: StreamForward,
429 A: ToSocketAddrs,
430{
431 run_server_side_cli_with_callback::<LocalStream, A>(local_addr, remote_addr, key, options, None)
432 .await
433}
434
435pub async fn run_server_side_cli_with_pinned_credential<LocalStream, A>(
436 local_addr: A,
437 remote_addr: A,
438 key: Arc<str>,
439 options: ServerTunnelOptions,
440 status_callback: Option<StatusCallback>,
441 credential: Credential,
442) where
443 LocalStream: StreamProvider + Send + 'static,
444 LocalStream::Item: StreamForward,
445 A: ToSocketAddrs,
446{
447 let Some((local_addr, remote_addr)) = resolve_tunnel_ends(local_addr, remote_addr).await else {
448 return;
449 };
450 run_server_side_cli_pool::<LocalStream>(
451 local_addr,
452 remote_addr,
453 key,
454 options,
455 status_callback,
456 Some(credential),
457 CancellationToken::new(),
458 )
459 .await;
460}
461
462pub async fn run_server_side_cli_with_shutdown<LocalStream>(
468 local_addr: ResolvedAddrs,
469 remote_addr: ResolvedAddrs,
470 key: Arc<str>,
471 options: ServerTunnelOptions,
472 status_callback: Option<StatusCallback>,
473 credential: Credential,
474 shutdown: CancellationToken,
475) where
476 LocalStream: StreamProvider + Send + 'static,
477 LocalStream::Item: StreamForward,
478{
479 run_server_side_cli_pool::<LocalStream>(
480 local_addr,
481 remote_addr,
482 key,
483 options,
484 status_callback,
485 Some(credential),
486 shutdown,
487 )
488 .await;
489}
490
491pub async fn run_server_side_cli_with_callback<LocalStream, A>(
492 local_addr: A,
493 remote_addr: A,
494 key: Arc<str>,
495 options: ServerTunnelOptions,
496 status_callback: Option<StatusCallback>,
497) where
498 LocalStream: StreamProvider + Send + 'static,
499 LocalStream::Item: StreamForward,
500 A: ToSocketAddrs,
501{
502 let Some((local_addr, remote_addr)) = resolve_tunnel_ends(local_addr, remote_addr).await else {
503 return;
504 };
505 run_server_side_cli_pool::<LocalStream>(
506 local_addr,
507 remote_addr,
508 key,
509 options,
510 status_callback,
511 None,
512 CancellationToken::new(),
513 )
514 .await;
515}
516
517async fn resolve_registration_credential(
518 pinned: Option<Credential>,
519 shutdown: &CancellationToken,
520) -> Option<Credential> {
521 if let Some(credential) = pinned {
522 return Some(credential);
523 }
524 let mut retry_backoff = RetryBackoff::default();
525 loop {
526 if shutdown.is_cancelled() {
527 return None;
528 }
529 match get_process_credential() {
530 Ok(credential) => return Some(credential),
531 Err(error) => {
532 tracing::error!("load registration credential failed: {error}");
533 tokio::select! {
534 () = shutdown.cancelled() => return None,
535 () = tokio::time::sleep(retry_backoff.next_delay()) => {}
536 }
537 }
538 }
539 }
540}
541
542async fn run_server_side_cli_pool<LocalStream>(
543 local_addr: ResolvedAddrs,
544 remote_addr: ResolvedAddrs,
545 key: Arc<str>,
546 options: ServerTunnelOptions,
547 status_callback: Option<StatusCallback>,
548 pinned_credential: Option<Credential>,
549 shutdown: CancellationToken,
550) where
551 LocalStream: StreamProvider + Send + 'static,
552 LocalStream::Item: StreamForward,
553{
554 run_server_side_cli_recovering::<LocalStream>(
555 local_addr,
556 RelayEndpoint::fixed(remote_addr),
557 key,
558 options,
559 status_callback,
560 pinned_credential,
561 shutdown,
562 Diagnostics::default(),
563 None,
564 false,
565 crate::sdk::PortMapping::Off,
566 )
567 .await;
568}
569
570#[allow(clippy::too_many_arguments)]
571pub(crate) async fn run_server_side_cli_recovering<LocalStream>(
572 local_addr: ResolvedAddrs,
573 remote_addr: RelayEndpoint,
574 key: Arc<str>,
575 options: ServerTunnelOptions,
576 status_callback: Option<StatusCallback>,
577 pinned_credential: Option<Credential>,
578 shutdown: CancellationToken,
579 diagnostics: Diagnostics,
580 idle_timeout: Option<Duration>,
581 allow_direct: bool,
582 port_mapping: crate::sdk::PortMapping,
583) where
584 LocalStream: StreamProvider + Send + 'static,
585 LocalStream::Item: StreamForward,
586{
587 let Some(credential) = resolve_registration_credential(pinned_credential, &shutdown).await
588 else {
589 return;
590 };
591 remote_addr.start();
592 let pool_size = control_conn_pool_size().max(1);
593 tracing::info!(
594 event = "local_server_control_pool_starting",
595 key = %key,
596 pool_size,
597 "starting local server control connection pool"
598 );
599 #[cfg(feature = "p2p")]
600 let direct = allow_direct.then(|| {
601 crate::direct::Provider::spawn::<LocalStream>(
602 local_addr.clone(),
603 key.clone(),
604 options.is_datagram,
605 shutdown.clone(),
606 idle_timeout,
607 diagnostics.clone(),
608 port_mapping,
609 )
610 });
611 #[cfg(not(feature = "p2p"))]
612 let _ = (allow_direct, port_mapping);
613 let mut workers = JoinSet::new();
614 let pool_status =
618 status_callback.map(|callback| Arc::new(PoolStatus::new(callback, pool_size)));
619 for worker_index in 0..pool_size {
620 let worker_key = key.clone();
621 let worker_status = pool_status.clone();
622 let worker_shutdown = shutdown.clone();
623 let worker_local = local_addr.clone();
624 let worker_remote = remote_addr.clone();
625 let worker_diagnostics = diagnostics.clone();
626 #[cfg(feature = "p2p")]
627 let worker_direct = direct.clone();
628 workers.spawn(async move {
629 run_server_side_cli_worker::<LocalStream>(
630 worker_local,
631 worker_remote,
632 worker_key,
633 options,
634 worker_status,
635 worker_index,
636 credential,
637 worker_shutdown,
638 worker_diagnostics,
639 idle_timeout,
640 #[cfg(feature = "p2p")]
641 worker_direct,
642 )
643 .await;
644 });
645 }
646 while let Some(result) = workers.join_next().await {
647 if let Err(e) = result {
648 tracing::warn!(
649 event = "local_server_control_worker_join_failed",
650 error = %e,
651 "local server control worker join failed"
652 );
653 }
654 }
655 #[cfg(feature = "p2p")]
656 if let Some(direct) = direct {
657 direct.stop().await;
658 }
659}
660
661#[allow(clippy::too_many_arguments)]
662async fn run_server_side_cli_worker<LocalStream>(
663 local_addr: ResolvedAddrs,
664 remote_addr: RelayEndpoint,
665 key: Arc<str>,
666 options: ServerTunnelOptions,
667 pool_status: Option<Arc<PoolStatus>>,
668 worker_index: usize,
669 credential: Credential,
670 shutdown: CancellationToken,
671 diagnostics: Diagnostics,
672 idle_timeout: Option<Duration>,
673 #[cfg(feature = "p2p")] direct: Option<Arc<crate::direct::Provider>>,
674) where
675 LocalStream: StreamProvider + Send + 'static,
676 LocalStream::Item: StreamForward,
677{
678 let report = |status: WorkerStatus| {
679 if let Some(ref pool_status) = pool_status {
680 pool_status.report(worker_index, status);
681 }
682 };
683 let mut backoff = ControlBackoff::new();
684 let run_config = ServerCliRunConfig {
685 local_addr: local_addr.clone(),
686 remote_addr: remote_addr.clone(),
687 diagnostics: diagnostics.clone(),
688 key: key.clone(),
689 options,
690 worker_index,
691 credential,
692 idle_timeout,
693 #[cfg(feature = "p2p")]
694 direct: direct.clone(),
695 };
696 let mut stream_tasks = JoinSet::new();
700 let setup_slots = remote_addr.data_slots();
701 let mut wake = remote_addr.wake();
702 diagnostics.network_seen(remote_addr.network_generation());
703 'outer: loop {
704 if shutdown.is_cancelled() {
705 break 'outer;
706 }
707 wake.acknowledge();
708 diagnostics.attempt();
709 let mut carrier = None;
710 let status = if let Err(status) = run_server_side_cli_inner::<LocalStream>(
711 &mut backoff,
712 run_config.clone(),
713 &report,
714 shutdown.clone(),
715 &mut stream_tasks,
716 &setup_slots,
717 &mut wake,
718 &mut carrier,
719 )
720 .await
721 {
722 status
723 } else {
724 if shutdown.is_cancelled() {
725 break 'outer;
726 }
727 tracing::warn!(
728 event = "local_server_control_worker_finished",
729 key = %key,
730 worker_index,
731 "local server control worker finished without an error; reconnecting"
732 );
733 Status::ReadMsg
734 };
735 if matches!(status, Status::Cancelled) {
736 break 'outer;
737 }
738 let failure = match &status {
740 Status::Resolve => RecoveryFailure::Dns,
741 Status::Timeout => RecoveryFailure::Timeout,
742 Status::Rejected(_) | Status::RejectedRetryable(_) => RecoveryFailure::Rejected,
743 Status::NetworkChanged => RecoveryFailure::NetworkChanged,
744 _ => RecoveryFailure::Transport,
745 };
746 if matches!(
747 failure,
748 RecoveryFailure::Transport | RecoveryFailure::Timeout | RecoveryFailure::Dns
749 ) {
750 remote_addr.transport_failed(carrier);
751 }
752 let (retry_interval, retry_count) = match status {
753 Status::Cancelled => break 'outer,
754 #[cfg(feature = "p2p")]
755 Status::Rejected(_)
756 if direct
757 .as_ref()
758 .is_some_and(|provider| provider.has_active()) =>
759 {
760 (Duration::from_secs(30), 0)
762 }
763 Status::Rejected(reason) => {
764 diagnostics.failed(RecoveryFailure::Rejected, Duration::ZERO);
765 tracing::error!(
766 event = "local_server_registration_rejected_permanently",
767 key = %key,
768 worker_index,
769 reason = %reason,
770 "pb server permanently rejected this registration; not reconnecting"
771 );
772 report(WorkerStatus::Failed(reason));
773 break 'outer;
774 }
775 Status::RejectedRetryable(ref reason) => {
776 let interval = backoff.reject.next_delay();
777 tracing::debug!(
778 event = "local_server_registration_rejected_retryable",
779 key = %key,
780 worker_index,
781 reason = %reason,
782 retry_delay = ?interval,
783 retry_count = backoff.reject.failures(),
784 "pb server rejected this registration for a condition that may clear"
785 );
786 (interval, backoff.reject.failures())
787 }
788 Status::NetworkChanged => {
789 diagnostics.network_changed(remote_addr.network_generation());
790 backoff.transport.reset();
791 (Duration::ZERO, 0)
792 }
793 Status::ReadMsg
794 | Status::SendPing
795 | Status::ConnectRemote
796 | Status::Resolve
797 | Status::Timeout => (
798 jitter(backoff.transport.next_delay()),
799 backoff.transport.failures(),
800 ),
801 };
802 if diagnostics.failed(failure, retry_interval) {
803 tracing::info!(
804 event = "local_server_control_reconnect_scheduled",
805 key = %key,
806 worker_index,
807 local_addr = ?local_addr,
808 remote_addr = ?remote_addr,
809 status = ?status,
810 retry_delay = ?retry_interval,
811 retry_count,
812 "local server control connection will reconnect"
813 );
814 }
815 report(WorkerStatus::Retrying);
816
817 tokio::select! {
818 () = shutdown.cancelled() => break 'outer,
819 () = tokio::time::sleep(retry_interval) => {},
820 woke = wake.changed(), if !matches!(failure, RecoveryFailure::Rejected) => {
821 if let crate::endpoint::Wake::Network(generation) = woke {
822 diagnostics.network_changed(generation);
823 }
824 backoff.transport.reset();
825 }
826 }
827 if shutdown.is_cancelled() {
828 break 'outer;
829 }
830 }
831
832 stream_tasks.shutdown().await;
835}
836
837#[allow(clippy::too_many_arguments)]
840#[instrument(skip(backoff, report, shutdown, stream_tasks, setup_slots, wake, carrier))]
841async fn run_server_side_cli_inner<LocalStream: StreamProvider>(
842 backoff: &mut ControlBackoff,
843 config: ServerCliRunConfig,
844 report: &impl Fn(WorkerStatus),
845 shutdown: CancellationToken,
846 stream_tasks: &mut JoinSet<()>,
847 setup_slots: &Arc<tokio::sync::Semaphore>,
848 wake: &mut RecoveryWake,
849 carrier: &mut Option<CarrierId>,
850) -> std::result::Result<(), Status>
851where
852 LocalStream::Item: StreamForward,
853{
854 let ServerCliRunConfig {
855 local_addr,
856 remote_addr,
857 diagnostics,
858 key,
859 options:
860 ServerTunnelOptions {
861 need_codec,
862 is_datagram,
863 keep_alive,
864 namespace,
865 force_namespace,
866 },
867 worker_index,
868 credential,
869 idle_timeout,
870 #[cfg(feature = "p2p")]
871 direct,
872 } = config;
873 let control_permit = tokio::select! {
874 () = shutdown.cancelled() => return Err(Status::Cancelled),
875 () = wake.network_changed() => return Err(Status::NetworkChanged),
876 permit = remote_addr.control_permit() => permit,
877 };
878 let started = tokio::time::Instant::now();
879 let timeout = backoff.timing.timeout();
880 let deadline = started + timeout;
881 diagnostics.phase(RecoveryPhase::Resolving);
882 let addresses = tokio::select! {
883 () = shutdown.cancelled() => return Err(Status::Cancelled),
884 () = wake.network_changed() => return Err(Status::NetworkChanged),
885 result = tokio::time::timeout_at(deadline, remote_addr.addresses()) => match result {
886 Ok(Ok(addresses)) => addresses,
887 _ => return Err(Status::Resolve),
888 },
889 };
890 diagnostics.phase(RecoveryPhase::Connecting);
891 let mut manager_stream = tokio::select! {
892 () = shutdown.cancelled() => return Err(Status::Cancelled),
893 () = wake.network_changed() => return Err(Status::NetworkChanged),
894 result = tokio::time::timeout_at(deadline, remote_addr.connect_addresses(&addresses, credential, carrier)) => {
895 match result {
896 Ok(Ok(stream)) => stream,
897 Ok(Err(error)) => {
898 tracing::debug!(event = "local_server_dial_failed", %error, %key, worker_index);
899 if let crate::relay::ConnectError::Rejected { response } = error {
900 remote_addr.protocol_succeeded();
901 diagnostics.responded(started.elapsed());
902 backoff.timing.record(started.elapsed());
903 return Err(Status::rejected(response));
904 }
905 return Err(Status::ConnectRemote);
906 }
907 Err(_) => {
908 backoff.timing.timed_out();
909 tracing::debug!(event = "local_server_setup_timeout", %key, worker_index, ?timeout, phase = "dial");
910 return Err(Status::Timeout);
911 }
912 }
913 }
914 };
915 tracing::debug!(
916 event = "local_server_connected_remote",
917 key = %key,
918 worker_index,
919 local_addr = %local_addr,
920 remote_addr = %remote_addr,
921 need_codec,
922 is_datagram,
923 "local server connected to pb server"
924 );
925
926 snafu_error_handle!(manager_stream.configure(keep_alive));
927
928 let registered_addr = manager_stream
929 .relay_addr()
930 .map(ResolvedAddrs::from)
931 .unwrap_or(addresses);
932 diagnostics.phase(RecoveryPhase::Handshake);
933 let session = match ClientHeaderSession::new_v2(&credential) {
937 Ok(session) => session,
938 Err(error) => {
939 tracing::error!("create manager protocol-v2 session failed: {error}");
940 return Err(Status::ConnectRemote);
941 }
942 };
943 let heartbeat_interval = control_heartbeat_interval();
944 let heartbeat_tolerance = control_heartbeat_tolerance();
945 #[cfg(feature = "p2p")]
946 let direct_version = direct
947 .as_ref()
948 .map(|_| pb_mapper_protocol::direct::DIRECT_VERSION);
949 #[cfg(not(feature = "p2p"))]
950 let direct_version = None;
951 let request = match namespace {
952 Some(namespace) => PbConnRequest::RegisterScoped {
953 direct: direct_version,
954 key: key.to_string(),
955 namespace,
956 force_namespace,
957 need_codec,
958 is_datagram,
959 protocol_version: Some(CONTROL_PROTOCOL_V2),
960 client_instance_id: Some(new_client_instance_id(worker_index)),
961 heartbeat_interval_ms: Some(duration_to_millis(heartbeat_interval)),
962 heartbeat_tolerance_ms: Some(duration_to_millis(heartbeat_tolerance)),
963 },
964 None => PbConnRequest::Register {
965 direct: direct_version,
966 key: key.to_string(),
967 need_codec,
968 is_datagram,
969 protocol_version: Some(CONTROL_PROTOCOL_V2),
970 client_instance_id: Some(new_client_instance_id(worker_index)),
971 heartbeat_interval_ms: Some(duration_to_millis(heartbeat_interval)),
972 heartbeat_tolerance_ms: Some(duration_to_millis(heartbeat_tolerance)),
973 },
974 };
975 let msg = snafu_error_get_or_return_ok!(request.encode().context(EncodeRegisterReqSnafu));
976 tokio::select! {
977 () = shutdown.cancelled() => return Err(Status::Cancelled),
978 () = wake.network_changed() => return Err(Status::NetworkChanged),
979 result = tokio::time::timeout_at(deadline, session.write_initial(&mut manager_stream, &msg)) => {
980 match result {
981 Ok(result) => snafu_error_get_or_return_ok!(result.context(SendRegisterReqSnafu)),
982 Err(_) => {
983 backoff.timing.timed_out();
984 tracing::debug!(event = "local_server_setup_timeout", %key, worker_index, ?timeout, phase = "write");
985 return Err(Status::Timeout);
986 }
987 }
988 }
989 }
990 let (mut reader, mut writer) = manager_stream.into_split();
991 let mut msg_reader = match session.response_reader(&mut reader) {
992 Ok(reader) => reader,
993 Err(e) => {
994 tracing::error!("create manager header reader failed: {e}");
995 return Err(Status::ReadMsg);
996 }
997 };
998 let (key, registration) = {
1000 let msg = tokio::select! {
1001 () = shutdown.cancelled() => return Err(Status::Cancelled),
1002 () = wake.network_changed() => return Err(Status::NetworkChanged),
1003 result = tokio::time::timeout_at(deadline, msg_reader.read_msg()) => {
1004 match result {
1005 Ok(result) => snafu_error_get_or_return_ok!(result.context(ReadRegisterRespSnafu)),
1006 Err(_) => {
1007 backoff.timing.timed_out();
1008 tracing::debug!(event = "local_server_setup_timeout", %key, worker_index, ?timeout, phase = "response");
1009 return Err(Status::Timeout);
1010 }
1011 }
1012 }
1013 };
1014 let resp = snafu_error_get_or_return_ok!(
1015 PbConnResponse::decode(msg).context(DecodeRegisterRespSnafu)
1016 );
1017 remote_addr.protocol_succeeded();
1018 diagnostics.responded(started.elapsed());
1019 backoff.timing.record(started.elapsed());
1020 let registration = match resp {
1021 PbConnResponse::RegisterV2 {
1022 conn_id,
1023 generation,
1024 lease_ttl_ms,
1025 ..
1026 } => ControlRegistration {
1027 conn_id,
1028 generation,
1029 protocol_version: CONTROL_PROTOCOL_V2,
1030 lease_ttl_ms,
1031 },
1032 PbConnResponse::Register(conn_id) => ControlRegistration {
1033 conn_id,
1034 generation: 0,
1035 protocol_version: 1,
1036 lease_ttl_ms: 0,
1037 },
1038 PbConnResponse::Error(error) => {
1044 tracing::debug!(
1045 event = "local_server_registration_rejected",
1046 key = %key,
1047 worker_index,
1048 reason = %error.code,
1049 retryable = error.retryable,
1050 message = %error.message,
1051 "pb server rejected service registration"
1052 );
1053 return Err(Status::rejected(error));
1054 }
1055 _ => snafu_error_get_or_return_ok!(RegisterRespNotMatchSnafu {}.fail()),
1056 };
1057 tracing::info!(
1058 event = "local_server_registered",
1059 key = %key,
1060 conn_id = %registration.conn_id,
1061 generation = registration.generation,
1062 protocol_version = registration.protocol_version,
1063 lease_ttl_ms = registration.lease_ttl_ms,
1064 worker_index,
1065 local_addr = %local_addr,
1066 remote_addr = %remote_addr,
1067 "local server registered with pb server"
1068 );
1069
1070 report(WorkerStatus::Connected);
1073 (key, registration)
1074 };
1075
1076 drop(control_permit);
1077 diagnostics.succeeded(started.elapsed());
1078 remote_addr.protocol_succeeded();
1079 backoff.timing.record(started.elapsed());
1080 tracing::debug!(event = "local_server_setup_latency", elapsed_ms = duration_to_millis(started.elapsed()), next_timeout_ms = duration_to_millis(backoff.timing.timeout()), %key, worker_index);
1081 backoff.reset();
1082 let (write_tx, mut write_rx) = tokio::sync::mpsc::channel::<LocalControlWrite>(64);
1083 #[cfg(feature = "p2p")]
1084 if let Some(direct) = &direct {
1085 direct.connected(write_tx.clone());
1086 }
1087 let lease_state = Arc::new(tokio::sync::Mutex::new(ControlLeaseState::new()));
1088 let writer_key = key.clone();
1089 let writer_registration = registration;
1090 let mut writer_handle = tokio_util::task::AbortOnDropHandle::new(tokio::spawn(async move {
1091 let mut msg_writer = match session.continuation_writer(&mut writer) {
1092 Ok(writer) => writer,
1093 Err(e) => {
1094 tracing::error!("create manager header writer failed: {e}");
1095 return Err(Status::SendPing);
1096 }
1097 };
1098 loop {
1099 let Some(cmd) = write_rx.recv().await else {
1100 return Ok(());
1101 };
1102 match cmd {
1103 #[cfg(feature = "p2p")]
1104 LocalControlWrite::Direct(message) => {
1105 let payload = PbServerRequest::Direct(*message)
1106 .encode()
1107 .map_err(|_| Status::SendPing)?;
1108 tokio::time::timeout(control_io_timeout(), msg_writer.write_msg(&payload))
1109 .await
1110 .map_err(|_| Status::SendPing)?
1111 .map_err(|_| Status::SendPing)?;
1112 }
1113 LocalControlWrite::Ping { seq } => {
1114 snafu_error_get_or_return!(
1115 handle_ping_interval(
1116 &mut msg_writer,
1117 writer_key.clone(),
1118 writer_registration,
1119 seq
1120 )
1121 .await,
1122 "[send ping]",
1123 Err(Status::SendPing)
1124 );
1125 tracing::debug!(
1126 event = "local_server_heartbeat_sent",
1127 key = %writer_key,
1128 conn_id = %writer_registration.conn_id,
1129 generation = writer_registration.generation,
1130 protocol_version = writer_registration.protocol_version,
1131 seq,
1132 interval = ?control_heartbeat_interval(),
1133 "local server heartbeat sent"
1134 );
1135 }
1136 LocalControlWrite::StreamAck {
1137 client_id,
1138 server_generation,
1139 } => {
1140 snafu_error_get_or_return!(
1141 write_stream_ack(&mut msg_writer, client_id, server_generation).await,
1142 "[send stream ack]",
1143 Err(Status::SendPing)
1144 );
1145 }
1146 }
1147 }
1148 }));
1149
1150 let heartbeat_interval = control_heartbeat_interval();
1151 let heartbeat_tolerance = control_heartbeat_tolerance();
1152 let suspect_grace = control_suspect_grace();
1153 let mut heartbeat = tokio::time::interval(heartbeat_interval);
1154 heartbeat.set_missed_tick_behavior(MissedTickBehavior::Delay);
1155 heartbeat.tick().await;
1156 let mut probes = JoinSet::new();
1157 let mut ping_seq = 0_u64;
1158 let mut control_deadline = tokio::time::Instant::now() + heartbeat_tolerance + suspect_grace;
1159
1160 let result = loop {
1161 tokio::select! {
1162 msg = msg_reader.read_msg() => {
1163 let msg = match msg.context(ReadStreamReqSnafu) {
1164 Ok(msg) => msg,
1165 Err(e) => {
1166 tracing::error!(
1167 event = "local_server_control_read_failed",
1168 key = %key,
1169 conn_id = %registration.conn_id,
1170 generation = registration.generation,
1171 error = %snafu::Report::from_error(e),
1172 "local server control read failed"
1173 );
1174 break Err(Status::ReadMsg);
1175 }
1176 };
1177 remote_addr.protocol_succeeded();
1178 diagnostics.heard();
1179 lease_state.lock().await.record_rx();
1180 control_deadline = tokio::time::Instant::now() + heartbeat_tolerance + suspect_grace;
1181 snafu_error_get_or_continue!(
1182 handle_request::<LocalStream>(
1183 msg,
1184 StreamConnect {
1185 local_addr: local_addr.clone(),
1186 remote_addr: registered_addr.clone(),
1187 keep_alive,
1188 namespace,
1189 credential,
1190 idle_timeout,
1191 },
1192 remote_addr.clone(),
1193 key.clone(),
1194 registration.conn_id,
1195 &write_tx,
1196 lease_state.clone(),
1197 stream_tasks,
1198 &shutdown,
1199 setup_slots,
1200 #[cfg(feature = "p2p")]
1201 direct.as_ref(),
1202 )
1203 .await
1204 );
1205 }
1206 Some(_) = stream_tasks.join_next() => {
1207 }
1210 result = &mut writer_handle => {
1211 break match result {
1212 Ok(result) => result,
1213 Err(e) => {
1214 tracing::error!(
1215 event = "local_server_control_writer_join_failed",
1216 key = %key,
1217 conn_id = %registration.conn_id,
1218 generation = registration.generation,
1219 error = %e,
1220 "local server control writer task failed"
1221 );
1222 Err(Status::SendPing)
1223 }
1224 };
1225 }
1226 _ = heartbeat.tick() => {
1227 ping_seq = ping_seq.wrapping_add(1);
1228 if write_tx.try_send(LocalControlWrite::Ping { seq: ping_seq }).is_err() {
1229 break Err(Status::SendPing);
1230 }
1231
1232 let last_rx_age = lease_state.lock().await.last_rx_age();
1233 if registration.protocol_version >= CONTROL_PROTOCOL_V2
1234 && last_rx_age >= heartbeat_tolerance
1235 && probes.is_empty()
1236 {
1237 tracing::warn!(
1238 event = "local_server_lease_suspect",
1239 key = %key,
1240 conn_id = %registration.conn_id,
1241 generation = registration.generation,
1242 worker_index,
1243 last_rx_age_ms = duration_to_millis(last_rx_age),
1244 heartbeat_tolerance_ms = duration_to_millis(heartbeat_tolerance),
1245 "local server control lease is suspect; probing remote registration"
1246 );
1247 let probe_key = key.clone();
1248 let probe_remote = registered_addr.clone();
1249 let probe_endpoint = remote_addr.clone();
1250 probes.spawn(async move {
1251 let _permit = probe_endpoint.control_permit().await;
1252 probe_remote_registration(
1253 probe_remote,
1254 probe_endpoint.clone(),
1255 probe_key,
1256 registration,
1257 namespace,
1258 credential,
1259 )
1260 .await
1261 });
1262 }
1263 }
1264 () = shutdown.cancelled() => {
1265 tracing::info!(
1266 event = "local_server_control_cancelled",
1267 key = %key,
1268 conn_id = %registration.conn_id,
1269 generation = registration.generation,
1270 worker_index,
1271 "local server control loop cancelled"
1272 );
1273 break Err(Status::Cancelled);
1274 }
1275 () = tokio::time::sleep_until(control_deadline) => {
1276 tracing::warn!(event = "local_server_control_unresponsive", %key, worker_index, "control socket received no reply past grace; reconnecting");
1277 break Err(Status::ReadMsg);
1278 }
1279 Some(probe_result) = probes.join_next() => {
1280 let probe_result = probe_result.unwrap_or_else(|error| RegistrationProbeResult::Failed(error.to_string()));
1281 let last_rx_age = lease_state.lock().await.last_rx_age();
1282 match probe_result {
1283 RegistrationProbeResult::Present => {
1284 tracing::debug!(
1285 event = "local_server_registration_probe_ok",
1286 key = %key,
1287 conn_id = %registration.conn_id,
1288 generation = registration.generation,
1289 worker_index,
1290 last_rx_age_ms = duration_to_millis(last_rx_age),
1291 "remote registration still contains this control connection"
1292 );
1293 }
1294 RegistrationProbeResult::Missing if last_rx_age >= heartbeat_tolerance => {
1295 tracing::warn!(
1296 event = "local_server_registration_missing",
1297 key = %key,
1298 conn_id = %registration.conn_id,
1299 generation = registration.generation,
1300 worker_index,
1301 last_rx_age_ms = duration_to_millis(last_rx_age),
1302 "remote registration no longer contains this control connection; reconnecting"
1303 );
1304 break Err(Status::ReadMsg);
1305 }
1306 RegistrationProbeResult::Missing => {
1307 tracing::debug!(
1308 event = "local_server_registration_missing_ignored_after_recent_activity",
1309 key = %key,
1310 conn_id = %registration.conn_id,
1311 generation = registration.generation,
1312 worker_index,
1313 last_rx_age_ms = duration_to_millis(last_rx_age),
1314 "remote registration probe was stale after recent control activity"
1315 );
1316 }
1317 RegistrationProbeResult::Failed(reason)
1318 if last_rx_age >= heartbeat_tolerance + suspect_grace =>
1319 {
1320 tracing::warn!(
1321 event = "local_server_status_probe_failed",
1322 key = %key,
1323 conn_id = %registration.conn_id,
1324 generation = registration.generation,
1325 worker_index,
1326 last_rx_age_ms = duration_to_millis(last_rx_age),
1327 reason = %reason,
1328 "registration probe failed past suspect grace; reconnecting"
1329 );
1330 break Err(Status::ReadMsg);
1331 }
1332 RegistrationProbeResult::Failed(reason) => {
1333 tracing::warn!(
1334 event = "local_server_status_probe_failed",
1335 key = %key,
1336 conn_id = %registration.conn_id,
1337 generation = registration.generation,
1338 worker_index,
1339 last_rx_age_ms = duration_to_millis(last_rx_age),
1340 reason = %reason,
1341 "registration probe failed; waiting inside suspect grace"
1342 );
1343 }
1344 }
1345 }
1346 }
1347 };
1348 if !writer_handle.is_finished() {
1349 writer_handle.abort();
1350 }
1351 result
1352}
1353
1354#[instrument(skip(writer))]
1355async fn handle_ping_interval<T: MessageWriter>(
1356 writer: &mut T,
1357 _key: Arc<str>,
1358 registration: ControlRegistration,
1359 seq: u64,
1360) -> error::Result<()> {
1361 let ping_msg = get_ping_message(registration.protocol_version, seq)?;
1362 let timeout = control_io_timeout();
1363 match tokio::time::timeout(timeout, writer.write_msg(&ping_msg)).await {
1364 Ok(result) => result.context(WritePingMsgSnafu),
1365 Err(_) => ControlIoTimeoutSnafu {
1366 action: "write ping message",
1367 timeout,
1368 }
1369 .fail(),
1370 }
1371}
1372
1373#[instrument(skip_all, fields(%key, conn_id))]
1374#[allow(clippy::too_many_arguments)]
1375async fn handle_request<LocalStream: StreamProvider>(
1376 msg: &[u8],
1377 target: StreamConnect,
1378 relay_endpoint: RelayEndpoint,
1379 key: Arc<str>,
1380 conn_id: u32,
1381 write_tx: &tokio::sync::mpsc::Sender<LocalControlWrite>,
1382 lease_state: Arc<tokio::sync::Mutex<ControlLeaseState>>,
1383 stream_tasks: &mut JoinSet<()>,
1384 shutdown: &CancellationToken,
1385 setup_slots: &Arc<tokio::sync::Semaphore>,
1386 #[cfg(feature = "p2p")] direct: Option<&Arc<crate::direct::Provider>>,
1387) -> error::Result<()>
1388where
1389 LocalStream::Item: StreamForward,
1390{
1391 let req = LocalServer::decode(msg).context(DecodeStreamReqSnafu)?;
1392
1393 match req {
1394 LocalServer::Direct(event) => {
1395 #[cfg(feature = "p2p")]
1396 if let Some(direct) = direct {
1397 let mut event = event;
1398 if let (
1402 pb_mapper_protocol::direct::DirectEvent::Offer { stun_addr, .. },
1403 Some(relay),
1404 ) = (&mut *event, target.remote_addr.as_slice().first())
1405 {
1406 *stun_addr = pb_mapper_protocol::direct::stun_target(*stun_addr, relay.ip());
1407 }
1408 direct.event(*event, write_tx.clone());
1409 }
1410 #[cfg(not(feature = "p2p"))]
1411 let _ = event;
1412 }
1413 LocalServer::Stream {
1414 client_id,
1415 server_generation,
1416 } => {
1417 tracing::debug!(
1418 event = "local_server_stream_request_received",
1419 key = %key,
1420 server_conn_id = %conn_id,
1421 client_conn_id = client_id,
1422 server_generation,
1423 "local server received stream request"
1424 );
1425 let Ok(permit) = setup_slots.clone().try_acquire_owned() else {
1426 tracing::warn!(event = "local_server_setup_saturated", %key, client_id, "stream setup capacity reached; relay can select another worker");
1427 return Ok(());
1428 };
1429 write_tx
1430 .try_send(LocalControlWrite::StreamAck {
1431 client_id,
1432 server_generation,
1433 })
1434 .map_err(|_| error::Error::ControlWriterClosed {
1435 action: "stream ack message",
1436 })?;
1437 let key = key.clone();
1438 let stream_shutdown = shutdown.clone();
1439 stream_tasks.spawn(async move {
1443 let forward = handle_stream_on_endpoint::<LocalStream>(
1444 key,
1445 client_id,
1446 server_generation,
1447 target,
1448 permit,
1449 relay_endpoint,
1450 );
1451 tokio::select! {
1452 () = stream_shutdown.cancelled() => {}
1453 result = forward => snafu_error_handle!(result),
1454 }
1455 });
1456 }
1457 LocalServer::Pong => {
1459 lease_state.lock().await.record_pong();
1460 tracing::debug!(
1461 event = "local_server_pong_received",
1462 key = %key,
1463 server_conn_id = %conn_id,
1464 "local server received pong"
1465 );
1466 }
1467 LocalServer::PongV2 { seq } => {
1468 lease_state.lock().await.record_pong();
1469 tracing::debug!(
1470 event = "local_server_pong_received",
1471 key = %key,
1472 server_conn_id = %conn_id,
1473 seq,
1474 "local server received pong v2"
1475 );
1476 }
1477 LocalServer::Retire {
1478 reason,
1479 conn_id: retired_conn_id,
1480 server_generation,
1481 } => {
1482 tracing::warn!(
1483 event = "local_server_control_retired",
1484 key = %key,
1485 server_conn_id = %conn_id,
1486 retired_conn_id,
1487 server_generation,
1488 reason = %reason,
1489 "remote server retired this local control connection"
1490 );
1491 }
1492 }
1493 Ok(())
1494}
1495
1496async fn write_stream_ack<T: MessageWriter>(
1497 writer: &mut T,
1498 client_id: u32,
1499 server_generation: u64,
1500) -> error::Result<()> {
1501 let ack = PbServerRequest::StreamAck {
1502 client_id,
1503 server_generation,
1504 }
1505 .encode()
1506 .context(EncodeStreamAckMsgSnafu)?;
1507 let timeout = control_io_timeout();
1508 match tokio::time::timeout(timeout, writer.write_msg(&ack)).await {
1509 Ok(result) => result.context(WriteStreamAckMsgSnafu),
1510 Err(_) => ControlIoTimeoutSnafu {
1511 action: "write stream ack message",
1512 timeout,
1513 }
1514 .fail(),
1515 }
1516}
1517
1518#[cfg(test)]
1519mod pool_status_tests {
1520 use std::sync::Mutex as StdMutex;
1521
1522 use super::*;
1523
1524 fn recording_pool(pool_size: usize) -> (PoolStatus, Arc<StdMutex<Vec<String>>>) {
1526 let seen = Arc::new(StdMutex::new(Vec::new()));
1527 let sink = seen.clone();
1528 let callback: StatusCallback = Box::new(move |status: &str| {
1529 sink.lock().unwrap().push(status.to_string());
1530 });
1531 (PoolStatus::new(callback, pool_size), seen)
1532 }
1533
1534 fn published(seen: &Arc<StdMutex<Vec<String>>>) -> Vec<String> {
1535 seen.lock().unwrap().clone()
1536 }
1537
1538 #[test]
1541 fn one_connected_worker_makes_the_pool_connected() {
1542 let (pool, seen) = recording_pool(2);
1543 pool.report(0, WorkerStatus::Connected);
1544 pool.report(1, WorkerStatus::Retrying);
1545 assert_eq!(published(&seen), vec!["connected".to_string()]);
1546 }
1547
1548 #[test]
1551 fn a_single_failure_does_not_fail_the_pool() {
1552 let (pool, seen) = recording_pool(2);
1553 pool.report(0, WorkerStatus::Failed("service_transport_mismatch".into()));
1554 assert_eq!(published(&seen), vec!["retrying".to_string()]);
1555
1556 pool.report(1, WorkerStatus::Connected);
1557 assert_eq!(
1558 published(&seen),
1559 vec!["retrying".to_string(), "connected".to_string()]
1560 );
1561 }
1562
1563 #[test]
1566 fn the_pool_fails_only_when_every_worker_has() {
1567 let (pool, seen) = recording_pool(2);
1568 pool.report(0, WorkerStatus::Failed("service_transport_mismatch".into()));
1569 pool.report(1, WorkerStatus::Failed("namespace_access_denied".into()));
1570 assert_eq!(
1571 published(&seen),
1572 vec![
1573 "retrying".to_string(),
1574 "failed: service_transport_mismatch".to_string(),
1575 ],
1576 "the first permanent rejection is the one that explains the pool"
1577 );
1578 }
1579
1580 #[test]
1583 fn unchanged_aggregates_are_not_republished() {
1584 let (pool, seen) = recording_pool(2);
1585 pool.report(0, WorkerStatus::Connected);
1586 pool.report(1, WorkerStatus::Connected);
1587 pool.report(0, WorkerStatus::Connected);
1588 assert_eq!(published(&seen), vec!["connected".to_string()]);
1589 }
1590
1591 #[test]
1594 fn losing_the_last_connection_publishes_retrying() {
1595 let (pool, seen) = recording_pool(1);
1596 pool.report(0, WorkerStatus::Connected);
1597 pool.report(0, WorkerStatus::Retrying);
1598 assert_eq!(
1599 published(&seen),
1600 vec!["connected".to_string(), "retrying".to_string()]
1601 );
1602 }
1603}
1604
1605#[cfg(test)]
1606mod shutdown_tests {
1607 use std::time::Duration;
1608
1609 use super::*;
1610 use uni_stream::stream::TcpStreamProvider;
1611
1612 #[tokio::test]
1613 async fn register_loop_stops_on_cancel() {
1614 let shutdown = CancellationToken::new();
1615 let token = shutdown.clone();
1616 let addr = ResolvedAddrs::from("127.0.0.1:1".parse::<std::net::SocketAddr>().unwrap());
1617 let task = tokio::spawn(async move {
1618 run_server_side_cli_with_shutdown::<TcpStreamProvider>(
1619 addr.clone(),
1620 addr,
1621 "k".into(),
1622 ServerTunnelOptions {
1623 need_codec: false,
1624 is_datagram: false,
1625 keep_alive: false,
1626 namespace: None,
1627 force_namespace: false,
1628 },
1629 None,
1630 Credential::Admin(*b"0123456789abcdefghijklmnopqrstuv"),
1631 token,
1632 )
1633 .await;
1634 });
1635 tokio::time::sleep(Duration::from_millis(80)).await;
1636 shutdown.cancel();
1637 tokio::time::timeout(Duration::from_secs(3), task)
1638 .await
1639 .expect("register loop did not stop after cancel")
1640 .expect("join");
1641 }
1642}