Skip to main content

sz_orm_health/
advanced.rs

1//! 高级健康检查功能:缓存、级联检查、readiness/liveness 区分、超时
2//!
3//! 本模块在 [`DbHealthChecker`] 基础上补充生产级健康检查所需的核心能力:
4//!
5//! - **健康检查缓存**([`HealthCheckCache`]):带 TTL 的结果缓存,避免高频探活
6//!   对后端造成压力。缓存命中时直接返回上次结果,过期后重新检查。
7//! - **级联健康检查**([`CascadingHealthChecker`]):按依赖图检查资源及其依赖,
8//!   任一依赖不健康则级联标记资源不健康。支持循环依赖检测。
9//! - **Readiness / Liveness 探针**([`ProbeManager`]):区分 Kubernetes 风格的
10//!   liveness(进程存活)与 readiness(就绪可接流量)探针,两者独立管理。
11//! - **超时健康检查**([`TimeoutHealthChecker`]):通过独立线程 + mpsc 通道实现
12//!   真实超时,超时后返回 Unhealthy。提供检查耗时与超时率统计。
13
14use crate::{DbHealthChecker, HealthReport, HealthSnapshot, HealthStatus, HealthStatusProvider};
15use serde::{Deserialize, Serialize};
16use std::collections::{HashMap, HashSet};
17use std::sync::{mpsc, Arc, Mutex, RwLock};
18use std::time::{Duration, Instant};
19
20// ============================================================================
21// 健康检查缓存(HealthCheckCache)
22// ============================================================================
23
24/// 缓存的健康检查结果,记录检查时刻用于 TTL 判断。
25struct CachedReport {
26    report: HealthReport,
27    cached_at: Instant,
28}
29
30/// 缓存统计信息
31struct CacheStats {
32    /// 缓存命中次数(TTL 内直接返回缓存)
33    hits: u64,
34    /// 缓存未命中次数(TTL 过期或无缓存,触发实际检查)
35    misses: u64,
36    /// 主动失效次数(调用 invalidate 清除缓存)
37    evictions: u64,
38}
39
40/// 带 TTL 缓存的健康检查器。
41///
42/// 包装一个 [`DbHealthChecker`],在 TTL 有效期内直接返回缓存的 [`HealthReport`],
43/// 过期后才执行实际检查。适用于高频探活场景(如 Kubernetes 每 2 秒 liveness 探针),
44/// 避免每次探活都打到后端数据库。
45///
46/// # 线程安全
47///
48/// 内部使用 `RwLock` 保护缓存 map,`Mutex` 保护统计计数器,支持多线程并发访问。
49pub struct HealthCheckCache {
50    /// 被包装的实际健康检查器
51    inner: Arc<dyn DbHealthChecker>,
52    /// 缓存生存时间(TTL)
53    ttl: Duration,
54    /// 按 pool 名缓存的检查结果
55    cache: RwLock<HashMap<String, CachedReport>>,
56    /// 缓存统计
57    stats: Mutex<CacheStats>,
58}
59
60impl HealthCheckCache {
61    /// 创建带缓存的健康检查器
62    ///
63    /// # 参数
64    /// - `inner`:被包装的实际检查器
65    /// - `ttl`:缓存生存时间,超过此时间后下次检查会触发实际调用
66    pub fn new(inner: Arc<dyn DbHealthChecker>, ttl: Duration) -> Self {
67        Self {
68            inner,
69            ttl,
70            cache: RwLock::new(HashMap::new()),
71            stats: Mutex::new(CacheStats {
72                hits: 0,
73                misses: 0,
74                evictions: 0,
75            }),
76        }
77    }
78
79    /// 检查指定 pool 的健康状态(带缓存)。
80    ///
81    /// 若缓存中存在且未过期(`cached_at + ttl > now`),直接返回缓存结果(hit);
82    /// 否则调用内部检查器执行实际检查,更新缓存后返回(miss)。
83    pub fn check(&self, pool: &str) -> HealthReport {
84        // 先尝试读缓存(读锁)
85        if let Ok(cache) = self.cache.read() {
86            if let Some(cached) = cache.get(pool) {
87                if cached.cached_at.elapsed() < self.ttl {
88                    // 缓存命中
89                    if let Ok(mut stats) = self.stats.lock() {
90                        stats.hits += 1;
91                    }
92                    return cached.report.clone();
93                }
94            }
95        }
96
97        // 缓存未命中或已过期,执行实际检查
98        let report = self.inner.check(pool);
99        let cached = CachedReport {
100            report: report.clone(),
101            cached_at: Instant::now(),
102        };
103
104        // 写入缓存(写锁)
105        if let Ok(mut cache) = self.cache.write() {
106            cache.insert(pool.to_string(), cached);
107        }
108        if let Ok(mut stats) = self.stats.lock() {
109            stats.misses += 1;
110        }
111
112        report
113    }
114
115    /// 批量检查多个 pool(带缓存),委托给 [`Self::check`] 逐个处理。
116    pub fn check_all(&self, pools: &[&str]) -> Vec<HealthReport> {
117        pools.iter().map(|p| self.check(p)).collect()
118    }
119
120    /// 主动失效指定 pool 的缓存。返回 `true` 表示之前有缓存被清除。
121    pub fn invalidate(&self, pool: &str) -> bool {
122        let removed = if let Ok(mut cache) = self.cache.write() {
123            cache.remove(pool).is_some()
124        } else {
125            false
126        };
127        if removed {
128            if let Ok(mut stats) = self.stats.lock() {
129                stats.evictions += 1;
130            }
131        }
132        removed
133    }
134
135    /// 清空所有缓存。
136    pub fn clear(&self) {
137        if let Ok(mut cache) = self.cache.write() {
138            cache.clear();
139        }
140    }
141
142    /// 获取缓存统计快照:`(hits, misses, evictions)`
143    pub fn stats(&self) -> (u64, u64, u64) {
144        if let Ok(stats) = self.stats.lock() {
145            (stats.hits, stats.misses, stats.evictions)
146        } else {
147            (0, 0, 0)
148        }
149    }
150
151    /// 获取缓存命中率(0.0..=1.0)。无请求时返回 0.0。
152    pub fn hit_rate(&self) -> f64 {
153        if let Ok(stats) = self.stats.lock() {
154            let total = stats.hits + stats.misses;
155            if total == 0 {
156                0.0
157            } else {
158                stats.hits as f64 / total as f64
159            }
160        } else {
161            0.0
162        }
163    }
164
165    /// 获取 TTL
166    pub fn ttl(&self) -> Duration {
167        self.ttl
168    }
169}
170
171// ============================================================================
172// 级联健康检查(CascadingHealthChecker)
173// ============================================================================
174
175/// 级联健康检查结果:包含资源自身的报告及其所有依赖的报告。
176#[derive(Debug, Clone)]
177pub struct CascadingReport {
178    /// 资源自身的健康报告
179    pub report: HealthReport,
180    /// 所有依赖的健康报告(按检查顺序)
181    pub dependencies: Vec<HealthReport>,
182    /// 资源及其所有依赖是否全部健康
183    pub all_healthy: bool,
184}
185
186/// 级联健康检查器:检查资源时同时检查其依赖链。
187///
188/// 在 Kubernetes / 微服务架构中,一个服务的健康通常依赖下游服务。
189/// [`CascadingHealthChecker`] 维护一个 pool -> 依赖列表 的映射,检查时先检查
190/// 资源自身,再递归检查所有依赖。若任一依赖不健康,则 `all_healthy` 为 `false`。
191///
192/// # 循环依赖检测
193///
194/// 内部使用 `visited` 集合检测循环依赖,避免无限递归。遇到已访问的 pool 时跳过。
195pub struct CascadingHealthChecker {
196    /// 实际执行检查的检查器
197    checker: Arc<dyn DbHealthChecker>,
198    /// 依赖关系图:pool -> [依赖 pool 列表]
199    dependencies: RwLock<HashMap<String, Vec<String>>>,
200}
201
202impl CascadingHealthChecker {
203    /// 创建级联健康检查器
204    pub fn new(checker: Arc<dyn DbHealthChecker>) -> Self {
205        Self {
206            checker,
207            dependencies: RwLock::new(HashMap::new()),
208        }
209    }
210
211    /// 为指定 pool 添加一个依赖
212    pub fn add_dependency(&self, pool: &str, depends_on: impl Into<String>) {
213        if let Ok(mut deps) = self.dependencies.write() {
214            deps.entry(pool.to_string())
215                .or_default()
216                .push(depends_on.into());
217        }
218    }
219
220    /// 为指定 pool 批量添加依赖
221    pub fn add_dependencies(&self, pool: &str, deps: Vec<String>) {
222        if let Ok(mut map) = self.dependencies.write() {
223            map.entry(pool.to_string()).or_default().extend(deps);
224        }
225    }
226
227    /// 移除指定 pool 的某个依赖。返回 `true` 表示成功移除。
228    pub fn remove_dependency(&self, pool: &str, depends_on: &str) -> bool {
229        if let Ok(mut map) = self.dependencies.write() {
230            if let Some(deps) = map.get_mut(pool) {
231                let before = deps.len();
232                deps.retain(|d| d != depends_on);
233                return deps.len() < before;
234            }
235        }
236        false
237    }
238
239    /// 获取指定 pool 的依赖列表(拷贝)
240    pub fn dependencies(&self, pool: &str) -> Vec<String> {
241        if let Ok(map) = self.dependencies.read() {
242            map.get(pool).cloned().unwrap_or_default()
243        } else {
244            Vec::new()
245        }
246    }
247
248    /// 清除指定 pool 的所有依赖
249    pub fn clear_dependencies(&self, pool: &str) {
250        if let Ok(mut map) = self.dependencies.write() {
251            map.remove(pool);
252        }
253    }
254
255    /// 检查指定 pool 及其所有依赖(递归),返回级联报告。
256    ///
257    /// 使用 `visited` 集合防止循环依赖导致的无限递归。
258    pub fn check_with_deps(&self, pool: &str) -> CascadingReport {
259        let mut visited = HashSet::new();
260        visited.insert(pool.to_string());
261        let mut dep_reports = Vec::new();
262        self.collect_dep_reports(pool, &mut visited, &mut dep_reports);
263
264        let report = self.checker.check(pool);
265        let all_healthy = report.status == HealthStatus::Healthy
266            && dep_reports
267                .iter()
268                .all(|r| r.status == HealthStatus::Healthy);
269
270        CascadingReport {
271            report,
272            dependencies: dep_reports,
273            all_healthy,
274        }
275    }
276
277    /// 递归收集依赖的健康报告
278    fn collect_dep_reports(
279        &self,
280        pool: &str,
281        visited: &mut HashSet<String>,
282        reports: &mut Vec<HealthReport>,
283    ) {
284        let deps = self.dependencies(pool);
285        for dep in deps {
286            if visited.contains(&dep) {
287                // 循环依赖检测:跳过已访问的节点
288                continue;
289            }
290            visited.insert(dep.clone());
291            reports.push(self.checker.check(&dep));
292            self.collect_dep_reports(&dep, visited, reports);
293        }
294    }
295}
296
297// ============================================================================
298// Readiness / Liveness 探针(ProbeManager)
299// ============================================================================
300
301/// 探针类型:liveness(存活)或 readiness(就绪)。
302///
303/// 在 Kubernetes 中:
304/// - **Liveness** 探针失败会导致 Pod 重启(进程不健康)
305/// - **Readiness** 探针失败会从 Service Endpoints 中摘除 Pod(不接流量)
306#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
307pub enum ProbeKind {
308    /// 存活探针:进程是否在运行
309    Liveness,
310    /// 就绪探针:是否准备好接收流量
311    Readiness,
312}
313
314/// 探针检查结果
315#[derive(Debug, Clone, Serialize, Deserialize)]
316pub struct ProbeResult {
317    /// 探针类型
318    pub kind: ProbeKind,
319    /// 健康状态
320    pub status: HealthStatus,
321    /// 附加消息
322    pub message: String,
323    /// 检查时间戳(RFC3339)
324    pub timestamp: String,
325}
326
327/// 探针管理器:独立管理 liveness 和 readiness 探针。
328///
329/// 两种探针的状态独立设置和查询,互不影响。例如:
330/// - 进程刚启动时 liveness=Healthy 但 readiness=Unhealthy(正在加载缓存)
331/// - 依赖下游故障时 readiness=Unhealthy 但 liveness=Healthy(进程本身没问题)
332pub struct ProbeManager {
333    /// liveness 探针状态
334    liveness: RwLock<HashMap<String, HealthSnapshot>>,
335    /// readiness 探针状态
336    readiness: RwLock<HashMap<String, HealthSnapshot>>,
337}
338
339impl Default for ProbeManager {
340    fn default() -> Self {
341        Self::new()
342    }
343}
344
345impl ProbeManager {
346    /// 创建探针管理器
347    pub fn new() -> Self {
348        Self {
349            liveness: RwLock::new(HashMap::new()),
350            readiness: RwLock::new(HashMap::new()),
351        }
352    }
353
354    /// 设置 liveness 探针状态
355    pub fn set_liveness(&self, name: &str, snapshot: HealthSnapshot) {
356        if let Ok(mut map) = self.liveness.write() {
357            map.insert(name.to_string(), snapshot);
358        }
359    }
360
361    /// 设置 readiness 探针状态
362    pub fn set_readiness(&self, name: &str, snapshot: HealthSnapshot) {
363        if let Ok(mut map) = self.readiness.write() {
364            map.insert(name.to_string(), snapshot);
365        }
366    }
367
368    /// 查询单个探针的 liveness 状态
369    pub fn check_liveness(&self, name: &str) -> ProbeResult {
370        let snapshot = self.read_probe(ProbeKind::Liveness, name);
371        ProbeResult {
372            kind: ProbeKind::Liveness,
373            status: snapshot.status,
374            message: snapshot.message,
375            timestamp: chrono::Utc::now().to_rfc3339(),
376        }
377    }
378
379    /// 查询单个探针的 readiness 状态
380    pub fn check_readiness(&self, name: &str) -> ProbeResult {
381        let snapshot = self.read_probe(ProbeKind::Readiness, name);
382        ProbeResult {
383            kind: ProbeKind::Readiness,
384            status: snapshot.status,
385            message: snapshot.message,
386            timestamp: chrono::Utc::now().to_rfc3339(),
387        }
388    }
389
390    /// 查询所有 liveness 探针
391    pub fn liveness_all(&self) -> Vec<ProbeResult> {
392        self.all_probes(ProbeKind::Liveness)
393    }
394
395    /// 查询所有 readiness 探针
396    pub fn readiness_all(&self) -> Vec<ProbeResult> {
397        self.all_probes(ProbeKind::Readiness)
398    }
399
400    /// 聚合 liveness 状态:任一不健康则整体不健康,任一未知则整体未知
401    pub fn overall_liveness(&self) -> HealthStatus {
402        self.overall(ProbeKind::Liveness)
403    }
404
405    /// 聚合 readiness 状态:任一不健康则整体不健康,任一未知则整体未知
406    pub fn overall_readiness(&self) -> HealthStatus {
407        self.overall(ProbeKind::Readiness)
408    }
409
410    /// 读取指定探针的状态快照
411    fn read_probe(&self, kind: ProbeKind, name: &str) -> HealthSnapshot {
412        let map = match kind {
413            ProbeKind::Liveness => self.liveness.read(),
414            ProbeKind::Readiness => self.readiness.read(),
415        };
416        match map {
417            Ok(guard) => guard.get(name).cloned().unwrap_or_else(|| HealthSnapshot {
418                status: HealthStatus::Unknown,
419                connection_count: 0,
420                slow_queries: 0,
421                message: format!("no {:?} probe registered for '{}'", kind, name),
422            }),
423            Err(_) => HealthSnapshot {
424                status: HealthStatus::Unknown,
425                connection_count: 0,
426                slow_queries: 0,
427                message: "lock poisoned".to_string(),
428            },
429        }
430    }
431
432    /// 查询指定类型的所有探针
433    fn all_probes(&self, kind: ProbeKind) -> Vec<ProbeResult> {
434        let map = match kind {
435            ProbeKind::Liveness => self.liveness.read(),
436            ProbeKind::Readiness => self.readiness.read(),
437        };
438        let timestamp = chrono::Utc::now().to_rfc3339();
439        match map {
440            Ok(guard) => {
441                let mut results: Vec<ProbeResult> = guard
442                    .values()
443                    .map(|snap| ProbeResult {
444                        kind,
445                        status: snap.status.clone(),
446                        message: snap.message.clone(),
447                        timestamp: timestamp.clone(),
448                    })
449                    .collect();
450                // 按 message 排序保证输出确定性
451                results.sort_by(|a, b| a.message.cmp(&b.message));
452                results
453            }
454            Err(_) => Vec::new(),
455        }
456    }
457
458    /// 聚合指定类型的整体状态
459    fn overall(&self, kind: ProbeKind) -> HealthStatus {
460        let map = match kind {
461            ProbeKind::Liveness => self.liveness.read(),
462            ProbeKind::Readiness => self.readiness.read(),
463        };
464        match map {
465            Ok(guard) => {
466                if guard.is_empty() {
467                    return HealthStatus::Unknown;
468                }
469                let mut any_unknown = false;
470                for snap in guard.values() {
471                    match snap.status {
472                        HealthStatus::Unhealthy => return HealthStatus::Unhealthy,
473                        HealthStatus::Unknown => any_unknown = true,
474                        HealthStatus::Healthy => {}
475                    }
476                }
477                if any_unknown {
478                    HealthStatus::Unknown
479                } else {
480                    HealthStatus::Healthy
481                }
482            }
483            Err(_) => HealthStatus::Unknown,
484        }
485    }
486}
487
488// ============================================================================
489// 超时健康检查(TimeoutHealthChecker)
490// ============================================================================
491
492/// 超时检查统计
493struct TimeoutStats {
494    /// 总检查次数
495    total_checks: u64,
496    /// 超时次数
497    timeouts: u64,
498    /// 累计检查耗时
499    total_duration: Duration,
500}
501
502/// 超时统计快照(不可变视图)
503#[derive(Debug, Clone)]
504pub struct TimeoutStatsSnapshot {
505    /// 总检查次数
506    pub total_checks: u64,
507    /// 超时次数
508    pub timeouts: u64,
509    /// 累计检查耗时
510    pub total_duration: Duration,
511    /// 平均检查耗时
512    pub avg_duration: Duration,
513    /// 超时率(0.0..=1.0)
514    pub timeout_rate: f64,
515}
516
517/// 带超时的健康检查提供者。
518///
519/// 通过独立线程 + mpsc 通道实现真实超时:在调用内部 provider 的 `snapshot` 时,
520/// 若超过 `timeout` 仍未返回结果,则立即返回 Unhealthy(超时消息)。
521///
522/// 适用于包装可能阻塞的 provider(如远程 HTTP 健康检查),防止探活请求挂起
523/// 导致整个健康检查系统卡死。
524///
525/// # 注意
526///
527/// 超时后内部线程会被分离(detach),仍会在后台完成。这是 sync Rust 的限制:
528/// 无法真正中断一个正在执行的 sync 函数。
529pub struct TimeoutHealthChecker {
530    /// 被包装的 provider
531    inner: Arc<dyn HealthStatusProvider>,
532    /// 超时时长
533    timeout: Duration,
534    /// 统计信息
535    stats: Mutex<TimeoutStats>,
536}
537
538impl TimeoutHealthChecker {
539    /// 创建带超时的健康检查提供者
540    ///
541    /// # 参数
542    /// - `inner`:被包装的 provider
543    /// - `timeout`:超时时长,超过此时间未返回则标记为 Unhealthy
544    pub fn new(inner: Arc<dyn HealthStatusProvider>, timeout: Duration) -> Self {
545        Self {
546            inner,
547            timeout,
548            stats: Mutex::new(TimeoutStats {
549                total_checks: 0,
550                timeouts: 0,
551                total_duration: Duration::ZERO,
552            }),
553        }
554    }
555
556    /// 获取超时时长
557    pub fn timeout(&self) -> Duration {
558        self.timeout
559    }
560
561    /// 获取统计快照
562    pub fn stats(&self) -> TimeoutStatsSnapshot {
563        if let Ok(stats) = self.stats.lock() {
564            let avg = if stats.total_checks > 0 {
565                stats.total_duration / stats.total_checks as u32
566            } else {
567                Duration::ZERO
568            };
569            let rate = if stats.total_checks > 0 {
570                stats.timeouts as f64 / stats.total_checks as f64
571            } else {
572                0.0
573            };
574            TimeoutStatsSnapshot {
575                total_checks: stats.total_checks,
576                timeouts: stats.timeouts,
577                total_duration: stats.total_duration,
578                avg_duration: avg,
579                timeout_rate: rate,
580            }
581        } else {
582            TimeoutStatsSnapshot {
583                total_checks: 0,
584                timeouts: 0,
585                total_duration: Duration::ZERO,
586                avg_duration: Duration::ZERO,
587                timeout_rate: 0.0,
588            }
589        }
590    }
591}
592
593impl HealthStatusProvider for TimeoutHealthChecker {
594    fn snapshot(&self, pool: &str) -> HealthSnapshot {
595        let start = Instant::now();
596
597        // 通过 channel 实现超时:在独立线程中执行实际检查
598        let (tx, rx) = mpsc::channel();
599        let inner = Arc::clone(&self.inner);
600        let pool_owned = pool.to_string();
601
602        // 分离线程执行检查(线程会在发送结果后自动结束)
603        std::thread::spawn(move || {
604            let result = inner.snapshot(&pool_owned);
605            let _ = tx.send(result);
606        });
607
608        // 等待结果或超时
609        let result = match rx.recv_timeout(self.timeout) {
610            Ok(snapshot) => snapshot,
611            Err(_) => {
612                // 超时:返回 Unhealthy
613                HealthSnapshot {
614                    status: HealthStatus::Unhealthy,
615                    connection_count: 0,
616                    slow_queries: 0,
617                    message: format!(
618                        "health check timed out after {:?} for pool '{}'",
619                        self.timeout, pool
620                    ),
621                }
622            }
623        };
624
625        // 更新统计
626        let elapsed = start.elapsed();
627        if let Ok(mut stats) = self.stats.lock() {
628            stats.total_checks += 1;
629            stats.total_duration += elapsed;
630            if result.status == HealthStatus::Unhealthy && result.message.contains("timed out") {
631                stats.timeouts += 1;
632            }
633        }
634
635        result
636    }
637}
638
639#[cfg(test)]
640mod tests {
641    use super::*;
642    use std::sync::atomic::{AtomicU32, Ordering};
643
644    // ===================== 测试辅助类型 =====================
645
646    /// 记录调用次数的 mock 检查器
647    struct CountingChecker {
648        call_count: AtomicU32,
649        status: HealthStatus,
650    }
651
652    impl CountingChecker {
653        fn new(status: HealthStatus) -> Self {
654            Self {
655                call_count: AtomicU32::new(0),
656                status,
657            }
658        }
659
660        fn calls(&self) -> u32 {
661            self.call_count.load(Ordering::SeqCst)
662        }
663    }
664
665    impl DbHealthChecker for CountingChecker {
666        fn check(&self, pool: &str) -> HealthReport {
667            self.call_count.fetch_add(1, Ordering::SeqCst);
668            HealthReport::new(pool).set_status(self.status.clone())
669        }
670
671        fn check_all(&self, pools: &[&str]) -> Vec<HealthReport> {
672            pools.iter().map(|p| self.check(p)).collect()
673        }
674    }
675
676    /// 模拟延迟的 provider
677    struct SlowProvider {
678        delay: Duration,
679    }
680
681    impl HealthStatusProvider for SlowProvider {
682        fn snapshot(&self, _pool: &str) -> HealthSnapshot {
683            std::thread::sleep(self.delay);
684            HealthSnapshot::healthy()
685        }
686    }
687
688    /// 快速返回的 provider
689    struct FastProvider;
690
691    impl HealthStatusProvider for FastProvider {
692        fn snapshot(&self, _pool: &str) -> HealthSnapshot {
693            HealthSnapshot::healthy()
694        }
695    }
696
697    // ===================== HealthCheckCache 测试 =====================
698
699    #[test]
700    fn test_cache_first_call_misses() {
701        let checker = Arc::new(CountingChecker::new(HealthStatus::Healthy));
702        let cache = HealthCheckCache::new(checker.clone(), Duration::from_secs(60));
703
704        let report = cache.check("pool-a");
705        assert_eq!(report.status, HealthStatus::Healthy);
706        assert_eq!(checker.calls(), 1);
707
708        let (hits, misses, _) = cache.stats();
709        assert_eq!(hits, 0);
710        assert_eq!(misses, 1);
711    }
712
713    #[test]
714    fn test_cache_second_call_within_ttl_hits() {
715        let checker = Arc::new(CountingChecker::new(HealthStatus::Healthy));
716        let cache = HealthCheckCache::new(checker.clone(), Duration::from_secs(60));
717
718        cache.check("pool-a");
719        cache.check("pool-a");
720
721        // 第二次应命中缓存,不增加调用次数
722        assert_eq!(checker.calls(), 1);
723        let (hits, misses, _) = cache.stats();
724        assert_eq!(hits, 1);
725        assert_eq!(misses, 1);
726    }
727
728    #[test]
729    fn test_cache_expired_triggers_recheck() {
730        let checker = Arc::new(CountingChecker::new(HealthStatus::Healthy));
731        let cache = HealthCheckCache::new(checker.clone(), Duration::from_millis(50));
732
733        cache.check("pool-a");
734        std::thread::sleep(Duration::from_millis(60));
735        cache.check("pool-a");
736
737        // TTL 过期后应重新检查
738        assert_eq!(checker.calls(), 2);
739        let (hits, misses, _) = cache.stats();
740        assert_eq!(hits, 0);
741        assert_eq!(misses, 2);
742    }
743
744    #[test]
745    fn test_cache_invalidate_clears_entry() {
746        let checker = Arc::new(CountingChecker::new(HealthStatus::Healthy));
747        let cache = HealthCheckCache::new(checker.clone(), Duration::from_secs(60));
748
749        cache.check("pool-a");
750        assert!(cache.invalidate("pool-a"));
751        cache.check("pool-a");
752
753        // 失效后应重新检查
754        assert_eq!(checker.calls(), 2);
755        let (_, _, evictions) = cache.stats();
756        assert_eq!(evictions, 1);
757    }
758
759    #[test]
760    fn test_cache_invalidate_missing_returns_false() {
761        let checker = Arc::new(CountingChecker::new(HealthStatus::Healthy));
762        let cache = HealthCheckCache::new(checker.clone(), Duration::from_secs(60));
763        assert!(!cache.invalidate("never-cached"));
764    }
765
766    #[test]
767    fn test_cache_clear_empties_all() {
768        let checker = Arc::new(CountingChecker::new(HealthStatus::Healthy));
769        let cache = HealthCheckCache::new(checker.clone(), Duration::from_secs(60));
770
771        cache.check("a");
772        cache.check("b");
773        cache.clear();
774        cache.check("a");
775
776        // 清空后应重新检查
777        assert_eq!(checker.calls(), 3);
778    }
779
780    #[test]
781    fn test_cache_hit_rate() {
782        let checker = Arc::new(CountingChecker::new(HealthStatus::Healthy));
783        let cache = HealthCheckCache::new(checker.clone(), Duration::from_secs(60));
784
785        cache.check("p"); // miss
786        cache.check("p"); // hit
787        cache.check("p"); // hit
788
789        let rate = cache.hit_rate();
790        assert!((rate - 2.0 / 3.0).abs() < 0.01);
791    }
792
793    #[test]
794    fn test_cache_hit_rate_no_requests() {
795        let checker = Arc::new(CountingChecker::new(HealthStatus::Healthy));
796        let cache = HealthCheckCache::new(checker.clone(), Duration::from_secs(60));
797        assert_eq!(cache.hit_rate(), 0.0);
798    }
799
800    #[test]
801    fn test_cache_ttl_accessor() {
802        let checker = Arc::new(CountingChecker::new(HealthStatus::Healthy));
803        let cache = HealthCheckCache::new(checker, Duration::from_secs(30));
804        assert_eq!(cache.ttl(), Duration::from_secs(30));
805    }
806
807    #[test]
808    fn test_cache_check_all_uses_cache() {
809        let checker = Arc::new(CountingChecker::new(HealthStatus::Healthy));
810        let cache = HealthCheckCache::new(checker.clone(), Duration::from_secs(60));
811
812        cache.check_all(&["a", "b"]);
813        cache.check_all(&["a", "b"]);
814
815        // 第二次全部命中缓存
816        assert_eq!(checker.calls(), 2);
817    }
818
819    #[test]
820    fn test_cache_different_pools_independent() {
821        let checker = Arc::new(CountingChecker::new(HealthStatus::Healthy));
822        let cache = HealthCheckCache::new(checker.clone(), Duration::from_secs(60));
823
824        cache.check("a");
825        cache.check("b");
826        cache.check("a"); // hit
827        cache.check("b"); // hit
828
829        assert_eq!(checker.calls(), 2);
830        let (hits, misses, _) = cache.stats();
831        assert_eq!(hits, 2);
832        assert_eq!(misses, 2);
833    }
834
835    // ===================== CascadingHealthChecker 测试 =====================
836
837    #[test]
838    fn test_cascading_no_deps_returns_own_report() {
839        let checker = Arc::new(CountingChecker::new(HealthStatus::Healthy));
840        let cascading = CascadingHealthChecker::new(checker);
841
842        let result = cascading.check_with_deps("pool-a");
843        assert_eq!(result.report.status, HealthStatus::Healthy);
844        assert!(result.dependencies.is_empty());
845        assert!(result.all_healthy);
846    }
847
848    #[test]
849    fn test_cascading_with_healthy_deps() {
850        let checker = Arc::new(CountingChecker::new(HealthStatus::Healthy));
851        let cascading = CascadingHealthChecker::new(checker);
852
853        cascading.add_dependency("app", "database");
854        cascading.add_dependency("app", "cache");
855
856        let result = cascading.check_with_deps("app");
857        assert_eq!(result.report.status, HealthStatus::Healthy);
858        assert_eq!(result.dependencies.len(), 2);
859        assert!(result.all_healthy);
860    }
861
862    #[test]
863    fn test_cascading_unhealthy_dependency_propagates() {
864        struct MixedChecker;
865        impl DbHealthChecker for MixedChecker {
866            fn check(&self, pool: &str) -> HealthReport {
867                if pool == "db-down" {
868                    HealthReport::new(pool).set_status(HealthStatus::Unhealthy)
869                } else {
870                    HealthReport::new(pool).set_healthy()
871                }
872            }
873            fn check_all(&self, pools: &[&str]) -> Vec<HealthReport> {
874                pools.iter().map(|p| self.check(p)).collect()
875            }
876        }
877
878        let checker = Arc::new(MixedChecker);
879        let cascading = CascadingHealthChecker::new(checker);
880
881        cascading.add_dependency("app", "db-down");
882        let result = cascading.check_with_deps("app");
883        assert!(!result.all_healthy);
884        assert_eq!(result.dependencies.len(), 1);
885        assert_eq!(result.dependencies[0].status, HealthStatus::Unhealthy);
886    }
887
888    #[test]
889    fn test_cascading_nested_deps() {
890        let checker = Arc::new(CountingChecker::new(HealthStatus::Healthy));
891        let cascading = CascadingHealthChecker::new(checker);
892
893        cascading.add_dependency("app", "middleware");
894        cascading.add_dependency("middleware", "database");
895
896        let result = cascading.check_with_deps("app");
897        assert_eq!(result.dependencies.len(), 2);
898        assert!(result.all_healthy);
899    }
900
901    #[test]
902    fn test_cascading_circular_dependency_no_infinite_loop() {
903        let checker = Arc::new(CountingChecker::new(HealthStatus::Healthy));
904        let cascading = CascadingHealthChecker::new(checker);
905
906        // 创建循环依赖:A -> B -> A
907        cascading.add_dependency("a", "b");
908        cascading.add_dependency("b", "a");
909
910        // 不应死循环
911        let result = cascading.check_with_deps("a");
912        assert_eq!(result.dependencies.len(), 1); // 只检查 b,b 的依赖 a 已访问
913    }
914
915    #[test]
916    fn test_cascading_remove_dependency() {
917        let checker = Arc::new(CountingChecker::new(HealthStatus::Healthy));
918        let cascading = CascadingHealthChecker::new(checker);
919
920        cascading.add_dependency("app", "db");
921        cascading.add_dependency("app", "cache");
922        assert_eq!(cascading.dependencies("app").len(), 2);
923
924        assert!(cascading.remove_dependency("app", "db"));
925        assert_eq!(cascading.dependencies("app").len(), 1);
926        assert_eq!(cascading.dependencies("app")[0], "cache");
927    }
928
929    #[test]
930    fn test_cascading_remove_missing_dependency_returns_false() {
931        let checker = Arc::new(CountingChecker::new(HealthStatus::Healthy));
932        let cascading = CascadingHealthChecker::new(checker);
933        assert!(!cascading.remove_dependency("app", "never-added"));
934    }
935
936    #[test]
937    fn test_cascading_clear_dependencies() {
938        let checker = Arc::new(CountingChecker::new(HealthStatus::Healthy));
939        let cascading = CascadingHealthChecker::new(checker);
940
941        cascading.add_dependency("app", "db");
942        cascading.add_dependency("app", "cache");
943        cascading.clear_dependencies("app");
944
945        assert!(cascading.dependencies("app").is_empty());
946    }
947
948    #[test]
949    fn test_cascading_add_dependencies_batch() {
950        let checker = Arc::new(CountingChecker::new(HealthStatus::Healthy));
951        let cascading = CascadingHealthChecker::new(checker);
952
953        cascading.add_dependencies(
954            "app",
955            vec!["db".to_string(), "cache".to_string(), "queue".to_string()],
956        );
957        assert_eq!(cascading.dependencies("app").len(), 3);
958    }
959
960    #[test]
961    fn test_cascading_unknown_dependency_pool_returns_unknown() {
962        struct UnknownChecker;
963        impl DbHealthChecker for UnknownChecker {
964            fn check(&self, pool: &str) -> HealthReport {
965                HealthReport::new(pool) // status defaults to Unknown
966            }
967            fn check_all(&self, pools: &[&str]) -> Vec<HealthReport> {
968                pools.iter().map(|p| self.check(p)).collect()
969            }
970        }
971
972        let checker = Arc::new(UnknownChecker);
973        let cascading = CascadingHealthChecker::new(checker);
974        cascading.add_dependency("app", "unknown-dep");
975
976        let result = cascading.check_with_deps("app");
977        assert!(!result.all_healthy); // Unknown is not Healthy
978    }
979
980    // ===================== ProbeManager 测试 =====================
981
982    #[test]
983    fn test_probe_manager_default_is_unknown() {
984        let mgr = ProbeManager::new();
985        let result = mgr.check_liveness("svc");
986        assert_eq!(result.status, HealthStatus::Unknown);
987        assert_eq!(result.kind, ProbeKind::Liveness);
988        assert!(!result.message.is_empty());
989    }
990
991    #[test]
992    fn test_probe_manager_set_liveness_healthy() {
993        let mgr = ProbeManager::new();
994        mgr.set_liveness("svc", HealthSnapshot::healthy());
995
996        let result = mgr.check_liveness("svc");
997        assert_eq!(result.status, HealthStatus::Healthy);
998        assert_eq!(result.kind, ProbeKind::Liveness);
999    }
1000
1001    #[test]
1002    fn test_probe_manager_set_readiness_unhealthy() {
1003        let mgr = ProbeManager::new();
1004        mgr.set_readiness("svc", HealthSnapshot::unhealthy("dependency down"));
1005
1006        let result = mgr.check_readiness("svc");
1007        assert_eq!(result.status, HealthStatus::Unhealthy);
1008        assert_eq!(result.message, "dependency down");
1009        assert_eq!(result.kind, ProbeKind::Readiness);
1010    }
1011
1012    #[test]
1013    fn test_probe_manager_liveness_and_readiness_independent() {
1014        let mgr = ProbeManager::new();
1015
1016        // 进程存活但未就绪(正在启动)
1017        mgr.set_liveness("svc", HealthSnapshot::healthy());
1018        mgr.set_readiness("svc", HealthSnapshot::unhealthy("warming up"));
1019
1020        assert_eq!(mgr.check_liveness("svc").status, HealthStatus::Healthy);
1021        assert_eq!(mgr.check_readiness("svc").status, HealthStatus::Unhealthy);
1022    }
1023
1024    #[test]
1025    fn test_probe_manager_overall_liveness_all_healthy() {
1026        let mgr = ProbeManager::new();
1027        mgr.set_liveness("a", HealthSnapshot::healthy());
1028        mgr.set_liveness("b", HealthSnapshot::healthy());
1029        assert_eq!(mgr.overall_liveness(), HealthStatus::Healthy);
1030    }
1031
1032    #[test]
1033    fn test_probe_manager_overall_readiness_one_unhealthy() {
1034        let mgr = ProbeManager::new();
1035        mgr.set_readiness("a", HealthSnapshot::healthy());
1036        mgr.set_readiness("b", HealthSnapshot::unhealthy("down"));
1037        assert_eq!(mgr.overall_readiness(), HealthStatus::Unhealthy);
1038    }
1039
1040    #[test]
1041    fn test_probe_manager_overall_empty_returns_unknown() {
1042        let mgr = ProbeManager::new();
1043        assert_eq!(mgr.overall_liveness(), HealthStatus::Unknown);
1044        assert_eq!(mgr.overall_readiness(), HealthStatus::Unknown);
1045    }
1046
1047    #[test]
1048    fn test_probe_manager_overall_one_unknown_no_unhealthy() {
1049        let mgr = ProbeManager::new();
1050        mgr.set_liveness("a", HealthSnapshot::healthy());
1051        mgr.set_liveness("b", HealthSnapshot::unknown());
1052        assert_eq!(mgr.overall_liveness(), HealthStatus::Unknown);
1053    }
1054
1055    #[test]
1056    fn test_probe_manager_liveness_all_returns_all_probes() {
1057        let mgr = ProbeManager::new();
1058        mgr.set_liveness("a", HealthSnapshot::healthy());
1059        mgr.set_liveness("b", HealthSnapshot::healthy());
1060
1061        let results = mgr.liveness_all();
1062        assert_eq!(results.len(), 2);
1063        assert!(results.iter().all(|r| r.kind == ProbeKind::Liveness));
1064    }
1065
1066    #[test]
1067    fn test_probe_manager_readiness_all_returns_all_probes() {
1068        let mgr = ProbeManager::new();
1069        mgr.set_readiness("x", HealthSnapshot::healthy());
1070
1071        let results = mgr.readiness_all();
1072        assert_eq!(results.len(), 1);
1073        assert_eq!(results[0].kind, ProbeKind::Readiness);
1074    }
1075
1076    #[test]
1077    fn test_probe_result_serialization_roundtrip() {
1078        let result = ProbeResult {
1079            kind: ProbeKind::Readiness,
1080            status: HealthStatus::Unhealthy,
1081            message: "db down".to_string(),
1082            timestamp: "2024-01-01T00:00:00Z".to_string(),
1083        };
1084        let json = serde_json::to_string(&result).expect("serialize");
1085        let back: ProbeResult = serde_json::from_str(&json).expect("deserialize");
1086        assert_eq!(back.kind, ProbeKind::Readiness);
1087        assert_eq!(back.status, HealthStatus::Unhealthy);
1088        assert_eq!(back.message, "db down");
1089    }
1090
1091    #[test]
1092    fn test_probe_kind_eq() {
1093        assert_eq!(ProbeKind::Liveness, ProbeKind::Liveness);
1094        assert_ne!(ProbeKind::Liveness, ProbeKind::Readiness);
1095    }
1096
1097    // ===================== TimeoutHealthChecker 测试 =====================
1098
1099    #[test]
1100    fn test_timeout_checker_fast_provider_succeeds() {
1101        let provider = Arc::new(FastProvider);
1102        let checker = TimeoutHealthChecker::new(provider, Duration::from_secs(1));
1103
1104        let snap = checker.snapshot("pool");
1105        assert_eq!(snap.status, HealthStatus::Healthy);
1106
1107        let stats = checker.stats();
1108        assert_eq!(stats.total_checks, 1);
1109        assert_eq!(stats.timeouts, 0);
1110        assert_eq!(stats.timeout_rate, 0.0);
1111    }
1112
1113    #[test]
1114    fn test_timeout_checker_slow_provider_times_out() {
1115        let provider = Arc::new(SlowProvider {
1116            delay: Duration::from_millis(200),
1117        });
1118        let checker = TimeoutHealthChecker::new(provider, Duration::from_millis(50));
1119
1120        let snap = checker.snapshot("pool");
1121        assert_eq!(snap.status, HealthStatus::Unhealthy);
1122        assert!(snap.message.contains("timed out"));
1123
1124        let stats = checker.stats();
1125        assert_eq!(stats.total_checks, 1);
1126        assert_eq!(stats.timeouts, 1);
1127        assert!((stats.timeout_rate - 1.0).abs() < 0.01);
1128    }
1129
1130    #[test]
1131    fn test_timeout_checker_timeout_accessor() {
1132        let provider = Arc::new(FastProvider);
1133        let checker = TimeoutHealthChecker::new(provider, Duration::from_millis(500));
1134        assert_eq!(checker.timeout(), Duration::from_millis(500));
1135    }
1136
1137    #[test]
1138    fn test_timeout_checker_stats_avg_duration() {
1139        let provider = Arc::new(FastProvider);
1140        let checker = TimeoutHealthChecker::new(provider, Duration::from_secs(1));
1141
1142        checker.snapshot("a");
1143        checker.snapshot("b");
1144
1145        let stats = checker.stats();
1146        assert_eq!(stats.total_checks, 2);
1147        assert!(stats.avg_duration < Duration::from_millis(100));
1148    }
1149
1150    #[test]
1151    fn test_timeout_checker_stats_empty() {
1152        let provider = Arc::new(FastProvider);
1153        let checker = TimeoutHealthChecker::new(provider, Duration::from_secs(1));
1154
1155        let stats = checker.stats();
1156        assert_eq!(stats.total_checks, 0);
1157        assert_eq!(stats.timeouts, 0);
1158        assert_eq!(stats.timeout_rate, 0.0);
1159        assert_eq!(stats.avg_duration, Duration::ZERO);
1160    }
1161
1162    #[test]
1163    fn test_timeout_checker_implements_send_sync() {
1164        fn assert_send_sync<T: Send + Sync>() {}
1165        assert_send_sync::<TimeoutHealthChecker>();
1166        assert_send_sync::<HealthCheckCache>();
1167        assert_send_sync::<CascadingHealthChecker>();
1168        assert_send_sync::<ProbeManager>();
1169    }
1170}