1use std::net::{AddrParseError, SocketAddr};
2use std::sync::{Arc, Once};
3use std::time::Duration;
4
5use clap::ValueEnum;
6use snafu::ResultExt;
7use tracing_subscriber::layer::SubscriberExt;
8use tracing_subscriber::{EnvFilter, Layer, fmt};
9
10use crate::error::{CfgPbServerEnvNotExistSnafu, Result};
11
12#[derive(ValueEnum, Debug, Clone, Copy)]
13pub enum StatusOp {
14 RemoteId,
16 Keys,
18 Sessions,
20}
21
22#[derive(Clone, Debug, Eq, PartialEq)]
40pub struct ResolvedAddrs {
41 addrs: Arc<[SocketAddr]>,
43}
44
45impl ResolvedAddrs {
46 fn new(name: &str, addrs: Vec<SocketAddr>, parse_error: AddrParseError) -> Result<Self> {
52 if addrs.is_empty() {
53 return Err(crate::error::Error::CfgParseSockAddr {
54 string: name.to_string(),
55 source: parse_error,
56 });
57 }
58 Ok(Self {
59 addrs: Arc::from(addrs),
60 })
61 }
62
63 #[must_use]
69 pub fn from_candidates(addrs: Vec<SocketAddr>) -> Option<Self> {
70 if addrs.is_empty() {
71 return None;
72 }
73 Some(Self {
74 addrs: Arc::from(addrs),
75 })
76 }
77
78 #[inline]
84 #[must_use]
85 pub fn as_slice(&self) -> &[SocketAddr] {
86 &self.addrs
87 }
88
89 #[inline]
95 #[must_use]
96 pub fn primary(&self) -> SocketAddr {
97 self.addrs[0]
100 }
101}
102
103impl std::fmt::Display for ResolvedAddrs {
104 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109 write!(formatter, "{}", self.primary())?;
110 match self.addrs.len() {
111 1 => Ok(()),
112 more => write!(formatter, " (+{} more)", more - 1),
113 }
114 }
115}
116
117impl From<SocketAddr> for ResolvedAddrs {
118 fn from(addr: SocketAddr) -> Self {
119 Self {
120 addrs: Arc::from(vec![addr]),
121 }
122 }
123}
124
125#[inline]
131pub fn resolve_addrs(addr: &str) -> Result<ResolvedAddrs> {
132 let parse_error = match addr.parse::<SocketAddr>() {
136 Ok(socket_addr) => return Ok(ResolvedAddrs::from(socket_addr)),
137 Err(error) => error,
138 };
139
140 let system = || match std::net::ToSocketAddrs::to_socket_addrs(addr) {
141 Ok(addrs) => addrs.collect(),
142 Err(_) => Vec::new(),
143 };
144
145 let addrs = if addr.starts_with("localhost:") {
146 system()
147 } else {
148 match crate::addr::get_socket_addrs(addr) {
149 Ok(addrs) if !addrs.is_empty() => addrs,
150 _ => system(),
151 }
152 };
153 ResolvedAddrs::new(addr, addrs, parse_error)
154}
155
156pub async fn resolve_addrs_async(addr: &str) -> Result<ResolvedAddrs> {
158 let parse_error = match addr.parse::<SocketAddr>() {
159 Ok(socket_addr) => return Ok(ResolvedAddrs::from(socket_addr)),
160 Err(error) => error,
161 };
162
163 async fn system(addr: &str) -> Vec<SocketAddr> {
164 match tokio::net::lookup_host(addr).await {
165 Ok(addrs) => addrs.collect(),
166 Err(_) => Vec::new(),
167 }
168 }
169
170 let addrs = if addr.starts_with("localhost:") {
171 system(addr).await
172 } else {
173 match crate::addr::get_socket_addrs_async(addr).await {
174 Ok(addrs) if !addrs.is_empty() => addrs,
175 _ => system(addr).await,
176 }
177 };
178 ResolvedAddrs::new(addr, addrs, parse_error)
179}
180
181const PB_MAPPER_SERVER: &str = "PB_MAPPER_SERVER";
182
183pub const PB_MAPPER_KEEP_ALIVE: &str = "PB_MAPPER_KEEP_ALIVE";
185pub const PB_MAPPER_CONTROL_IO_TIMEOUT: &str = "PB_MAPPER_CONTROL_IO_TIMEOUT";
186pub const PB_MAPPER_STREAM_ACK_TIMEOUT: &str = "PB_MAPPER_STREAM_ACK_TIMEOUT";
187pub const PB_MAPPER_STREAM_READY_TIMEOUT: &str = "PB_MAPPER_STREAM_READY_TIMEOUT";
188pub const PB_MAPPER_STREAM_RECOVERY_TIMEOUT: &str = "PB_MAPPER_STREAM_RECOVERY_TIMEOUT";
189pub const PB_MAPPER_CONTROL_CONN_POOL_SIZE: &str = "PB_MAPPER_CONTROL_CONN_POOL_SIZE";
190pub const PB_MAPPER_CONTROL_HEARTBEAT_INTERVAL: &str = "PB_MAPPER_CONTROL_HEARTBEAT_INTERVAL";
191pub const PB_MAPPER_CONTROL_HEARTBEAT_TOLERANCE: &str = "PB_MAPPER_CONTROL_HEARTBEAT_TOLERANCE";
192pub const PB_MAPPER_CONTROL_SUSPECT_GRACE: &str = "PB_MAPPER_CONTROL_SUSPECT_GRACE";
193pub const PB_MAPPER_REGISTRATION_PROBE_TIMEOUT: &str = "PB_MAPPER_REGISTRATION_PROBE_TIMEOUT";
194pub const PB_MAPPER_REGISTRATION_REJECT_BACKOFF_MIN: &str =
195 "PB_MAPPER_REGISTRATION_REJECT_BACKOFF_MIN";
196pub const PB_MAPPER_REGISTRATION_REJECT_BACKOFF_MAX: &str =
197 "PB_MAPPER_REGISTRATION_REJECT_BACKOFF_MAX";
198pub const PB_MAPPER_SERVER_LEASE_TIMEOUT: &str = "PB_MAPPER_SERVER_LEASE_TIMEOUT";
199pub const PB_MAPPER_SERVER_LEASE_SWEEP_INTERVAL: &str = "PB_MAPPER_SERVER_LEASE_SWEEP_INTERVAL";
200pub const PB_MAPPER_CLIENT_HEALTH_CHECK_INTERVAL: &str = "PB_MAPPER_CLIENT_HEALTH_CHECK_INTERVAL";
201pub const PB_MAPPER_CLIENT_HEALTH_CHECK_TIMEOUT: &str = "PB_MAPPER_CLIENT_HEALTH_CHECK_TIMEOUT";
202pub const PB_MAPPER_CLIENT_HEALTH_FAILURE_THRESHOLD: &str =
203 "PB_MAPPER_CLIENT_HEALTH_FAILURE_THRESHOLD";
204pub const PB_MAPPER_LOG_FORMAT: &str = "PB_MAPPER_LOG_FORMAT";
205const DEFAULT_CONTROL_IO_TIMEOUT: Duration = Duration::from_secs(30);
206const DEFAULT_STREAM_ACK_TIMEOUT: Duration = Duration::from_millis(300);
207const DEFAULT_STREAM_READY_TIMEOUT: Duration = Duration::from_secs(1);
208const DEFAULT_STREAM_RECOVERY_TIMEOUT: Duration = Duration::from_secs(2);
209const DEFAULT_CONTROL_CONN_POOL_SIZE: usize = 2;
210const DEFAULT_CONTROL_HEARTBEAT_INTERVAL: Duration = Duration::from_secs(2);
211const DEFAULT_CONTROL_HEARTBEAT_TOLERANCE: Duration = Duration::from_secs(6);
212const DEFAULT_CONTROL_SUSPECT_GRACE: Duration = Duration::from_secs(2);
213const DEFAULT_REGISTRATION_PROBE_TIMEOUT: Duration = Duration::from_secs(1);
214const DEFAULT_REGISTRATION_REJECT_BACKOFF_MIN: Duration = Duration::from_secs(5);
215const DEFAULT_REGISTRATION_REJECT_BACKOFF_MAX: Duration = Duration::from_secs(80);
216const DEFAULT_SERVER_LEASE_TIMEOUT: Duration = Duration::from_secs(15);
217const DEFAULT_SERVER_LEASE_SWEEP_INTERVAL: Duration = Duration::from_secs(5);
218const DEFAULT_CLIENT_HEALTH_CHECK_INTERVAL: Duration = Duration::from_secs(15);
219const DEFAULT_CLIENT_HEALTH_CHECK_TIMEOUT: Duration = Duration::from_secs(5);
220const DEFAULT_CLIENT_HEALTH_FAILURE_THRESHOLD: usize = 3;
221
222#[derive(Debug, Clone, Copy, PartialEq, Eq)]
223pub enum LogFormat {
224 Pretty,
225 Compact,
226 Json,
227}
228
229pub fn parse_log_format(value: &str) -> LogFormat {
230 match value.trim().to_ascii_lowercase().as_str() {
231 "compact" => LogFormat::Compact,
232 "json" => LogFormat::Json,
233 _ => LogFormat::Pretty,
234 }
235}
236
237fn log_format_from_env() -> LogFormat {
238 std::env::var(PB_MAPPER_LOG_FORMAT)
239 .ok()
240 .map(|value| parse_log_format(&value))
241 .unwrap_or(LogFormat::Pretty)
242}
243
244fn default_env_filter() -> EnvFilter {
245 EnvFilter::builder()
246 .with_default_directive(tracing::level_filters::LevelFilter::INFO.into())
247 .from_env_lossy()
248}
249
250pub fn parse_duration(value: &str) -> Option<Duration> {
251 let value = value.trim();
252 if value.is_empty() {
253 return None;
254 }
255 if let Some(raw) = value.strip_suffix("ms") {
256 return raw.trim().parse::<u64>().ok().map(Duration::from_millis);
257 }
258 if let Some(raw) = value.strip_suffix('s') {
259 return raw.trim().parse::<u64>().ok().map(Duration::from_secs);
260 }
261 if let Some(raw) = value.strip_suffix('m') {
262 return raw
263 .trim()
264 .parse::<u64>()
265 .ok()
266 .and_then(|minutes| minutes.checked_mul(60))
267 .map(Duration::from_secs);
268 }
269 if let Some(raw) = value.strip_suffix('h') {
270 return raw
271 .trim()
272 .parse::<u64>()
273 .ok()
274 .and_then(|hours| hours.checked_mul(60 * 60))
275 .map(Duration::from_secs);
276 }
277 value.parse::<u64>().ok().map(Duration::from_secs)
278}
279
280pub fn duration_from_env(name: &str, default: Duration) -> Duration {
281 std::env::var(name)
282 .ok()
283 .and_then(|value| parse_duration(&value))
284 .unwrap_or(default)
285}
286
287fn positive_duration_from_env(name: &str, default: Duration) -> Duration {
299 let value = duration_from_env(name, default);
300 if value.is_zero() {
301 tracing::warn!(
302 event = "config_zero_duration_ignored",
303 variable = name,
304 default = ?default,
305 "ignoring a zero duration and using the default instead"
306 );
307 return default;
308 }
309 value
310}
311
312pub fn control_io_timeout() -> Duration {
313 duration_from_env(PB_MAPPER_CONTROL_IO_TIMEOUT, DEFAULT_CONTROL_IO_TIMEOUT)
314}
315
316pub fn stream_ack_timeout() -> Duration {
317 duration_from_env(PB_MAPPER_STREAM_ACK_TIMEOUT, DEFAULT_STREAM_ACK_TIMEOUT)
318}
319
320pub fn stream_ready_timeout() -> Duration {
321 duration_from_env(PB_MAPPER_STREAM_READY_TIMEOUT, DEFAULT_STREAM_READY_TIMEOUT)
322}
323
324pub fn stream_recovery_timeout() -> Duration {
325 duration_from_env(
326 PB_MAPPER_STREAM_RECOVERY_TIMEOUT,
327 DEFAULT_STREAM_RECOVERY_TIMEOUT,
328 )
329}
330
331pub fn control_conn_pool_size() -> usize {
332 std::env::var(PB_MAPPER_CONTROL_CONN_POOL_SIZE)
333 .ok()
334 .and_then(|value| value.trim().parse::<usize>().ok())
335 .filter(|size| *size > 0)
336 .map(|size| size.min(16))
337 .unwrap_or(DEFAULT_CONTROL_CONN_POOL_SIZE)
338}
339
340pub fn control_heartbeat_interval() -> Duration {
341 duration_from_env(
342 PB_MAPPER_CONTROL_HEARTBEAT_INTERVAL,
343 DEFAULT_CONTROL_HEARTBEAT_INTERVAL,
344 )
345}
346
347pub fn control_heartbeat_tolerance() -> Duration {
348 duration_from_env(
349 PB_MAPPER_CONTROL_HEARTBEAT_TOLERANCE,
350 DEFAULT_CONTROL_HEARTBEAT_TOLERANCE,
351 )
352}
353
354pub fn control_suspect_grace() -> Duration {
355 duration_from_env(
356 PB_MAPPER_CONTROL_SUSPECT_GRACE,
357 DEFAULT_CONTROL_SUSPECT_GRACE,
358 )
359}
360
361pub fn registration_probe_timeout() -> Duration {
362 duration_from_env(
363 PB_MAPPER_REGISTRATION_PROBE_TIMEOUT,
364 DEFAULT_REGISTRATION_PROBE_TIMEOUT,
365 )
366}
367
368pub fn registration_reject_backoff() -> (Duration, Duration) {
393 let min = positive_duration_from_env(
394 PB_MAPPER_REGISTRATION_REJECT_BACKOFF_MIN,
395 DEFAULT_REGISTRATION_REJECT_BACKOFF_MIN,
396 );
397 let max = positive_duration_from_env(
398 PB_MAPPER_REGISTRATION_REJECT_BACKOFF_MAX,
399 DEFAULT_REGISTRATION_REJECT_BACKOFF_MAX,
400 )
401 .max(min);
402 (min, max)
403}
404
405pub fn server_lease_timeout() -> Duration {
406 duration_from_env(PB_MAPPER_SERVER_LEASE_TIMEOUT, DEFAULT_SERVER_LEASE_TIMEOUT)
407}
408
409pub fn server_lease_sweep_interval() -> Duration {
421 positive_duration_from_env(
422 PB_MAPPER_SERVER_LEASE_SWEEP_INTERVAL,
423 DEFAULT_SERVER_LEASE_SWEEP_INTERVAL,
424 )
425}
426
427pub fn client_health_check_interval() -> Duration {
428 duration_from_env(
429 PB_MAPPER_CLIENT_HEALTH_CHECK_INTERVAL,
430 DEFAULT_CLIENT_HEALTH_CHECK_INTERVAL,
431 )
432}
433
434pub fn client_health_check_timeout() -> Duration {
435 duration_from_env(
436 PB_MAPPER_CLIENT_HEALTH_CHECK_TIMEOUT,
437 DEFAULT_CLIENT_HEALTH_CHECK_TIMEOUT,
438 )
439}
440
441pub fn client_health_failure_threshold() -> usize {
442 std::env::var(PB_MAPPER_CLIENT_HEALTH_FAILURE_THRESHOLD)
443 .ok()
444 .and_then(|value| value.trim().parse::<usize>().ok())
445 .filter(|threshold| *threshold > 0)
446 .map(|threshold| threshold.min(100))
447 .unwrap_or(DEFAULT_CLIENT_HEALTH_FAILURE_THRESHOLD)
448}
449
450pub fn keep_alive_from_env() -> bool {
459 match std::env::var(PB_MAPPER_KEEP_ALIVE) {
460 Ok(value) => matches!(
463 value.trim().to_ascii_lowercase().as_str(),
464 "on" | "1" | "true" | "yes"
465 ),
466 Err(_) => false,
467 }
468}
469
470pub fn pb_mapper_server_addr(addr: Option<&str>) -> Result<String> {
477 match addr {
478 Some(addr) => Ok(addr.to_string()),
479 None => std::env::var(PB_MAPPER_SERVER).context(CfgPbServerEnvNotExistSnafu),
480 }
481}
482
483#[inline]
485pub fn resolve_pb_mapper_server(addr: Option<&str>) -> Result<ResolvedAddrs> {
486 match addr {
487 Some(addr) => resolve_addrs(addr),
488 None => {
489 let addr = std::env::var(PB_MAPPER_SERVER).context(CfgPbServerEnvNotExistSnafu)?;
490 resolve_addrs(&addr)
491 }
492 }
493}
494
495pub async fn resolve_pb_mapper_server_async(addr: Option<&str>) -> Result<ResolvedAddrs> {
497 match addr {
498 Some(addr) => resolve_addrs_async(addr).await,
499 None => {
500 let addr = std::env::var(PB_MAPPER_SERVER).context(CfgPbServerEnvNotExistSnafu)?;
501 resolve_addrs_async(&addr).await
502 }
503 }
504}
505
506pub fn init_tracing() {
507 static INIT_TRACING: Once = Once::new();
508 INIT_TRACING.call_once(|| {
509 let result = match log_format_from_env() {
510 LogFormat::Pretty => {
511 let subscriber = tracing_subscriber::registry().with(
512 fmt::layer()
513 .pretty()
514 .with_writer(std::io::stdout)
515 .with_filter(default_env_filter()),
516 );
517 tracing::subscriber::set_global_default(subscriber)
518 }
519 LogFormat::Compact => {
520 let subscriber = tracing_subscriber::registry().with(
521 fmt::layer()
522 .compact()
523 .with_writer(std::io::stdout)
524 .with_filter(default_env_filter()),
525 );
526 tracing::subscriber::set_global_default(subscriber)
527 }
528 LogFormat::Json => {
529 let subscriber = tracing_subscriber::registry().with(
530 fmt::layer()
531 .json()
532 .flatten_event(true)
533 .with_writer(std::io::stdout)
534 .with_filter(default_env_filter()),
535 );
536 tracing::subscriber::set_global_default(subscriber)
537 }
538 };
539
540 if let Err(e) = result {
541 eprintln!("failed to initialize tracing subscriber: {e}");
542 }
543 });
544}
545
546#[cfg(test)]
547mod tests {
548 use super::*;
549
550 #[test]
551 fn parse_log_format_accepts_supported_values() {
552 assert_eq!(parse_log_format("pretty"), LogFormat::Pretty);
553 assert_eq!(parse_log_format("compact"), LogFormat::Compact);
554 assert_eq!(parse_log_format("json"), LogFormat::Json);
555 assert_eq!(parse_log_format(" JSON "), LogFormat::Json);
556 assert_eq!(parse_log_format("unknown"), LogFormat::Pretty);
557 }
558
559 #[test]
563 fn resolved_addrs_keeps_every_candidate() {
564 let first: SocketAddr = "127.0.0.1:7666".parse().expect("literal");
565 let second: SocketAddr = "[::1]:7666".parse().expect("literal");
566 let addrs = ResolvedAddrs::from_candidates(vec![first, second]).expect("non-empty");
567
568 assert_eq!(addrs.as_slice(), [first, second]);
569 assert_eq!(addrs.primary(), first, "order is the resolver's order");
570 }
571
572 #[test]
576 fn resolved_addrs_rejects_an_empty_candidate_list() {
577 assert!(ResolvedAddrs::from_candidates(Vec::new()).is_none());
578 }
579
580 #[test]
583 fn resolved_addrs_renders_the_primary_and_the_rest_as_a_count() {
584 let single: SocketAddr = "127.0.0.1:7666".parse().expect("literal");
585 assert_eq!(ResolvedAddrs::from(single).to_string(), "127.0.0.1:7666");
586
587 let second: SocketAddr = "[::1]:7666".parse().expect("literal");
588 let both = ResolvedAddrs::from_candidates(vec![single, second]).expect("non-empty");
589 assert_eq!(both.to_string(), "127.0.0.1:7666 (+1 more)");
590 }
591
592 #[test]
594 fn resolve_addrs_passes_a_literal_through() {
595 let addrs = resolve_addrs("127.0.0.1:7666").expect("a literal always resolves");
596 assert_eq!(
597 addrs.as_slice(),
598 ["127.0.0.1:7666".parse().expect("literal")]
599 );
600 }
601
602 #[test]
605 fn resolve_addrs_keeps_every_localhost_record() {
606 let addrs = resolve_addrs("localhost:7666").expect("localhost always resolves");
607 assert!(
608 addrs.as_slice().iter().all(|addr| addr.ip().is_loopback()),
609 "localhost must resolve to loopback only, got {:?}",
610 addrs.as_slice()
611 );
612 }
613
614 #[test]
621 fn resolve_addrs_fails_when_nothing_can_be_resolved() {
622 assert!(resolve_addrs("127.0.0.1").is_err(), "no port");
623 assert!(resolve_addrs("localhost").is_err(), "no port");
624 }
625
626 #[tokio::test]
629 async fn resolve_addrs_async_matches_the_blocking_path_on_a_literal() {
630 let expected = resolve_addrs("127.0.0.1:7666").expect("literal");
631 let actual = resolve_addrs_async("127.0.0.1:7666")
632 .await
633 .expect("literal");
634 assert_eq!(actual, expected);
635 }
636}