Skip to main content

wanning_core/
clock.rs

1//! 可注入时钟(Clock)。
2//!
3//! 闸的过期/生效判定依赖「现在几点」。**绝不 `sleep` 测试**——测试用 [`MockClock`]
4//! 推时间,生产用 [`SystemClock`]。W-06 在此之上落过期语义与边界(恰在 `valid_until`
5//! 按过期处理),trait 本体因 `Gate::decide` 需要取当前时间而提前到 W-03 落地。
6//!
7//! 线程约定:`SharedClock = Arc<dyn Clock + Send + Sync>`,闸整体可跨线程
8//! (P1 MCP server 大概率要多线程,这里不留 Rc 债)。
9
10use std::sync::atomic::{AtomicU64, Ordering};
11use std::sync::Arc;
12use std::time::{SystemTime, UNIX_EPOCH};
13
14/// 时钟抽象。返回 Unix 秒。(`Debug` 约束让持有闸/账本状态的结构体可以直接 derive Debug。)
15pub trait Clock: Send + Sync + std::fmt::Debug {
16    fn now(&self) -> u64;
17}
18
19/// 闸内共享的时钟句柄。
20pub type SharedClock = Arc<dyn Clock + Send + Sync>;
21
22/// 生产时钟:系统时间。
23#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
24pub struct SystemClock;
25
26impl Clock for SystemClock {
27    fn now(&self) -> u64 {
28        // 系统时间早于 Unix 纪元(时钟被回拨/损坏)时返回 0:
29        // 0 会让一切委托被判为「尚未生效」→ fail-closed,闸宁可全拒也不误放。
30        SystemTime::now()
31            .duration_since(UNIX_EPOCH)
32            .map(|d| d.as_secs())
33            .unwrap_or(0)
34    }
35}
36
37/// 测试时钟:可任意推时间,无 sleep、无真实等待。
38#[derive(Clone, Debug)]
39pub struct MockClock {
40    now: Arc<AtomicU64>,
41}
42
43impl MockClock {
44    pub fn new(now: u64) -> Self {
45        Self {
46            now: Arc::new(AtomicU64::new(now)),
47        }
48    }
49
50    /// 直接设定当前时刻。
51    pub fn set_now(&self, now: u64) {
52        self.now.store(now, Ordering::Relaxed);
53    }
54
55    /// 前进若干秒(测试里模拟时间流逝)。
56    pub fn advance(&self, secs: u64) {
57        self.now.fetch_add(secs, Ordering::Relaxed);
58    }
59
60    /// 读当前时刻(不经 trait,便于断言)。
61    pub fn peek(&self) -> u64 {
62        self.now.load(Ordering::Relaxed)
63    }
64}
65
66impl Clock for MockClock {
67    fn now(&self) -> u64 {
68        self.peek()
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75
76    #[test]
77    fn mock_clock_set_and_advance() {
78        let c = MockClock::new(1000);
79        assert_eq!(c.peek(), 1000);
80        c.advance(50);
81        assert_eq!(c.peek(), 1050);
82        c.set_now(2000);
83        assert_eq!(c.peek(), 2000);
84        // trait 视角与直读一致
85        let shared: SharedClock = Arc::new(c.clone());
86        assert_eq!(shared.now(), 2000);
87    }
88
89    #[test]
90    fn mock_clock_clone_shares_state() {
91        let c = MockClock::new(1);
92        let d = c.clone();
93        c.set_now(99);
94        assert_eq!(d.peek(), 99);
95    }
96
97    #[test]
98    fn system_clock_returns_unix_seconds() {
99        let now = SystemClock.now();
100        // 2026-09-02 ≈ 1.787e9;只要落在合理区间,说明读到的是 Unix 秒。
101        assert!(now > 1_700_000_000, "SystemClock 应返回 Unix 秒,得到 {now}");
102    }
103}