Monorepo for Tangled tangled.org
5

Configure Feed

Select the types of activity you want to include in your feed.

1use std::future::Future; 2use std::pin::Pin; 3use std::time::{Duration, SystemTime, UNIX_EPOCH}; 4 5use tokio::time::Instant; 6 7#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] 8pub struct UnixMicros(u64); 9 10impl UnixMicros { 11 pub const fn new(value: u64) -> Self { 12 Self(value) 13 } 14 15 pub const fn raw(self) -> u64 { 16 self.0 17 } 18} 19 20pub type SleepFuture = Pin<Box<dyn Future<Output = ()> + Send + 'static>>; 21 22pub trait Clock: Send + Sync + 'static { 23 fn now_unix_micros(&self) -> UnixMicros; 24 fn now_instant(&self) -> Instant; 25 fn sleep(&self, duration: Duration) -> SleepFuture; 26 fn sleep_until(&self, deadline: Instant) -> SleepFuture; 27} 28 29#[derive(Clone, Copy, Debug)] 30pub struct SystemClock { 31 base_unix: UnixMicros, 32 base_instant: Instant, 33} 34 35impl SystemClock { 36 pub fn new() -> Self { 37 let raw = SystemTime::now() 38 .duration_since(UNIX_EPOCH) 39 .expect("system clock before unix epoch") 40 .as_micros(); 41 Self { 42 base_unix: UnixMicros::new(u64::try_from(raw).unwrap_or(u64::MAX)), 43 base_instant: Instant::now(), 44 } 45 } 46} 47 48impl Default for SystemClock { 49 fn default() -> Self { 50 Self::new() 51 } 52} 53 54impl Clock for SystemClock { 55 fn now_unix_micros(&self) -> UnixMicros { 56 let elapsed = self 57 .now_instant() 58 .saturating_duration_since(self.base_instant); 59 let micros = u64::try_from(elapsed.as_micros()).unwrap_or(u64::MAX); 60 UnixMicros::new(self.base_unix.raw().saturating_add(micros)) 61 } 62 63 fn now_instant(&self) -> Instant { 64 Instant::now() 65 } 66 67 fn sleep(&self, duration: Duration) -> SleepFuture { 68 Box::pin(tokio::time::sleep(duration)) 69 } 70 71 fn sleep_until(&self, deadline: Instant) -> SleepFuture { 72 Box::pin(tokio::time::sleep_until(deadline)) 73 } 74} 75 76#[derive(Clone, Debug)] 77pub struct SimClock { 78 base_unix: UnixMicros, 79 base_instant: Instant, 80} 81 82impl SimClock { 83 pub fn at(base_unix: UnixMicros) -> Self { 84 Self { 85 base_unix, 86 base_instant: Instant::now(), 87 } 88 } 89} 90 91impl Clock for SimClock { 92 fn now_unix_micros(&self) -> UnixMicros { 93 let elapsed = self 94 .now_instant() 95 .saturating_duration_since(self.base_instant); 96 let micros = u64::try_from(elapsed.as_micros()).unwrap_or(u64::MAX); 97 UnixMicros::new(self.base_unix.raw().saturating_add(micros)) 98 } 99 100 fn now_instant(&self) -> Instant { 101 Instant::now() 102 } 103 104 fn sleep(&self, duration: Duration) -> SleepFuture { 105 Box::pin(tokio::time::sleep(duration)) 106 } 107 108 fn sleep_until(&self, deadline: Instant) -> SleepFuture { 109 Box::pin(tokio::time::sleep_until(deadline)) 110 } 111} 112 113#[cfg(test)] 114mod tests { 115 use super::*; 116 use std::sync::Arc; 117 use std::sync::atomic::{AtomicU64, Ordering}; 118 119 struct ManualClock { 120 base: Instant, 121 unix_micros: AtomicU64, 122 } 123 124 impl ManualClock { 125 fn new(unix_micros: u64) -> Self { 126 Self { 127 base: Instant::now(), 128 unix_micros: AtomicU64::new(unix_micros), 129 } 130 } 131 132 fn advance(&self, micros: u64) { 133 self.unix_micros.fetch_add(micros, Ordering::SeqCst); 134 } 135 } 136 137 impl Clock for ManualClock { 138 fn now_unix_micros(&self) -> UnixMicros { 139 UnixMicros::new(self.unix_micros.load(Ordering::SeqCst)) 140 } 141 142 fn now_instant(&self) -> Instant { 143 self.base + Duration::from_micros(self.unix_micros.load(Ordering::SeqCst)) 144 } 145 146 fn sleep(&self, _: Duration) -> SleepFuture { 147 Box::pin(std::future::ready(())) 148 } 149 150 fn sleep_until(&self, _: Instant) -> SleepFuture { 151 Box::pin(std::future::ready(())) 152 } 153 } 154 155 #[tokio::test] 156 async fn external_clock_works_behind_dyn_arc() { 157 let clock: Arc<dyn Clock> = Arc::new(ManualClock::new(42)); 158 assert_eq!(clock.now_unix_micros().raw(), 42); 159 let before = clock.now_instant(); 160 clock.sleep(Duration::from_secs(60)).await; 161 assert_eq!( 162 clock.now_instant(), 163 before, 164 "manual clock's sleep must not advance virtual time on its own", 165 ); 166 } 167 168 #[tokio::test] 169 async fn manual_clock_advance_moves_both_unix_and_instant() { 170 let clock = Arc::new(ManualClock::new(1_000)); 171 let dyn_clock: Arc<dyn Clock> = clock.clone(); 172 let unix_before = dyn_clock.now_unix_micros().raw(); 173 let instant_before = dyn_clock.now_instant(); 174 clock.advance(500); 175 assert_eq!(dyn_clock.now_unix_micros().raw(), unix_before + 500); 176 assert_eq!( 177 dyn_clock.now_instant(), 178 instant_before + Duration::from_micros(500), 179 ); 180 } 181 182 #[tokio::test(start_paused = true)] 183 async fn sim_clock_anchors_unix_at_explicit_base_under_paused_time() { 184 let base = UnixMicros::new(1_700_000_000_000_000); 185 let clock = SimClock::at(base); 186 assert_eq!( 187 clock.now_unix_micros(), 188 base, 189 "sim clock unix axis must reflect the explicit base, not wall clock", 190 ); 191 let bump = Duration::from_secs(10); 192 tokio::time::advance(bump).await; 193 assert_eq!( 194 clock.now_unix_micros().raw(), 195 base.raw() + u64::try_from(bump.as_micros()).unwrap(), 196 "sim clock unix axis must follow tokio virtual advance", 197 ); 198 } 199 200 #[tokio::test(start_paused = true)] 201 async fn sim_clock_two_constructions_with_same_base_align() { 202 let base = UnixMicros::new(2_000_000_000_000_000); 203 let a = SimClock::at(base); 204 let b = SimClock::at(base); 205 assert_eq!(a.now_unix_micros(), b.now_unix_micros()); 206 tokio::time::advance(Duration::from_secs(5)).await; 207 assert_eq!( 208 a.now_unix_micros(), 209 b.now_unix_micros(), 210 "two sim clocks anchored at the same base must agree on virtual time", 211 ); 212 } 213 214 #[tokio::test(start_paused = true)] 215 async fn system_clock_axes_advance_together_under_paused_time() { 216 let clock = SystemClock::new(); 217 let unix_before = clock.now_unix_micros().raw(); 218 let instant_before = clock.now_instant(); 219 let bump = Duration::from_secs(60); 220 tokio::time::advance(bump).await; 221 let unix_after = clock.now_unix_micros().raw(); 222 let instant_after = clock.now_instant(); 223 assert_eq!( 224 instant_after.saturating_duration_since(instant_before), 225 bump, 226 "instant axis must reflect virtual advance", 227 ); 228 assert_eq!( 229 unix_after - unix_before, 230 u64::try_from(bump.as_micros()).unwrap(), 231 "unix axis must follow the same virtual advance, not wall clock", 232 ); 233 } 234}