Atomic Time Instant in Rust
Jialun Cai
/
2023-05-06
/
Views
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))
}
}