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 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#[derive(Clone, Debug)]
41pub struct ClientOptions {
42 pub health_check_interval: Duration,
44 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 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 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 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 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 #[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 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 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_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}