Atomic Time Instant in Rust


I’ve been building a WireGuard library in Rust called WireTun and needed a thread-safe timestamp to time out connections and track metrics. At first, I thought about using a Mutex or RwLock as a guard for the std::time::Instant variable that isn’t thread-safe. All we gotta do is lock the mutex before accessing this variable.

use std::sync::{Arc, Mutex};
use std::time::Instant;

pub struct AtomicInstant {
    inner: Arc<Mutex<Instant>>,
}

impl AtomicInstant {
    pub fn now() -> Self {
        let inner = Arc::new(Mutex::new(Instant::now()));
        Self { inner }
    }

    pub fn set_now(&self) {
        let mut inner = self.inner.lock().unwrap();
        *inner = Instant::now();
    }

    pub fn to_std(&self) -> Instant {
        let inner = self.inner.lock().unwrap();
        inner.clone()
    }
}

Lots of operations rely on this AtomicInstant, and the lock frequency is high. I’ve realized we don’t actually need a super precise and consistent timestamp – it’s cool if we get a slightly old timestamp. It hit me: I could probably replace the mutex implementation with an atomic one. That way, there wouldn’t be any lock operations.

use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{Duration, Instant};

pub struct AtomicInstant {
    epoch: Instant,
    d: AtomicU64,
}

impl AtomicInstant {
    pub fn now() -> Self {
        let epoch = Instant::now();
        let d = AtomicU64::new(0);
        Self { epoch, d }
    }

    pub fn set_now(&self) {
        let elapsed = self.epoch.elapsed();
        self.d.store(elapsed.as_millis() as _, Ordering::Relaxed);
    }

    pub fn to_std(&self) -> Instant {
        self.epoch + Duration::from_millis(self.d.load(Ordering::Relaxed))
    }
}