Skip to main content

client/
client.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use 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 api::v1::region::region_client::RegionClient as PbRegionClient;
24use arrow_flight::flight_service_client::FlightServiceClient;
25use common_grpc::channel_manager::{
26    ChannelConfig, ChannelManager, ClientTlsOption, load_client_tls_config,
27};
28use parking_lot::RwLock;
29use snafu::{OptionExt, ResultExt};
30use tonic::codec::CompressionEncoding;
31use tonic::transport::Channel;
32
33use crate::load_balance::{LoadBalance, Loadbalancer};
34use crate::{Result, error};
35
36const DEFAULT_HEALTH_CHECK_INTERVAL: Duration = Duration::from_secs(30);
37const DEFAULT_HEALTH_CHECK_TIMEOUT: Duration = Duration::from_secs(1);
38
39/// Options for a gRPC client.
40#[derive(Clone, Debug)]
41pub struct ClientOptions {
42    /// Interval for refreshing peer health. `Duration::ZERO` disables background health checks.
43    pub health_check_interval: Duration,
44    /// Timeout for checking the health of a peer.
45    pub health_check_timeout: Duration,
46}
47
48impl Default for ClientOptions {
49    fn default() -> Self {
50        Self {
51            health_check_interval: DEFAULT_HEALTH_CHECK_INTERVAL,
52            health_check_timeout: DEFAULT_HEALTH_CHECK_TIMEOUT,
53        }
54    }
55}
56
57pub struct FlightClient {
58    addr: String,
59    client: FlightServiceClient<Channel>,
60}
61
62impl FlightClient {
63    pub fn addr(&self) -> &str {
64        &self.addr
65    }
66
67    pub fn mut_inner(&mut self) -> &mut FlightServiceClient<Channel> {
68        &mut self.client
69    }
70}
71
72#[derive(Clone, Debug, Default)]
73pub struct Client {
74    inner: Arc<Inner>,
75}
76
77#[derive(Debug)]
78struct Inner {
79    query_channel_manager: ChannelManager,
80    control_channel_manager: ChannelManager,
81    peers: RwLock<Peers>,
82    load_balance: Loadbalancer,
83    health_check_interval: Duration,
84    health_check_timeout: Duration,
85    health_check_started: AtomicBool,
86}
87
88impl Default for Inner {
89    fn default() -> Self {
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    fn with_manager_and_peers(
109        channel_manager: ChannelManager,
110        peers: Vec<String>,
111        options: ClientOptions,
112    ) -> Self {
113        Self::with_managers_and_peers(channel_manager.clone(), channel_manager, peers, options)
114    }
115
116    fn with_managers_and_peers(
117        query_channel_manager: ChannelManager,
118        control_channel_manager: ChannelManager,
119        peers: Vec<String>,
120        options: ClientOptions,
121    ) -> Self {
122        let peer_count = peers.len();
123        Self {
124            query_channel_manager,
125            control_channel_manager,
126            peers: RwLock::new(Peers {
127                addresses: peers,
128                states: PeerStates {
129                    active: (0..peer_count).collect(),
130                    inactive: Vec::new(),
131                },
132                generation: 0,
133            }),
134            load_balance: Loadbalancer::default(),
135            health_check_interval: options.health_check_interval,
136            health_check_timeout: options.health_check_timeout,
137            health_check_started: AtomicBool::new(false),
138        }
139    }
140
141    fn set_peers(&self, addresses: Vec<String>) {
142        let peer_count = addresses.len();
143        let mut peers = self.peers.write();
144        peers.addresses = addresses;
145        peers.states = PeerStates {
146            active: (0..peer_count).collect(),
147            inactive: Vec::new(),
148        };
149        peers.generation = peers.generation.wrapping_add(1);
150    }
151
152    fn peer_count(&self) -> usize {
153        self.peers.read().addresses.len()
154    }
155
156    fn get_peer(&self) -> Option<String> {
157        let peers = self.peers.read();
158        let index = self
159            .load_balance
160            .get_index(&peers.states.active)
161            .or_else(|| self.load_balance.get_index(&peers.states.inactive))?;
162        Some(peers.addresses[*index].clone())
163    }
164
165    async fn refresh_peer_states(&self) {
166        let (generation, peers) = {
167            let peers = self.peers.read();
168            let addresses = peers
169                .states
170                .active
171                .iter()
172                .chain(&peers.states.inactive)
173                .map(|&index| (index, peers.addresses[index].clone()))
174                .collect::<Vec<_>>();
175            (peers.generation, addresses)
176        };
177        let health_checks = peers.into_iter().map(|(index, addr)| async move {
178            let is_active = self.check_peer_health(&addr).await;
179            (index, is_active)
180        });
181        let results = futures::future::join_all(health_checks).await;
182
183        let (active, inactive) = results.into_iter().fold(
184            (Vec::new(), Vec::new()),
185            |(mut active, mut inactive), (index, is_active)| {
186                if is_active {
187                    active.push(index);
188                } else {
189                    inactive.push(index);
190                }
191                (active, inactive)
192            },
193        );
194
195        let mut peers = self.peers.write();
196        if peers.generation == generation {
197            peers.states = PeerStates { active, inactive };
198        }
199    }
200
201    async fn check_peer_health(&self, addr: &str) -> bool {
202        let Ok(channel) = self.control_channel_manager.get(addr) else {
203            return false;
204        };
205        let mut client = HealthCheckClient::new(channel);
206        tokio::time::timeout(
207            self.health_check_timeout,
208            client.health_check(HealthCheckRequest {}),
209        )
210        .await
211        .is_ok_and(|result| result.is_ok())
212    }
213}
214
215fn random_initial_delay(max_delay: Duration) -> Duration {
216    let max_nanos = max_delay.as_nanos().min(u64::MAX as u128) as u64;
217    if max_nanos == 0 {
218        return Duration::ZERO;
219    }
220
221    Duration::from_nanos(rand::random_range(0..max_nanos))
222}
223
224impl Client {
225    pub fn new() -> Self {
226        Default::default()
227    }
228
229    pub fn with_urls<U, A>(urls: A) -> Self
230    where
231        U: AsRef<str>,
232        A: AsRef<[U]>,
233    {
234        Self::with_urls_and_options(urls, ClientOptions::default())
235    }
236
237    /// Creates a client with URLs and custom options.
238    pub fn with_urls_and_options<U, A>(urls: A, options: ClientOptions) -> Self
239    where
240        U: AsRef<str>,
241        A: AsRef<[U]>,
242    {
243        Self::with_manager_and_urls_and_options(ChannelManager::new(), urls, options)
244    }
245
246    pub fn with_tls_and_urls<U, A>(urls: A, client_tls: ClientTlsOption) -> Result<Self>
247    where
248        U: AsRef<str>,
249        A: AsRef<[U]>,
250    {
251        Self::with_tls_and_urls_and_options(urls, client_tls, ClientOptions::default())
252    }
253
254    /// Creates a client with TLS URLs and custom options.
255    pub fn with_tls_and_urls_and_options<U, A>(
256        urls: A,
257        client_tls: ClientTlsOption,
258        options: ClientOptions,
259    ) -> Result<Self>
260    where
261        U: AsRef<str>,
262        A: AsRef<[U]>,
263    {
264        let channel_config = ChannelConfig::default().client_tls_config(client_tls.clone());
265        let tls_config =
266            load_client_tls_config(Some(client_tls)).context(error::CreateTlsChannelSnafu)?;
267        let channel_manager = ChannelManager::with_config(channel_config, tls_config);
268        Ok(Self::with_manager_and_urls_and_options(
269            channel_manager,
270            urls,
271            options,
272        ))
273    }
274
275    pub fn with_manager_and_urls<U, A>(channel_manager: ChannelManager, urls: A) -> Self
276    where
277        U: AsRef<str>,
278        A: AsRef<[U]>,
279    {
280        Self::with_manager_and_urls_and_options(channel_manager, urls, ClientOptions::default())
281    }
282
283    pub(crate) fn with_managers_and_urls<U, A>(
284        query_channel_manager: ChannelManager,
285        control_channel_manager: ChannelManager,
286        urls: A,
287    ) -> Self
288    where
289        U: AsRef<str>,
290        A: AsRef<[U]>,
291    {
292        Self::with_managers_and_urls_and_options(
293            query_channel_manager,
294            control_channel_manager,
295            urls,
296            ClientOptions::default(),
297        )
298    }
299
300    fn with_managers_and_urls_and_options<U, A>(
301        query_channel_manager: ChannelManager,
302        control_channel_manager: ChannelManager,
303        urls: A,
304        options: ClientOptions,
305    ) -> Self
306    where
307        U: AsRef<str>,
308        A: AsRef<[U]>,
309    {
310        let urls: Vec<String> = urls
311            .as_ref()
312            .iter()
313            .map(|peer| peer.as_ref().to_string())
314            .collect();
315        Self {
316            inner: Arc::new(Inner::with_managers_and_peers(
317                query_channel_manager,
318                control_channel_manager,
319                urls,
320                options,
321            )),
322        }
323    }
324
325    /// Creates a client with a channel manager, URLs, and custom options.
326    pub fn with_manager_and_urls_and_options<U, A>(
327        channel_manager: ChannelManager,
328        urls: A,
329        options: ClientOptions,
330    ) -> Self
331    where
332        U: AsRef<str>,
333        A: AsRef<[U]>,
334    {
335        let channel_manager_for_query = channel_manager.clone();
336        Self::with_managers_and_urls_and_options(
337            channel_manager_for_query,
338            channel_manager,
339            urls,
340            options,
341        )
342    }
343
344    pub fn start<U, A>(&self, urls: A)
345    where
346        U: AsRef<str>,
347        A: AsRef<[U]>,
348    {
349        let urls = urls
350            .as_ref()
351            .iter()
352            .map(|peer| peer.as_ref().to_string())
353            .collect();
354        self.inner.set_peers(urls);
355    }
356
357    fn trigger_health_check(&self) {
358        if self.inner.health_check_interval.is_zero() || self.inner.peer_count() <= 1 {
359            return;
360        }
361
362        if self
363            .inner
364            .health_check_started
365            .swap(true, Ordering::Relaxed)
366        {
367            return;
368        }
369
370        let inner = Arc::downgrade(&self.inner);
371        let health_check_interval = self.inner.health_check_interval;
372        common_runtime::spawn_global(async move {
373            tokio::time::sleep(random_initial_delay(health_check_interval)).await;
374            let mut interval = tokio::time::interval(health_check_interval);
375            interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
376
377            loop {
378                interval.tick().await;
379                let Some(inner) = inner.upgrade() else {
380                    return;
381                };
382                if inner.peer_count() > 1 {
383                    inner.refresh_peer_states().await;
384                }
385            }
386        });
387    }
388
389    pub fn find_channel(&self) -> Result<(String, Channel)> {
390        self.trigger_health_check();
391
392        let addr = self
393            .inner
394            .get_peer()
395            .context(error::IllegalGrpcClientStateSnafu {
396                err_msg: "No available peer found",
397            })?;
398
399        let channel = self
400            .inner
401            .control_channel_manager
402            .get(&addr)
403            .context(error::CreateChannelSnafu { addr: &addr })?;
404        Ok((addr, channel))
405    }
406
407    pub fn max_grpc_recv_message_size(&self) -> usize {
408        self.inner
409            .control_channel_manager
410            .config()
411            .max_recv_message_size
412            .as_bytes() as usize
413    }
414
415    pub fn max_grpc_send_message_size(&self) -> usize {
416        self.inner
417            .control_channel_manager
418            .config()
419            .max_send_message_size
420            .as_bytes() as usize
421    }
422
423    /// Creates a Flight client on the query lane for DoGet/distributed reads.
424    ///
425    /// This public name is retained for compatibility.
426    pub fn make_flight_client(
427        &self,
428        send_compression: bool,
429        accept_compression: bool,
430    ) -> Result<FlightClient> {
431        self.make_flight_client_with_manager(
432            &self.inner.query_channel_manager,
433            send_compression,
434            accept_compression,
435        )
436    }
437
438    pub(crate) fn make_control_flight_client(
439        &self,
440        send_compression: bool,
441        accept_compression: bool,
442    ) -> Result<FlightClient> {
443        self.make_flight_client_with_manager(
444            &self.inner.control_channel_manager,
445            send_compression,
446            accept_compression,
447        )
448    }
449
450    fn make_flight_client_with_manager(
451        &self,
452        channel_manager: &ChannelManager,
453        send_compression: bool,
454        accept_compression: bool,
455    ) -> Result<FlightClient> {
456        self.trigger_health_check();
457        let addr = self
458            .inner
459            .get_peer()
460            .context(error::IllegalGrpcClientStateSnafu {
461                err_msg: "No available peer found",
462            })?;
463        let channel = channel_manager
464            .get(&addr)
465            .context(error::CreateChannelSnafu { addr: &addr })?;
466
467        let mut client = FlightServiceClient::new(channel)
468            .max_decoding_message_size(
469                channel_manager.config().max_recv_message_size.as_bytes() as usize
470            )
471            .max_encoding_message_size(
472                channel_manager.config().max_send_message_size.as_bytes() as usize
473            );
474        // todo(hl): support compression methods.
475        if send_compression {
476            client = client.send_compressed(CompressionEncoding::Zstd);
477        }
478        if accept_compression {
479            client = client.accept_compressed(CompressionEncoding::Zstd);
480        }
481
482        Ok(FlightClient { addr, client })
483    }
484
485    pub(crate) fn raw_region_client(&self) -> Result<(String, PbRegionClient<Channel>)> {
486        let (addr, channel) = self.find_channel()?;
487        let client = PbRegionClient::new(channel)
488            .max_decoding_message_size(
489                self.inner
490                    .control_channel_manager
491                    .config()
492                    .max_recv_message_size
493                    .as_bytes() as usize,
494            )
495            .max_encoding_message_size(
496                self.inner
497                    .control_channel_manager
498                    .config()
499                    .max_send_message_size
500                    .as_bytes() as usize,
501            );
502        Ok((addr, client))
503    }
504
505    pub(crate) fn raw_flow_client(&self) -> Result<(String, PbFlowClient<Channel>)> {
506        let (addr, channel) = self.find_channel()?;
507        let client = PbFlowClient::new(channel)
508            .max_decoding_message_size(
509                self.inner
510                    .control_channel_manager
511                    .config()
512                    .max_recv_message_size
513                    .as_bytes() as usize,
514            )
515            .max_encoding_message_size(
516                self.inner
517                    .control_channel_manager
518                    .config()
519                    .max_send_message_size
520                    .as_bytes() as usize,
521            )
522            .accept_compressed(CompressionEncoding::Zstd)
523            .send_compressed(CompressionEncoding::Zstd);
524        Ok((addr, client))
525    }
526
527    pub fn make_prometheus_gateway_client(&self) -> Result<PrometheusGatewayClient<Channel>> {
528        let (_, channel) = self.find_channel()?;
529        let client = PrometheusGatewayClient::new(channel)
530            .accept_compressed(CompressionEncoding::Gzip)
531            .accept_compressed(CompressionEncoding::Zstd)
532            .send_compressed(CompressionEncoding::Gzip)
533            .send_compressed(CompressionEncoding::Zstd);
534        Ok(client)
535    }
536
537    pub async fn health_check(&self) -> Result<()> {
538        let (_, channel) = self.find_channel()?;
539        let mut client = HealthCheckClient::new(channel);
540        let _ = client.health_check(HealthCheckRequest {}).await?;
541        Ok(())
542    }
543
544    /// Returns peer addresses grouped by active and inactive state for tests.
545    #[cfg(feature = "testing")]
546    pub fn peer_addresses_by_state(&self) -> (Vec<String>, Vec<String>) {
547        let peers = self.inner.peers.read();
548        let addresses = |indices: &[usize]| {
549            indices
550                .iter()
551                .map(|&index| peers.addresses[index].clone())
552                .collect()
553        };
554        (
555            addresses(&peers.states.active),
556            addresses(&peers.states.inactive),
557        )
558    }
559}
560
561#[cfg(test)]
562mod tests {
563    use std::collections::HashSet;
564    use std::sync::Arc;
565    use std::sync::atomic::Ordering;
566    use std::time::Duration;
567
568    use api::v1::health_check_server::{HealthCheck, HealthCheckServer};
569    use api::v1::{HealthCheckRequest, HealthCheckResponse};
570    use common_grpc::channel_manager::ChannelManager;
571    use tokio::net::TcpListener;
572    use tokio::sync::Notify;
573    use tokio::task::JoinHandle;
574    use tokio::time::{interval, timeout};
575    use tokio_stream::wrappers::TcpListenerStream;
576    use tonic::{Request, Response, Status};
577
578    use super::{Client, ClientOptions, Inner, PeerStates};
579    use crate::load_balance::Loadbalancer;
580
581    const HEALTH_REFRESH_INTERVAL: Duration = Duration::from_millis(10);
582    const STATE_REFRESH_TIMEOUT: Duration = Duration::from_secs(1);
583
584    struct HealthyHealthCheck;
585
586    #[tonic::async_trait]
587    impl HealthCheck for HealthyHealthCheck {
588        async fn health_check(
589            &self,
590            _request: Request<HealthCheckRequest>,
591        ) -> Result<Response<HealthCheckResponse>, Status> {
592            Ok(Response::new(HealthCheckResponse {}))
593        }
594    }
595
596    struct UnhealthyHealthCheck;
597
598    #[tonic::async_trait]
599    impl HealthCheck for UnhealthyHealthCheck {
600        async fn health_check(
601            &self,
602            _request: Request<HealthCheckRequest>,
603        ) -> Result<Response<HealthCheckResponse>, Status> {
604            Err(Status::unavailable("peer is unavailable"))
605        }
606    }
607
608    struct PendingHealthCheck {
609        started: Option<Arc<Notify>>,
610    }
611
612    #[tonic::async_trait]
613    impl HealthCheck for PendingHealthCheck {
614        async fn health_check(
615            &self,
616            _request: Request<HealthCheckRequest>,
617        ) -> Result<Response<HealthCheckResponse>, Status> {
618            if let Some(started) = &self.started {
619                started.notify_one();
620            }
621            std::future::pending().await
622        }
623    }
624
625    async fn start_health_check_server<T>(handler: T) -> (String, JoinHandle<()>)
626    where
627        T: HealthCheck + Send + Sync + 'static,
628    {
629        let listener = TcpListener::bind("127.0.0.1:0")
630            .await
631            .expect("bind health check server");
632        let addr = listener
633            .local_addr()
634            .expect("read health check server address")
635            .to_string();
636        let server = tokio::spawn(async move {
637            tonic::transport::Server::builder()
638                .add_service(HealthCheckServer::new(handler))
639                .serve_with_incoming(TcpListenerStream::new(listener))
640                .await
641                .expect("serve health check server");
642        });
643
644        (addr, server)
645    }
646
647    async fn wait_for_peer_states(client: &Client, expected: PeerStates) {
648        let mut poll = interval(HEALTH_REFRESH_INTERVAL);
649        timeout(STATE_REFRESH_TIMEOUT, async {
650            loop {
651                poll.tick().await;
652                if client.inner.peers.read().states == expected {
653                    return;
654                }
655            }
656        })
657        .await
658        .expect("health refresh did not reach expected peer states");
659    }
660
661    fn mock_peers() -> Vec<String> {
662        vec![
663            "127.0.0.1:3001".to_string(),
664            "127.0.0.1:3002".to_string(),
665            "127.0.0.1:3003".to_string(),
666        ]
667    }
668
669    #[test]
670    fn test_inner() {
671        let inner = Inner::default();
672
673        assert!(matches!(
674            inner.load_balance,
675            Loadbalancer::Random(crate::load_balance::Random)
676        ));
677        assert!(inner.get_peer().is_none());
678
679        let peers = mock_peers();
680        let all: HashSet<String> = peers.iter().cloned().collect();
681        let inner =
682            Inner::with_manager_and_peers(ChannelManager::new(), peers, ClientOptions::default());
683
684        for _ in 0..20 {
685            assert!(all.contains(&inner.get_peer().unwrap()));
686        }
687    }
688
689    #[test]
690    fn test_inner_prefers_active_peer() {
691        let peers = mock_peers();
692        let inner = Inner::with_manager_and_peers(
693            ChannelManager::new(),
694            peers.clone(),
695            ClientOptions::default(),
696        );
697        inner.peers.write().states = PeerStates {
698            active: vec![0],
699            inactive: vec![1, 2],
700        };
701
702        assert_eq!(Some(peers[0].clone()), inner.get_peer());
703    }
704
705    #[test]
706    fn test_zero_health_check_interval_disables_background_task() {
707        let client = Client::with_urls_and_options(
708            mock_peers(),
709            ClientOptions {
710                health_check_interval: Duration::ZERO,
711                ..Default::default()
712            },
713        );
714
715        assert!(!client.inner.health_check_started.load(Ordering::Relaxed));
716        let peers = client.inner.peers.read();
717        assert_eq!(mock_peers(), peers.addresses);
718        assert_eq!(vec![0, 1, 2], peers.states.active);
719        assert!(peers.states.inactive.is_empty());
720    }
721
722    #[test]
723    fn test_multi_peer_constructor_defers_background_task() {
724        let client = Client::with_urls(mock_peers());
725
726        assert!(!client.inner.health_check_started.load(Ordering::Relaxed));
727    }
728
729    #[tokio::test]
730    async fn test_single_peer_does_not_start_background_task() {
731        let client = Client::with_urls(["127.0.0.1:3001"]);
732
733        client.find_channel().unwrap();
734
735        assert!(!client.inner.health_check_started.load(Ordering::Relaxed));
736    }
737
738    #[test]
739    fn test_start_initializes_new_client_without_starting_background_task() {
740        let client = Client::new();
741        let peers = mock_peers();
742
743        client.start(peers.clone());
744
745        assert!(peers.contains(&client.inner.get_peer().unwrap()));
746        assert!(!client.inner.health_check_started.load(Ordering::Relaxed));
747    }
748
749    #[tokio::test]
750    async fn test_health_refresh_marks_unhealthy_peer_inactive_and_selects_healthy_peer() {
751        // Arrange: one peer responds to health checks and the other rejects them.
752        let (healthy_addr, healthy_server) = start_health_check_server(HealthyHealthCheck).await;
753        let (unhealthy_addr, unhealthy_server) =
754            start_health_check_server(UnhealthyHealthCheck).await;
755        let client = Client::with_urls_and_options(
756            [healthy_addr.clone(), unhealthy_addr],
757            ClientOptions {
758                health_check_interval: HEALTH_REFRESH_INTERVAL,
759                ..Default::default()
760            },
761        );
762        assert!(!client.inner.health_check_started.load(Ordering::Relaxed));
763
764        // Act: trigger lazy health checks, then poll until the background refresh completes.
765        client.find_channel().unwrap();
766        assert!(client.inner.health_check_started.load(Ordering::Relaxed));
767        wait_for_peer_states(
768            &client,
769            PeerStates {
770                active: vec![0],
771                inactive: vec![1],
772            },
773        )
774        .await;
775
776        // Assert: an inactive peer does not prevent selection of its active peer.
777        assert_eq!(Some(healthy_addr), client.inner.get_peer());
778
779        healthy_server.abort();
780        unhealthy_server.abort();
781    }
782
783    #[tokio::test]
784    async fn test_health_refresh_times_out_pending_peer() {
785        let (healthy_addr, healthy_server) = start_health_check_server(HealthyHealthCheck).await;
786        let (pending_addr, pending_server) =
787            start_health_check_server(PendingHealthCheck { started: None }).await;
788        let client = Client::with_urls_and_options(
789            [healthy_addr, pending_addr],
790            ClientOptions {
791                health_check_interval: HEALTH_REFRESH_INTERVAL,
792                health_check_timeout: Duration::from_millis(20),
793            },
794        );
795
796        client.find_channel().unwrap();
797        wait_for_peer_states(
798            &client,
799            PeerStates {
800                active: vec![0],
801                inactive: vec![1],
802            },
803        )
804        .await;
805
806        healthy_server.abort();
807        pending_server.abort();
808    }
809
810    #[tokio::test]
811    async fn test_peer_update_ignores_in_flight_health_result() {
812        let started = Arc::new(Notify::new());
813        let (pending_addr, pending_server) = start_health_check_server(PendingHealthCheck {
814            started: Some(started.clone()),
815        })
816        .await;
817        let inner = Arc::new(Inner::with_manager_and_peers(
818            ChannelManager::new(),
819            vec![pending_addr],
820            ClientOptions {
821                health_check_timeout: Duration::from_millis(20),
822                ..Default::default()
823            },
824        ));
825        let refresh_inner = inner.clone();
826        let refresh = tokio::spawn(async move {
827            refresh_inner.refresh_peer_states().await;
828        });
829        timeout(STATE_REFRESH_TIMEOUT, started.notified())
830            .await
831            .expect("pending health check did not start");
832
833        inner.set_peers(vec!["127.0.0.1:3001".to_string()]);
834        refresh.await.unwrap();
835
836        let peers = inner.peers.read();
837        assert_eq!(vec!["127.0.0.1:3001"], peers.addresses);
838        assert_eq!(vec![0], peers.states.active);
839        assert!(peers.states.inactive.is_empty());
840        pending_server.abort();
841    }
842}