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    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        Self::with_manager_and_peers(ChannelManager::new(), Vec::new(), ClientOptions::default())
90    }
91}
92
93#[derive(Debug, Default, PartialEq, Eq)]
94struct PeerStates {
95    active: Vec<usize>,
96    inactive: Vec<usize>,
97}
98
99#[derive(Debug, Default)]
100struct Peers {
101    addresses: Vec<String>,
102    states: PeerStates,
103    generation: u64,
104}
105
106impl Inner {
107    fn with_manager_and_peers(
108        channel_manager: ChannelManager,
109        peers: Vec<String>,
110        options: ClientOptions,
111    ) -> Self {
112        let peer_count = peers.len();
113        Self {
114            channel_manager,
115            peers: RwLock::new(Peers {
116                addresses: peers,
117                states: PeerStates {
118                    active: (0..peer_count).collect(),
119                    inactive: Vec::new(),
120                },
121                generation: 0,
122            }),
123            load_balance: Loadbalancer::default(),
124            health_check_interval: options.health_check_interval,
125            health_check_timeout: options.health_check_timeout,
126            health_check_started: AtomicBool::new(false),
127        }
128    }
129
130    fn set_peers(&self, addresses: Vec<String>) {
131        let peer_count = addresses.len();
132        let mut peers = self.peers.write();
133        peers.addresses = addresses;
134        peers.states = PeerStates {
135            active: (0..peer_count).collect(),
136            inactive: Vec::new(),
137        };
138        peers.generation = peers.generation.wrapping_add(1);
139    }
140
141    fn peer_count(&self) -> usize {
142        self.peers.read().addresses.len()
143    }
144
145    fn get_peer(&self) -> Option<String> {
146        let peers = self.peers.read();
147        let index = self
148            .load_balance
149            .get_index(&peers.states.active)
150            .or_else(|| self.load_balance.get_index(&peers.states.inactive))?;
151        Some(peers.addresses[*index].clone())
152    }
153
154    async fn refresh_peer_states(&self) {
155        let (generation, peers) = {
156            let peers = self.peers.read();
157            let addresses = peers
158                .states
159                .active
160                .iter()
161                .chain(&peers.states.inactive)
162                .map(|&index| (index, peers.addresses[index].clone()))
163                .collect::<Vec<_>>();
164            (peers.generation, addresses)
165        };
166        let health_checks = peers.into_iter().map(|(index, addr)| async move {
167            let is_active = self.check_peer_health(&addr).await;
168            (index, is_active)
169        });
170        let results = futures::future::join_all(health_checks).await;
171
172        let (active, inactive) = results.into_iter().fold(
173            (Vec::new(), Vec::new()),
174            |(mut active, mut inactive), (index, is_active)| {
175                if is_active {
176                    active.push(index);
177                } else {
178                    inactive.push(index);
179                }
180                (active, inactive)
181            },
182        );
183
184        let mut peers = self.peers.write();
185        if peers.generation == generation {
186            peers.states = PeerStates { active, inactive };
187        }
188    }
189
190    async fn check_peer_health(&self, addr: &str) -> bool {
191        let Ok(channel) = self.channel_manager.get(addr) else {
192            return false;
193        };
194        let mut client = HealthCheckClient::new(channel);
195        tokio::time::timeout(
196            self.health_check_timeout,
197            client.health_check(HealthCheckRequest {}),
198        )
199        .await
200        .is_ok_and(|result| result.is_ok())
201    }
202}
203
204fn random_initial_delay(max_delay: Duration) -> Duration {
205    let max_nanos = max_delay.as_nanos().min(u64::MAX as u128) as u64;
206    if max_nanos == 0 {
207        return Duration::ZERO;
208    }
209
210    Duration::from_nanos(rand::random_range(0..max_nanos))
211}
212
213impl Client {
214    pub fn new() -> Self {
215        Default::default()
216    }
217
218    pub fn with_urls<U, A>(urls: A) -> Self
219    where
220        U: AsRef<str>,
221        A: AsRef<[U]>,
222    {
223        Self::with_urls_and_options(urls, ClientOptions::default())
224    }
225
226    /// Creates a client with URLs and custom options.
227    pub fn with_urls_and_options<U, A>(urls: A, options: ClientOptions) -> Self
228    where
229        U: AsRef<str>,
230        A: AsRef<[U]>,
231    {
232        Self::with_manager_and_urls_and_options(ChannelManager::new(), urls, options)
233    }
234
235    pub fn with_tls_and_urls<U, A>(urls: A, client_tls: ClientTlsOption) -> Result<Self>
236    where
237        U: AsRef<str>,
238        A: AsRef<[U]>,
239    {
240        Self::with_tls_and_urls_and_options(urls, client_tls, ClientOptions::default())
241    }
242
243    /// Creates a client with TLS URLs and custom options.
244    pub fn with_tls_and_urls_and_options<U, A>(
245        urls: A,
246        client_tls: ClientTlsOption,
247        options: ClientOptions,
248    ) -> Result<Self>
249    where
250        U: AsRef<str>,
251        A: AsRef<[U]>,
252    {
253        let channel_config = ChannelConfig::default().client_tls_config(client_tls.clone());
254        let tls_config =
255            load_client_tls_config(Some(client_tls)).context(error::CreateTlsChannelSnafu)?;
256        let channel_manager = ChannelManager::with_config(channel_config, tls_config);
257        Ok(Self::with_manager_and_urls_and_options(
258            channel_manager,
259            urls,
260            options,
261        ))
262    }
263
264    pub fn with_manager_and_urls<U, A>(channel_manager: ChannelManager, urls: A) -> Self
265    where
266        U: AsRef<str>,
267        A: AsRef<[U]>,
268    {
269        Self::with_manager_and_urls_and_options(channel_manager, urls, ClientOptions::default())
270    }
271
272    /// Creates a client with a channel manager, URLs, and custom options.
273    pub fn with_manager_and_urls_and_options<U, A>(
274        channel_manager: ChannelManager,
275        urls: A,
276        options: ClientOptions,
277    ) -> Self
278    where
279        U: AsRef<str>,
280        A: AsRef<[U]>,
281    {
282        let urls: Vec<String> = urls
283            .as_ref()
284            .iter()
285            .map(|peer| peer.as_ref().to_string())
286            .collect();
287        Self {
288            inner: Arc::new(Inner::with_manager_and_peers(
289                channel_manager,
290                urls,
291                options,
292            )),
293        }
294    }
295
296    pub fn start<U, A>(&self, urls: A)
297    where
298        U: AsRef<str>,
299        A: AsRef<[U]>,
300    {
301        let urls = urls
302            .as_ref()
303            .iter()
304            .map(|peer| peer.as_ref().to_string())
305            .collect();
306        self.inner.set_peers(urls);
307    }
308
309    fn trigger_health_check(&self) {
310        if self.inner.health_check_interval.is_zero() || self.inner.peer_count() <= 1 {
311            return;
312        }
313
314        if self
315            .inner
316            .health_check_started
317            .swap(true, Ordering::Relaxed)
318        {
319            return;
320        }
321
322        let inner = Arc::downgrade(&self.inner);
323        let health_check_interval = self.inner.health_check_interval;
324        common_runtime::spawn_global(async move {
325            tokio::time::sleep(random_initial_delay(health_check_interval)).await;
326            let mut interval = tokio::time::interval(health_check_interval);
327            interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
328
329            loop {
330                interval.tick().await;
331                let Some(inner) = inner.upgrade() else {
332                    return;
333                };
334                if inner.peer_count() > 1 {
335                    inner.refresh_peer_states().await;
336                }
337            }
338        });
339    }
340
341    pub fn find_channel(&self) -> Result<(String, Channel)> {
342        self.trigger_health_check();
343
344        let addr = self
345            .inner
346            .get_peer()
347            .context(error::IllegalGrpcClientStateSnafu {
348                err_msg: "No available peer found",
349            })?;
350
351        let channel = self
352            .inner
353            .channel_manager
354            .get(&addr)
355            .context(error::CreateChannelSnafu { addr: &addr })?;
356        Ok((addr, channel))
357    }
358
359    pub fn max_grpc_recv_message_size(&self) -> usize {
360        self.inner
361            .channel_manager
362            .config()
363            .max_recv_message_size
364            .as_bytes() as usize
365    }
366
367    pub fn max_grpc_send_message_size(&self) -> usize {
368        self.inner
369            .channel_manager
370            .config()
371            .max_send_message_size
372            .as_bytes() as usize
373    }
374
375    pub fn make_flight_client(
376        &self,
377        send_compression: bool,
378        accept_compression: bool,
379    ) -> Result<FlightClient> {
380        let (addr, channel) = self.find_channel()?;
381
382        let mut client = FlightServiceClient::new(channel)
383            .max_decoding_message_size(self.max_grpc_recv_message_size())
384            .max_encoding_message_size(self.max_grpc_send_message_size());
385        // todo(hl): support compression methods.
386        if send_compression {
387            client = client.send_compressed(CompressionEncoding::Zstd);
388        }
389        if accept_compression {
390            client = client.accept_compressed(CompressionEncoding::Zstd);
391        }
392
393        Ok(FlightClient { addr, client })
394    }
395
396    pub(crate) fn raw_region_client(&self) -> Result<(String, PbRegionClient<Channel>)> {
397        let (addr, channel) = self.find_channel()?;
398        let client = PbRegionClient::new(channel)
399            .max_decoding_message_size(self.max_grpc_recv_message_size())
400            .max_encoding_message_size(self.max_grpc_send_message_size());
401        Ok((addr, client))
402    }
403
404    pub(crate) fn raw_flow_client(&self) -> Result<(String, PbFlowClient<Channel>)> {
405        let (addr, channel) = self.find_channel()?;
406        let client = PbFlowClient::new(channel)
407            .max_decoding_message_size(self.max_grpc_recv_message_size())
408            .max_encoding_message_size(self.max_grpc_send_message_size())
409            .accept_compressed(CompressionEncoding::Zstd)
410            .send_compressed(CompressionEncoding::Zstd);
411        Ok((addr, client))
412    }
413
414    pub fn make_prometheus_gateway_client(&self) -> Result<PrometheusGatewayClient<Channel>> {
415        let (_, channel) = self.find_channel()?;
416        let client = PrometheusGatewayClient::new(channel)
417            .accept_compressed(CompressionEncoding::Gzip)
418            .accept_compressed(CompressionEncoding::Zstd)
419            .send_compressed(CompressionEncoding::Gzip)
420            .send_compressed(CompressionEncoding::Zstd);
421        Ok(client)
422    }
423
424    pub async fn health_check(&self) -> Result<()> {
425        let (_, channel) = self.find_channel()?;
426        let mut client = HealthCheckClient::new(channel);
427        let _ = client.health_check(HealthCheckRequest {}).await?;
428        Ok(())
429    }
430
431    /// Returns peer addresses grouped by active and inactive state for tests.
432    #[cfg(feature = "testing")]
433    pub fn peer_addresses_by_state(&self) -> (Vec<String>, Vec<String>) {
434        let peers = self.inner.peers.read();
435        let addresses = |indices: &[usize]| {
436            indices
437                .iter()
438                .map(|&index| peers.addresses[index].clone())
439                .collect()
440        };
441        (
442            addresses(&peers.states.active),
443            addresses(&peers.states.inactive),
444        )
445    }
446}
447
448#[cfg(test)]
449mod tests {
450    use std::collections::HashSet;
451    use std::sync::Arc;
452    use std::sync::atomic::Ordering;
453    use std::time::Duration;
454
455    use api::v1::health_check_server::{HealthCheck, HealthCheckServer};
456    use api::v1::{HealthCheckRequest, HealthCheckResponse};
457    use common_grpc::channel_manager::ChannelManager;
458    use tokio::net::TcpListener;
459    use tokio::sync::Notify;
460    use tokio::task::JoinHandle;
461    use tokio::time::{interval, timeout};
462    use tokio_stream::wrappers::TcpListenerStream;
463    use tonic::{Request, Response, Status};
464
465    use super::{Client, ClientOptions, Inner, PeerStates};
466    use crate::load_balance::Loadbalancer;
467
468    const HEALTH_REFRESH_INTERVAL: Duration = Duration::from_millis(10);
469    const STATE_REFRESH_TIMEOUT: Duration = Duration::from_secs(1);
470
471    struct HealthyHealthCheck;
472
473    #[tonic::async_trait]
474    impl HealthCheck for HealthyHealthCheck {
475        async fn health_check(
476            &self,
477            _request: Request<HealthCheckRequest>,
478        ) -> Result<Response<HealthCheckResponse>, Status> {
479            Ok(Response::new(HealthCheckResponse {}))
480        }
481    }
482
483    struct UnhealthyHealthCheck;
484
485    #[tonic::async_trait]
486    impl HealthCheck for UnhealthyHealthCheck {
487        async fn health_check(
488            &self,
489            _request: Request<HealthCheckRequest>,
490        ) -> Result<Response<HealthCheckResponse>, Status> {
491            Err(Status::unavailable("peer is unavailable"))
492        }
493    }
494
495    struct PendingHealthCheck {
496        started: Option<Arc<Notify>>,
497    }
498
499    #[tonic::async_trait]
500    impl HealthCheck for PendingHealthCheck {
501        async fn health_check(
502            &self,
503            _request: Request<HealthCheckRequest>,
504        ) -> Result<Response<HealthCheckResponse>, Status> {
505            if let Some(started) = &self.started {
506                started.notify_one();
507            }
508            std::future::pending().await
509        }
510    }
511
512    async fn start_health_check_server<T>(handler: T) -> (String, JoinHandle<()>)
513    where
514        T: HealthCheck + Send + Sync + 'static,
515    {
516        let listener = TcpListener::bind("127.0.0.1:0")
517            .await
518            .expect("bind health check server");
519        let addr = listener
520            .local_addr()
521            .expect("read health check server address")
522            .to_string();
523        let server = tokio::spawn(async move {
524            tonic::transport::Server::builder()
525                .add_service(HealthCheckServer::new(handler))
526                .serve_with_incoming(TcpListenerStream::new(listener))
527                .await
528                .expect("serve health check server");
529        });
530
531        (addr, server)
532    }
533
534    async fn wait_for_peer_states(client: &Client, expected: PeerStates) {
535        let mut poll = interval(HEALTH_REFRESH_INTERVAL);
536        timeout(STATE_REFRESH_TIMEOUT, async {
537            loop {
538                poll.tick().await;
539                if client.inner.peers.read().states == expected {
540                    return;
541                }
542            }
543        })
544        .await
545        .expect("health refresh did not reach expected peer states");
546    }
547
548    fn mock_peers() -> Vec<String> {
549        vec![
550            "127.0.0.1:3001".to_string(),
551            "127.0.0.1:3002".to_string(),
552            "127.0.0.1:3003".to_string(),
553        ]
554    }
555
556    #[test]
557    fn test_inner() {
558        let inner = Inner::default();
559
560        assert!(matches!(
561            inner.load_balance,
562            Loadbalancer::Random(crate::load_balance::Random)
563        ));
564        assert!(inner.get_peer().is_none());
565
566        let peers = mock_peers();
567        let all: HashSet<String> = peers.iter().cloned().collect();
568        let inner =
569            Inner::with_manager_and_peers(ChannelManager::new(), peers, ClientOptions::default());
570
571        for _ in 0..20 {
572            assert!(all.contains(&inner.get_peer().unwrap()));
573        }
574    }
575
576    #[test]
577    fn test_inner_prefers_active_peer() {
578        let peers = mock_peers();
579        let inner = Inner::with_manager_and_peers(
580            ChannelManager::new(),
581            peers.clone(),
582            ClientOptions::default(),
583        );
584        inner.peers.write().states = PeerStates {
585            active: vec![0],
586            inactive: vec![1, 2],
587        };
588
589        assert_eq!(Some(peers[0].clone()), inner.get_peer());
590    }
591
592    #[test]
593    fn test_zero_health_check_interval_disables_background_task() {
594        let client = Client::with_urls_and_options(
595            mock_peers(),
596            ClientOptions {
597                health_check_interval: Duration::ZERO,
598                ..Default::default()
599            },
600        );
601
602        assert!(!client.inner.health_check_started.load(Ordering::Relaxed));
603        let peers = client.inner.peers.read();
604        assert_eq!(mock_peers(), peers.addresses);
605        assert_eq!(vec![0, 1, 2], peers.states.active);
606        assert!(peers.states.inactive.is_empty());
607    }
608
609    #[test]
610    fn test_multi_peer_constructor_defers_background_task() {
611        let client = Client::with_urls(mock_peers());
612
613        assert!(!client.inner.health_check_started.load(Ordering::Relaxed));
614    }
615
616    #[tokio::test]
617    async fn test_single_peer_does_not_start_background_task() {
618        let client = Client::with_urls(["127.0.0.1:3001"]);
619
620        client.find_channel().unwrap();
621
622        assert!(!client.inner.health_check_started.load(Ordering::Relaxed));
623    }
624
625    #[test]
626    fn test_start_initializes_new_client_without_starting_background_task() {
627        let client = Client::new();
628        let peers = mock_peers();
629
630        client.start(peers.clone());
631
632        assert!(peers.contains(&client.inner.get_peer().unwrap()));
633        assert!(!client.inner.health_check_started.load(Ordering::Relaxed));
634    }
635
636    #[tokio::test]
637    async fn test_health_refresh_marks_unhealthy_peer_inactive_and_selects_healthy_peer() {
638        // Arrange: one peer responds to health checks and the other rejects them.
639        let (healthy_addr, healthy_server) = start_health_check_server(HealthyHealthCheck).await;
640        let (unhealthy_addr, unhealthy_server) =
641            start_health_check_server(UnhealthyHealthCheck).await;
642        let client = Client::with_urls_and_options(
643            [healthy_addr.clone(), unhealthy_addr],
644            ClientOptions {
645                health_check_interval: HEALTH_REFRESH_INTERVAL,
646                ..Default::default()
647            },
648        );
649        assert!(!client.inner.health_check_started.load(Ordering::Relaxed));
650
651        // Act: trigger lazy health checks, then poll until the background refresh completes.
652        client.find_channel().unwrap();
653        assert!(client.inner.health_check_started.load(Ordering::Relaxed));
654        wait_for_peer_states(
655            &client,
656            PeerStates {
657                active: vec![0],
658                inactive: vec![1],
659            },
660        )
661        .await;
662
663        // Assert: an inactive peer does not prevent selection of its active peer.
664        assert_eq!(Some(healthy_addr), client.inner.get_peer());
665
666        healthy_server.abort();
667        unhealthy_server.abort();
668    }
669
670    #[tokio::test]
671    async fn test_health_refresh_times_out_pending_peer() {
672        let (healthy_addr, healthy_server) = start_health_check_server(HealthyHealthCheck).await;
673        let (pending_addr, pending_server) =
674            start_health_check_server(PendingHealthCheck { started: None }).await;
675        let client = Client::with_urls_and_options(
676            [healthy_addr, pending_addr],
677            ClientOptions {
678                health_check_interval: HEALTH_REFRESH_INTERVAL,
679                health_check_timeout: Duration::from_millis(20),
680            },
681        );
682
683        client.find_channel().unwrap();
684        wait_for_peer_states(
685            &client,
686            PeerStates {
687                active: vec![0],
688                inactive: vec![1],
689            },
690        )
691        .await;
692
693        healthy_server.abort();
694        pending_server.abort();
695    }
696
697    #[tokio::test]
698    async fn test_peer_update_ignores_in_flight_health_result() {
699        let started = Arc::new(Notify::new());
700        let (pending_addr, pending_server) = start_health_check_server(PendingHealthCheck {
701            started: Some(started.clone()),
702        })
703        .await;
704        let inner = Arc::new(Inner::with_manager_and_peers(
705            ChannelManager::new(),
706            vec![pending_addr],
707            ClientOptions {
708                health_check_timeout: Duration::from_millis(20),
709                ..Default::default()
710            },
711        ));
712        let refresh_inner = inner.clone();
713        let refresh = tokio::spawn(async move {
714            refresh_inner.refresh_peer_states().await;
715        });
716        timeout(STATE_REFRESH_TIMEOUT, started.notified())
717            .await
718            .expect("pending health check did not start");
719
720        inner.set_peers(vec!["127.0.0.1:3001".to_string()]);
721        refresh.await.unwrap();
722
723        let peers = inner.peers.read();
724        assert_eq!(vec!["127.0.0.1:3001"], peers.addresses);
725        assert_eq!(vec![0], peers.states.active);
726        assert!(peers.states.inactive.is_empty());
727        pending_server.abort();
728    }
729}