ntp-timer 0.1.0

A lightweight NTP time synchronization library for Rust applications. Provides in-process cached global time with background sync and clock-jump detection.
Documentation
use crate::config::Config;
use crate::error::TimerError;
use crate::ntp;
use crate::timer::GlobalTimer;
use lazy_static::lazy_static;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use tracing::{debug, info, warn};

lazy_static! {
    static ref GLOBAL_TIMER: Arc<Mutex<Option<GlobalTimer>>> = Arc::new(Mutex::new(None));
    static ref CONFIG: Arc<Mutex<Option<Arc<Config>>>> = Arc::new(Mutex::new(None));
}

/// Global timer manager providing unified interface for time synchronization and access
pub struct TimerManager;

impl TimerManager {
    /// Initializes the global timer (lazy initialization).
    ///
    /// On first call, fetches NTP timestamp and starts background sync task.
    /// Subsequent calls are no-op if already initialized.
    pub async fn init_global_timer(config: Arc<Config>) -> Result<(), TimerError> {
        config.validate().map_err(TimerError::ConfigError)?;

        let mut config_guard = CONFIG
            .lock()
            .map_err(|e| TimerError::LockError(e.to_string()))?;

        if config_guard.is_none() {
            *config_guard = Some(Arc::clone(&config));
        }
        drop(config_guard);

        let mut timer_guard = GLOBAL_TIMER
            .lock()
            .map_err(|e| TimerError::LockError(e.to_string()))?;

        if timer_guard.is_none() {
            let base_timestamp = ntp::get_ntp_timestamp(&config)?;
            let timer = GlobalTimer::new(base_timestamp);
            *timer_guard = Some(timer);
            drop(timer_guard);

            TimerManager::spawn_background_sync();
        }

        Ok(())
    }

    /// Gets the current timestamp via fast local path (<0.1ms).
    ///
    /// # Errors
    /// Returns `TimerError::NotInitialized` if `init_global_timer` was not called.
    pub fn get_timestamp() -> Result<i64, TimerError> {
        let timer_guard = GLOBAL_TIMER
            .lock()
            .map_err(|e| TimerError::LockError(e.to_string()))?;

        match timer_guard.as_ref() {
            Some(timer) => Ok(timer.get_current_timestamp()),
            None => Err(TimerError::NotInitialized),
        }
    }

    /// 手动同步 NTP
    pub async fn manual_sync() -> Result<i64, TimerError> {
        let config_opt = {
            let config_guard = CONFIG
                .lock()
                .map_err(|e| TimerError::LockError(e.to_string()))?;
            config_guard.as_ref().map(Arc::clone)
        };

        let config = config_opt.ok_or(TimerError::NotInitialized)?;

        let new_ntp_timestamp = ntp::get_ntp_timestamp(&config)?;

        let mut timer_guard = GLOBAL_TIMER
            .lock()
            .map_err(|e| TimerError::LockError(e.to_string()))?;

        match timer_guard.as_mut() {
            Some(timer) => {
                if timer.detect_clock_jump(new_ntp_timestamp, config.clock_jump_threshold_secs) {
                    timer.update_timestamp(new_ntp_timestamp, true);
                } else {
                    timer.update_timestamp(new_ntp_timestamp, false);
                }

                let timestamp = timer.get_current_timestamp();
                info!(timestamp, "NTP 同步完成");
                Ok(timestamp)
            }
            None => Err(TimerError::NotInitialized),
        }
    }

    /// 启动后台同步任务
    fn spawn_background_sync() {
        tokio::spawn(async {
            loop {
                let sync_interval = {
                    let config_guard = CONFIG.lock().ok();
                    config_guard.and_then(|g| g.as_ref().map(|c| c.sync_interval_secs))
                };

                if let Some(interval) = sync_interval {
                    tokio::time::sleep(Duration::from_secs(interval)).await;

                    match TimerManager::manual_sync().await {
                        Ok(_) => {
                            debug!("后台同步完成");
                        }
                        Err(e) => {
                            warn!("后台同步失败,继续使用本地计时器: {}", e);
                        }
                    }
                } else {
                    tokio::time::sleep(Duration::from_secs(1)).await;
                }
            }
        });
    }

    /// 重置计时器(用于测试)
    #[cfg(test)]
    pub fn reset() {
        if let Ok(mut timer_guard) = GLOBAL_TIMER.lock() {
            *timer_guard = None;
        }
        if let Ok(mut config_guard) = CONFIG.lock() {
            *config_guard = None;
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test_manual_sync_without_init() {
        TimerManager::reset();
        let result = TimerManager::manual_sync().await;
        assert!(result.is_err());
    }
}