1use std::sync::Arc;
16use std::sync::atomic::{AtomicBool, Ordering};
17use std::time::Duration;
18
19use api::v1::HealthCheckRequest;
20use api::v1::flow::flow_client::FlowClient as PbFlowClient;
21use api::v1::health_check_client::HealthCheckClient;
22use api::v1::prometheus_gateway_client::PrometheusGatewayClient;
23use arrow_flight::flight_service_client::FlightServiceClient;
24use common_grpc::channel_manager::{
25 ChannelConfig, ChannelManager, ClientTlsOption, load_client_tls_config,
26};
27use parking_lot::RwLock;
28use snafu::{OptionExt, ResultExt};
29use tonic::codec::CompressionEncoding;
30use tonic::transport::Channel;
31
32use crate::load_balance::{LoadBalance, Loadbalancer};
33use crate::{Result, error};
34
35const DEFAULT_HEALTH_CHECK_INTERVAL: Duration = Duration::from_secs(30);
36const DEFAULT_HEALTH_CHECK_TIMEOUT: Duration = Duration::from_secs(1);
37
38#[derive(Clone, Debug)]
40pub struct ClientOptions {
41 pub health_check_interval: Duration,
43 pub health_check_timeout: Duration,
45}
46
47impl Default for ClientOptions {
48 fn default() -> Self {
49 Self {
50 health_check_interval: DEFAULT_HEALTH_CHECK_INTERVAL,
51 health_check_timeout: DEFAULT_HEALTH_CHECK_TIMEOUT,
52 }
53 }
54}
55
56pub struct FlightClient {
57 addr: String,
58 client: FlightServiceClient<Channel>,
59}
60
61impl FlightClient {
62 pub fn addr(&self) -> &str {
63 &self.addr
64 }
65
66 pub fn mut_inner(&mut self) -> &mut FlightServiceClient<Channel> {
67 &mut self.client
68 }
69}
70
71#[derive(Clone, Debug, Default)]
72pub struct Client {
73 inner: Arc<Inner>,
74}
75
76#[derive(Debug)]
77struct Inner {
78 query_channel_manager: ChannelManager,
79 control_channel_manager: ChannelManager,
80 peers: RwLock<Peers>,
81 load_balance: Loadbalancer,
82 health_check_interval: Duration,
83 health_check_timeout: Duration,
84 health_check_started: AtomicBool,
85}
86
87impl Default for Inner {
88 fn default() -> Self {
89 #[allow(deprecated)]
90 Self::with_manager_and_peers(ChannelManager::new(), Vec::new(), ClientOptions::default())
91 }
92}
93
94#[derive(Debug, Default, PartialEq, Eq)]
95struct PeerStates {
96 active: Vec<usize>,
97 inactive: Vec<usize>,
98}
99
100#[derive(Debug, Default)]
101struct Peers {
102 addresses: Vec<String>,
103 states: PeerStates,
104 generation: u64,
105}
106
107impl Inner {
108 #[deprecated(note = "legacy single-manager path shares its pool between lanes")]
109 fn with_manager_and_peers(
110 channel_manager: ChannelManager,
111 peers: Vec<String>,
112 options: ClientOptions,
113 ) -> Self {
114 Self::with_managers_and_peers(channel_manager.clone(), channel_manager, peers, options)
115 }
116
117 fn with_managers_and_peers(
119 query_channel_manager: ChannelManager,
120 control_channel_manager: ChannelManager,
121 peers: Vec<String>,
122 options: ClientOptions,
123 ) -> Self {
124 let peer_count = peers.len();
125 Self {
126 query_channel_manager,
127 control_channel_manager,
128 peers: RwLock::new(Peers {
129 addresses: peers,
130 states: PeerStates {
131 active: (0..peer_count).collect(),
132 inactive: Vec::new(),
133 },
134 generation: 0,
135 }),
136 load_balance: Loadbalancer::default(),
137 health_check_interval: options.health_check_interval,
138 health_check_timeout: options.health_check_timeout,
139 health_check_started: AtomicBool::new(false),
140 }
141 }
142
143 fn set_peers(&self, addresses: Vec<String>) {
144 let peer_count = addresses.len();
145 let mut peers = self.peers.write();
146 peers.addresses = addresses;
147 peers.states = PeerStates {
148 active: (0..peer_count).collect(),
149 inactive: Vec::new(),
150 };
151 peers.generation = peers.generation.wrapping_add(1);
152 }
153
154 fn peer_count(&self) -> usize {
155 self.peers.read().addresses.len()
156 }
157
158 fn get_peer(&self) -> Option<String> {
159 let peers = self.peers.read();
160 let index = self
161 .load_balance
162 .get_index(&peers.states.active)
163 .or_else(|| self.load_balance.get_index(&peers.states.inactive))?;
164 Some(peers.addresses[*index].clone())
165 }
166
167 async fn refresh_peer_states(&self) {
168 let (generation, peers) = {
169 let peers = self.peers.read();
170 let addresses = peers
171 .states
172 .active
173 .iter()
174 .chain(&peers.states.inactive)
175 .map(|&index| (index, peers.addresses[index].clone()))
176 .collect::<Vec<_>>();
177 (peers.generation, addresses)
178 };
179 let health_checks = peers.into_iter().map(|(index, addr)| async move {
180 let is_active = self.check_peer_health(&addr).await;
181 (index, is_active)
182 });
183 let results = futures::future::join_all(health_checks).await;
184
185 let (active, inactive) = results.into_iter().fold(
186 (Vec::new(), Vec::new()),
187 |(mut active, mut inactive), (index, is_active)| {
188 if is_active {
189 active.push(index);
190 } else {
191 inactive.push(index);
192 }
193 (active, inactive)
194 },
195 );
196
197 let mut peers = self.peers.write();
198 if peers.generation == generation {
199 peers.states = PeerStates { active, inactive };
200 }
201 }
202
203 async fn check_peer_health(&self, addr: &str) -> bool {
204 let Ok(channel) = self.control_channel_manager.get(addr) else {
205 return false;
206 };
207 let mut client = HealthCheckClient::new(channel);
208 tokio::time::timeout(
209 self.health_check_timeout,
210 client.health_check(HealthCheckRequest {}),
211 )
212 .await
213 .is_ok_and(|result| result.is_ok())
214 }
215}
216
217fn random_initial_delay(max_delay: Duration) -> Duration {
218 let max_nanos = max_delay.as_nanos().min(u64::MAX as u128) as u64;
219 if max_nanos == 0 {
220 return Duration::ZERO;
221 }
222
223 Duration::from_nanos(rand::random_range(0..max_nanos))
224}
225
226impl Client {
227 #[deprecated(
229 note = "shares one manager between query and control lanes; use `with_query_and_control_managers` with independently constructed managers instead"
230 )]
231 pub fn new() -> Self {
232 Default::default()
233 }
234
235 #[deprecated(
237 note = "shares one manager between query and control lanes; use `with_query_and_control_managers` with independently constructed managers instead"
238 )]
239 pub fn with_urls<U, A>(urls: A) -> Self
240 where
241 U: AsRef<str>,
242 A: AsRef<[U]>,
243 {
244 #[allow(deprecated)]
245 Self::with_urls_and_options(urls, ClientOptions::default())
246 }
247
248 #[deprecated(
252 note = "shares one manager between query and control lanes; use `with_query_and_control_managers` instead"
253 )]
254 pub fn with_urls_and_options<U, A>(urls: A, options: ClientOptions) -> Self
255 where
256 U: AsRef<str>,
257 A: AsRef<[U]>,
258 {
259 #[allow(deprecated)]
260 Self::with_manager_and_urls_and_options(ChannelManager::new(), urls, options)
261 }
262
263 #[deprecated(
265 note = "shares one manager between query and control lanes; use `with_query_and_control_managers` instead"
266 )]
267 pub fn with_tls_and_urls<U, A>(urls: A, client_tls: ClientTlsOption) -> Result<Self>
268 where
269 U: AsRef<str>,
270 A: AsRef<[U]>,
271 {
272 #[allow(deprecated)]
273 Self::with_tls_and_urls_and_options(urls, client_tls, ClientOptions::default())
274 }
275
276 #[deprecated(
280 note = "shares one manager between query and control lanes; use `with_query_and_control_managers` instead"
281 )]
282 pub fn with_tls_and_urls_and_options<U, A>(
283 urls: A,
284 client_tls: ClientTlsOption,
285 options: ClientOptions,
286 ) -> Result<Self>
287 where
288 U: AsRef<str>,
289 A: AsRef<[U]>,
290 {
291 let channel_config = ChannelConfig::default().client_tls_config(client_tls.clone());
292 let tls_config =
293 load_client_tls_config(Some(client_tls)).context(error::CreateTlsChannelSnafu)?;
294 let channel_manager = ChannelManager::with_config(channel_config, tls_config);
295 #[allow(deprecated)]
296 Ok(Self::with_manager_and_urls_and_options(
297 channel_manager,
298 urls,
299 options,
300 ))
301 }
302
303 #[deprecated(
305 note = "shares one manager between query and control lanes; use `with_query_and_control_managers` with independently constructed managers instead"
306 )]
307 pub fn with_manager_and_urls<U, A>(channel_manager: ChannelManager, urls: A) -> Self
308 where
309 U: AsRef<str>,
310 A: AsRef<[U]>,
311 {
312 #[allow(deprecated)]
313 Self::with_manager_and_urls_and_options(channel_manager, urls, ClientOptions::default())
314 }
315
316 pub fn with_query_and_control_managers<U, A>(
321 query_channel_manager: ChannelManager,
322 control_channel_manager: ChannelManager,
323 urls: A,
324 ) -> Self
325 where
326 U: AsRef<str>,
327 A: AsRef<[U]>,
328 {
329 Self::with_query_and_control_managers_and_options(
330 query_channel_manager,
331 control_channel_manager,
332 urls,
333 ClientOptions::default(),
334 )
335 }
336
337 fn with_query_and_control_managers_and_options<U, A>(
338 query_channel_manager: ChannelManager,
339 control_channel_manager: ChannelManager,
340 urls: A,
341 options: ClientOptions,
342 ) -> Self
343 where
344 U: AsRef<str>,
345 A: AsRef<[U]>,
346 {
347 let urls: Vec<String> = urls
348 .as_ref()
349 .iter()
350 .map(|peer| peer.as_ref().to_string())
351 .collect();
352 Self {
353 inner: Arc::new(Inner::with_managers_and_peers(
354 query_channel_manager,
355 control_channel_manager,
356 urls,
357 options,
358 )),
359 }
360 }
361
362 #[deprecated(
366 note = "shares this manager and its pool between query and control lanes; use `with_query_and_control_managers` instead"
367 )]
368 pub fn with_manager_and_urls_and_options<U, A>(
369 channel_manager: ChannelManager,
370 urls: A,
371 options: ClientOptions,
372 ) -> Self
373 where
374 U: AsRef<str>,
375 A: AsRef<[U]>,
376 {
377 let channel_manager_for_query = channel_manager.clone();
378 Self::with_query_and_control_managers_and_options(
380 channel_manager_for_query,
381 channel_manager,
382 urls,
383 options,
384 )
385 }
386
387 pub fn start<U, A>(&self, urls: A)
388 where
389 U: AsRef<str>,
390 A: AsRef<[U]>,
391 {
392 let urls = urls
393 .as_ref()
394 .iter()
395 .map(|peer| peer.as_ref().to_string())
396 .collect();
397 self.inner.set_peers(urls);
398 }
399
400 fn trigger_health_check(&self) {
401 if self.inner.health_check_interval.is_zero() || self.inner.peer_count() <= 1 {
402 return;
403 }
404
405 if self
406 .inner
407 .health_check_started
408 .swap(true, Ordering::Relaxed)
409 {
410 return;
411 }
412
413 let inner = Arc::downgrade(&self.inner);
414 let health_check_interval = self.inner.health_check_interval;
415 common_runtime::spawn_global(async move {
416 tokio::time::sleep(random_initial_delay(health_check_interval)).await;
417 let mut interval = tokio::time::interval(health_check_interval);
418 interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
419
420 loop {
421 interval.tick().await;
422 let Some(inner) = inner.upgrade() else {
423 return;
424 };
425 if inner.peer_count() > 1 {
426 inner.refresh_peer_states().await;
427 }
428 }
429 });
430 }
431
432 pub fn find_channel(&self) -> Result<(String, Channel)> {
433 self.trigger_health_check();
434
435 let addr = self
436 .inner
437 .get_peer()
438 .context(error::IllegalGrpcClientStateSnafu {
439 err_msg: "No available peer found",
440 })?;
441
442 let channel = self
443 .inner
444 .control_channel_manager
445 .get(&addr)
446 .context(error::CreateChannelSnafu { addr: &addr })?;
447 Ok((addr, channel))
448 }
449
450 pub fn max_grpc_recv_message_size(&self) -> usize {
451 self.inner
452 .control_channel_manager
453 .config()
454 .max_recv_message_size
455 .as_bytes() as usize
456 }
457
458 pub fn max_grpc_send_message_size(&self) -> usize {
459 self.inner
460 .control_channel_manager
461 .config()
462 .max_send_message_size
463 .as_bytes() as usize
464 }
465
466 pub fn make_flight_client(
470 &self,
471 send_compression: bool,
472 accept_compression: bool,
473 ) -> Result<FlightClient> {
474 self.make_flight_client_with_manager(
475 &self.inner.query_channel_manager,
476 send_compression,
477 accept_compression,
478 )
479 }
480
481 pub(crate) fn make_control_flight_client(
482 &self,
483 send_compression: bool,
484 accept_compression: bool,
485 ) -> Result<FlightClient> {
486 self.make_flight_client_with_manager(
487 &self.inner.control_channel_manager,
488 send_compression,
489 accept_compression,
490 )
491 }
492
493 fn make_flight_client_with_manager(
494 &self,
495 channel_manager: &ChannelManager,
496 send_compression: bool,
497 accept_compression: bool,
498 ) -> Result<FlightClient> {
499 self.trigger_health_check();
500 let addr = self
501 .inner
502 .get_peer()
503 .context(error::IllegalGrpcClientStateSnafu {
504 err_msg: "No available peer found",
505 })?;
506 let channel = channel_manager
507 .get(&addr)
508 .context(error::CreateChannelSnafu { addr: &addr })?;
509
510 let mut client = FlightServiceClient::new(channel)
511 .max_decoding_message_size(
512 channel_manager.config().max_recv_message_size.as_bytes() as usize
513 )
514 .max_encoding_message_size(
515 channel_manager.config().max_send_message_size.as_bytes() as usize
516 );
517 if send_compression {
519 client = client.send_compressed(CompressionEncoding::Zstd);
520 }
521 if accept_compression {
522 client = client.accept_compressed(CompressionEncoding::Zstd);
523 }
524
525 Ok(FlightClient { addr, client })
526 }
527
528 pub(crate) fn raw_region_client(&self) -> Result<(String, tonic::client::Grpc<Channel>)> {
530 let (addr, channel) = self.find_channel()?;
531 let client = tonic::client::Grpc::new(channel)
532 .max_decoding_message_size(self.max_grpc_recv_message_size())
533 .max_encoding_message_size(self.max_grpc_send_message_size());
534 Ok((addr, client))
535 }
536
537 pub(crate) fn raw_flow_client(&self) -> Result<(String, PbFlowClient<Channel>)> {
538 let (addr, channel) = self.find_channel()?;
539 let client = PbFlowClient::new(channel)
540 .max_decoding_message_size(
541 self.inner
542 .control_channel_manager
543 .config()
544 .max_recv_message_size
545 .as_bytes() as usize,
546 )
547 .max_encoding_message_size(
548 self.inner
549 .control_channel_manager
550 .config()
551 .max_send_message_size
552 .as_bytes() as usize,
553 )
554 .accept_compressed(CompressionEncoding::Zstd)
555 .send_compressed(CompressionEncoding::Zstd);
556 Ok((addr, client))
557 }
558
559 pub fn make_prometheus_gateway_client(&self) -> Result<PrometheusGatewayClient<Channel>> {
560 let (_, channel) = self.find_channel()?;
561 let client = PrometheusGatewayClient::new(channel)
562 .accept_compressed(CompressionEncoding::Gzip)
563 .accept_compressed(CompressionEncoding::Zstd)
564 .send_compressed(CompressionEncoding::Gzip)
565 .send_compressed(CompressionEncoding::Zstd);
566 Ok(client)
567 }
568
569 pub async fn health_check(&self) -> Result<()> {
570 let (_, channel) = self.find_channel()?;
571 let mut client = HealthCheckClient::new(channel);
572 let _ = client.health_check(HealthCheckRequest {}).await?;
573 Ok(())
574 }
575
576 #[cfg(feature = "testing")]
578 pub fn channel_pool_sizes(&self) -> (usize, usize) {
579 let pool_size = |channel_manager: &ChannelManager| {
580 let mut size = 0;
581 channel_manager.retain_channel(|_, _| {
582 size += 1;
583 true
584 });
585 size
586 };
587 (
588 pool_size(&self.inner.query_channel_manager),
589 pool_size(&self.inner.control_channel_manager),
590 )
591 }
592
593 #[cfg(feature = "testing")]
595 pub fn peer_addresses_by_state(&self) -> (Vec<String>, Vec<String>) {
596 let peers = self.inner.peers.read();
597 let addresses = |indices: &[usize]| {
598 indices
599 .iter()
600 .map(|&index| peers.addresses[index].clone())
601 .collect()
602 };
603 (
604 addresses(&peers.states.active),
605 addresses(&peers.states.inactive),
606 )
607 }
608}
609
610#[cfg(test)]
611#[allow(deprecated)]
612mod tests {
613 use std::collections::HashSet;
614 use std::sync::Arc;
615 use std::sync::atomic::Ordering;
616 use std::time::Duration;
617
618 use api::v1::health_check_server::{HealthCheck, HealthCheckServer};
619 use api::v1::{HealthCheckRequest, HealthCheckResponse};
620 use common_grpc::channel_manager::ChannelManager;
621 use tokio::net::TcpListener;
622 use tokio::sync::Notify;
623 use tokio::task::JoinHandle;
624 use tokio::time::{interval, timeout};
625 use tokio_stream::wrappers::TcpListenerStream;
626 use tonic::{Request, Response, Status};
627
628 use super::{Client, ClientOptions, Inner, PeerStates};
629 use crate::load_balance::Loadbalancer;
630
631 const HEALTH_REFRESH_INTERVAL: Duration = Duration::from_millis(10);
632 const STATE_REFRESH_TIMEOUT: Duration = Duration::from_secs(1);
633
634 struct HealthyHealthCheck;
635
636 #[tonic::async_trait]
637 impl HealthCheck for HealthyHealthCheck {
638 async fn health_check(
639 &self,
640 _request: Request<HealthCheckRequest>,
641 ) -> Result<Response<HealthCheckResponse>, Status> {
642 Ok(Response::new(HealthCheckResponse {}))
643 }
644 }
645
646 struct UnhealthyHealthCheck;
647
648 #[tonic::async_trait]
649 impl HealthCheck for UnhealthyHealthCheck {
650 async fn health_check(
651 &self,
652 _request: Request<HealthCheckRequest>,
653 ) -> Result<Response<HealthCheckResponse>, Status> {
654 Err(Status::unavailable("peer is unavailable"))
655 }
656 }
657
658 struct PendingHealthCheck {
659 started: Option<Arc<Notify>>,
660 }
661
662 #[tonic::async_trait]
663 impl HealthCheck for PendingHealthCheck {
664 async fn health_check(
665 &self,
666 _request: Request<HealthCheckRequest>,
667 ) -> Result<Response<HealthCheckResponse>, Status> {
668 if let Some(started) = &self.started {
669 started.notify_one();
670 }
671 std::future::pending().await
672 }
673 }
674
675 async fn start_health_check_server<T>(handler: T) -> (String, JoinHandle<()>)
676 where
677 T: HealthCheck + Send + Sync + 'static,
678 {
679 let listener = TcpListener::bind("127.0.0.1:0")
680 .await
681 .expect("bind health check server");
682 let addr = listener
683 .local_addr()
684 .expect("read health check server address")
685 .to_string();
686 let server = tokio::spawn(async move {
687 tonic::transport::Server::builder()
688 .add_service(HealthCheckServer::new(handler))
689 .serve_with_incoming(TcpListenerStream::new(listener))
690 .await
691 .expect("serve health check server");
692 });
693
694 (addr, server)
695 }
696
697 async fn wait_for_peer_states(client: &Client, expected: PeerStates) {
698 let mut poll = interval(HEALTH_REFRESH_INTERVAL);
699 timeout(STATE_REFRESH_TIMEOUT, async {
700 loop {
701 poll.tick().await;
702 if client.inner.peers.read().states == expected {
703 return;
704 }
705 }
706 })
707 .await
708 .expect("health refresh did not reach expected peer states");
709 }
710
711 fn mock_peers() -> Vec<String> {
712 vec![
713 "127.0.0.1:3001".to_string(),
714 "127.0.0.1:3002".to_string(),
715 "127.0.0.1:3003".to_string(),
716 ]
717 }
718
719 #[tokio::test]
720 async fn test_explicit_dual_manager_constructor_uses_isolated_reused_channel_pools() {
721 let query_channel_manager = ChannelManager::new();
722 let control_channel_manager = ChannelManager::new();
723 let client = Client::with_query_and_control_managers(
724 query_channel_manager.clone(),
725 control_channel_manager.clone(),
726 ["127.0.0.1:3001"],
727 );
728
729 client.make_flight_client(false, false).unwrap();
730 client.make_flight_client(false, false).unwrap();
731 assert_eq!(1, channel_pool_size(&query_channel_manager));
732 assert_eq!(0, channel_pool_size(&control_channel_manager));
733
734 client.make_control_flight_client(false, false).unwrap();
735 client.make_control_flight_client(false, false).unwrap();
736 assert_eq!(1, channel_pool_size(&query_channel_manager));
737 assert_eq!(1, channel_pool_size(&control_channel_manager));
738 }
739
740 fn channel_pool_size(manager: &ChannelManager) -> usize {
741 let mut size = 0;
742 manager.retain_channel(|_, _| {
743 size += 1;
744 true
745 });
746 size
747 }
748
749 #[test]
750 fn test_inner() {
751 let inner = Inner::default();
752
753 assert!(matches!(
754 inner.load_balance,
755 Loadbalancer::Random(crate::load_balance::Random)
756 ));
757 assert!(inner.get_peer().is_none());
758
759 let peers = mock_peers();
760 let all: HashSet<String> = peers.iter().cloned().collect();
761 let inner =
762 Inner::with_manager_and_peers(ChannelManager::new(), peers, ClientOptions::default());
763
764 for _ in 0..20 {
765 assert!(all.contains(&inner.get_peer().unwrap()));
766 }
767 }
768
769 #[test]
770 fn test_inner_prefers_active_peer() {
771 let peers = mock_peers();
772 let inner = Inner::with_manager_and_peers(
773 ChannelManager::new(),
774 peers.clone(),
775 ClientOptions::default(),
776 );
777 inner.peers.write().states = PeerStates {
778 active: vec![0],
779 inactive: vec![1, 2],
780 };
781
782 assert_eq!(Some(peers[0].clone()), inner.get_peer());
783 }
784
785 #[test]
786 fn test_zero_health_check_interval_disables_background_task() {
787 let client = Client::with_urls_and_options(
788 mock_peers(),
789 ClientOptions {
790 health_check_interval: Duration::ZERO,
791 ..Default::default()
792 },
793 );
794
795 assert!(!client.inner.health_check_started.load(Ordering::Relaxed));
796 let peers = client.inner.peers.read();
797 assert_eq!(mock_peers(), peers.addresses);
798 assert_eq!(vec![0, 1, 2], peers.states.active);
799 assert!(peers.states.inactive.is_empty());
800 }
801
802 #[test]
803 fn test_multi_peer_constructor_defers_background_task() {
804 let client = Client::with_urls(mock_peers());
805
806 assert!(!client.inner.health_check_started.load(Ordering::Relaxed));
807 }
808
809 #[tokio::test]
810 async fn test_single_peer_does_not_start_background_task() {
811 let client = Client::with_urls(["127.0.0.1:3001"]);
812
813 client.find_channel().unwrap();
814
815 assert!(!client.inner.health_check_started.load(Ordering::Relaxed));
816 }
817
818 #[test]
819 fn test_start_initializes_new_client_without_starting_background_task() {
820 let client = Client::new();
821 let peers = mock_peers();
822
823 client.start(peers.clone());
824
825 assert!(peers.contains(&client.inner.get_peer().unwrap()));
826 assert!(!client.inner.health_check_started.load(Ordering::Relaxed));
827 }
828
829 #[tokio::test]
830 async fn test_health_refresh_marks_unhealthy_peer_inactive_and_selects_healthy_peer() {
831 let (healthy_addr, healthy_server) = start_health_check_server(HealthyHealthCheck).await;
833 let (unhealthy_addr, unhealthy_server) =
834 start_health_check_server(UnhealthyHealthCheck).await;
835 let client = Client::with_urls_and_options(
836 [healthy_addr.clone(), unhealthy_addr],
837 ClientOptions {
838 health_check_interval: HEALTH_REFRESH_INTERVAL,
839 ..Default::default()
840 },
841 );
842 assert!(!client.inner.health_check_started.load(Ordering::Relaxed));
843
844 client.find_channel().unwrap();
846 assert!(client.inner.health_check_started.load(Ordering::Relaxed));
847 wait_for_peer_states(
848 &client,
849 PeerStates {
850 active: vec![0],
851 inactive: vec![1],
852 },
853 )
854 .await;
855
856 assert_eq!(Some(healthy_addr), client.inner.get_peer());
858
859 healthy_server.abort();
860 unhealthy_server.abort();
861 }
862
863 #[tokio::test]
864 async fn test_health_refresh_times_out_pending_peer() {
865 let (healthy_addr, healthy_server) = start_health_check_server(HealthyHealthCheck).await;
866 let (pending_addr, pending_server) =
867 start_health_check_server(PendingHealthCheck { started: None }).await;
868 let client = Client::with_urls_and_options(
869 [healthy_addr, pending_addr],
870 ClientOptions {
871 health_check_interval: HEALTH_REFRESH_INTERVAL,
872 health_check_timeout: Duration::from_millis(20),
873 },
874 );
875
876 client.find_channel().unwrap();
877 wait_for_peer_states(
878 &client,
879 PeerStates {
880 active: vec![0],
881 inactive: vec![1],
882 },
883 )
884 .await;
885
886 healthy_server.abort();
887 pending_server.abort();
888 }
889
890 #[tokio::test]
891 async fn test_peer_update_ignores_in_flight_health_result() {
892 let started = Arc::new(Notify::new());
893 let (pending_addr, pending_server) = start_health_check_server(PendingHealthCheck {
894 started: Some(started.clone()),
895 })
896 .await;
897 let inner = Arc::new(Inner::with_manager_and_peers(
898 ChannelManager::new(),
899 vec![pending_addr],
900 ClientOptions {
901 health_check_timeout: Duration::from_millis(20),
902 ..Default::default()
903 },
904 ));
905 let refresh_inner = inner.clone();
906 let refresh = tokio::spawn(async move {
907 refresh_inner.refresh_peer_states().await;
908 });
909 timeout(STATE_REFRESH_TIMEOUT, started.notified())
910 .await
911 .expect("pending health check did not start");
912
913 inner.set_peers(vec!["127.0.0.1:3001".to_string()]);
914 refresh.await.unwrap();
915
916 let peers = inner.peers.read();
917 assert_eq!(vec!["127.0.0.1:3001"], peers.addresses);
918 assert_eq!(vec![0], peers.states.active);
919 assert!(peers.states.inactive.is_empty());
920 pending_server.abort();
921 }
922}