1- use std:: time:: Instant ;
1+ use std:: {
2+ sync:: {
3+ Arc ,
4+ atomic:: { AtomicU64 , Ordering } ,
5+ } ,
6+ time:: Instant ,
7+ } ;
28
39pub trait MonotonicClock : Send + Sync {
410 fn now_ns ( & self ) -> u64 ;
511}
612
13+ #[ derive( Debug ) ]
14+ struct SessionClockInner {
15+ epoch : Instant ,
16+ last_ns : AtomicU64 ,
17+ }
18+
719#[ derive( Debug , Clone ) ]
820pub struct SessionClock {
9- epoch : Instant ,
21+ inner : Arc < SessionClockInner > ,
1022}
1123
1224impl SessionClock {
1325 #[ must_use]
1426 pub fn start ( ) -> Self {
1527 Self {
16- epoch : Instant :: now ( ) ,
28+ inner : Arc :: new ( SessionClockInner {
29+ epoch : Instant :: now ( ) ,
30+ last_ns : AtomicU64 :: new ( 0 ) ,
31+ } ) ,
32+ }
33+ }
34+
35+ #[ must_use]
36+ pub fn elapsed_ns ( & self ) -> u64 {
37+ u64:: try_from ( self . inner . epoch . elapsed ( ) . as_nanos ( ) ) . unwrap_or ( u64:: MAX )
38+ }
39+
40+ fn publish_monotonic ( & self , candidate : u64 ) -> u64 {
41+ let mut last = self . inner . last_ns . load ( Ordering :: Relaxed ) ;
42+ loop {
43+ let next = candidate. max ( last) ;
44+ match self . inner . last_ns . compare_exchange_weak (
45+ last,
46+ next,
47+ Ordering :: Relaxed ,
48+ Ordering :: Relaxed ,
49+ ) {
50+ Ok ( _) => return next,
51+ Err ( observed) => last = observed,
52+ }
1753 }
1854 }
1955}
@@ -26,6 +62,41 @@ impl Default for SessionClock {
2662
2763impl MonotonicClock for SessionClock {
2864 fn now_ns ( & self ) -> u64 {
29- u64:: try_from ( self . epoch . elapsed ( ) . as_nanos ( ) ) . unwrap_or ( u64:: MAX )
65+ self . publish_monotonic ( self . elapsed_ns ( ) )
66+ }
67+ }
68+
69+ #[ cfg( test) ]
70+ mod tests {
71+ use std:: { sync:: Arc , thread} ;
72+
73+ use super :: { MonotonicClock , SessionClock } ;
74+
75+ #[ test]
76+ fn clones_share_one_epoch ( ) {
77+ let clock = SessionClock :: start ( ) ;
78+ let clone = clock. clone ( ) ;
79+ assert ! ( clone. now_ns( ) >= clock. now_ns( ) ) ;
80+ }
81+
82+ #[ test]
83+ fn concurrent_reads_never_move_backwards ( ) {
84+ let clock = Arc :: new ( SessionClock :: start ( ) ) ;
85+ let handles = ( 0 ..4 )
86+ . map ( |_| {
87+ let clock = clock. clone ( ) ;
88+ thread:: spawn ( move || {
89+ let mut previous = 0 ;
90+ for _ in 0 ..1_000 {
91+ let current = clock. now_ns ( ) ;
92+ assert ! ( current >= previous) ;
93+ previous = current;
94+ }
95+ } )
96+ } )
97+ . collect :: < Vec < _ > > ( ) ;
98+ for handle in handles {
99+ assert ! ( handle. join( ) . is_ok( ) ) ;
100+ }
30101 }
31102}
0 commit comments