hlc/
lib.rs

1//! An implementation of the
2//! [Hybrid Logical Clock](http://www.cse.buffalo.edu/tech-reports/2014-04.pdf)
3//! for Rust.
4
5extern crate time;
6
7use std::fmt::{Formatter, Display, Error};
8use std::sync::Mutex;
9
10/// The `HLTimespec` type stores a hybrid logical timestamp (also called
11/// timespec for symmetry with time::Timespec).
12///
13/// Such a timestamp is comprised of an "ordinary" wall time and
14/// a logical component. Timestamps are compared by wall time first,
15/// logical second.
16///
17/// # Examples
18///
19/// ```
20/// use hlc::HLTimespec;
21/// let early = HLTimespec::new(1, 0, 0);
22/// let middle = HLTimespec::new(1, 1, 0);
23/// let late = HLTimespec::new(1, 1, 1);
24/// assert!(early < middle && middle < late);
25/// ```
26#[derive(Debug,Clone,Copy,Eq,PartialEq,PartialOrd,Ord)]
27pub struct HLTimespec {
28    wall: time::Timespec,
29    logical: u16,
30}
31
32impl HLTimespec {
33    /// Creates a new hybrid logical timestamp with the given seconds,
34    /// nanoseconds, and logical ticks.
35    ///
36    /// # Examples
37    ///
38    /// ```
39    /// use hlc::HLTimespec;
40    /// let ts = HLTimespec::new(1, 2, 3);
41    /// assert_eq!(format!("{}", ts), "1.2+3");
42    /// ```
43    pub fn new(s: i64, ns: i32, l: u16) -> HLTimespec {
44        HLTimespec { wall: time::Timespec { sec: s, nsec: ns }, logical: l }
45    }
46}
47
48impl Display for HLTimespec {
49    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
50        f.write_str(&format!("{}.{}+{}", self.wall.sec, self.wall.nsec, self.logical))
51    }
52}
53
54/// `State` is a hybrid logical clock.
55///
56/// # Examples
57///
58/// ```
59/// use hlc::{HLTimespec, State};
60/// let mut s = State::new();
61/// println!("{}", s.get_time()); // attach to outgoing event
62/// let ext_event_ts = HLTimespec::new(12345, 67, 89); // external event's timestamp
63/// let ext_event_recv_ts = s.update(ext_event_ts);
64/// ```
65///
66/// If access to the clock isn't serializable, a convenience method returns
67/// a `State` wrapped in a `Mutex`:
68///
69/// ```
70/// use hlc::State;
71/// let mut mu = State::new_sendable();
72/// {
73///     let mut s = mu.lock().unwrap();
74///     s.get_time();
75/// }
76/// ```
77pub struct State<F> {
78    s: HLTimespec,
79    now: F,
80}
81
82impl State<()> {
83    // Creates a standard hybrid logical clock, using `time::get_time` as
84    // supplier of the physical clock's wall time.
85    pub fn new() -> State<fn() -> time::Timespec> {
86        State::new_with(time::get_time)
87    }
88
89    // Returns the result of `State::new()`, wrapped in a `Mutex`.
90    pub fn new_sendable() -> Mutex<State<fn() -> time::Timespec>> {
91        Mutex::new(State::new())
92    }
93}
94
95impl<F: FnMut() -> time::Timespec> State<F> {
96    /// Creates a hybrid logical clock with the supplied wall time. This is
97    /// useful for tests or settings in which an alternative clock is used.
98    ///
99    /// # Examples
100    ///
101    /// ```
102    /// # extern crate hlc;
103    /// # extern crate time;
104    /// # fn main() {
105    /// use hlc::{HLTimespec, State};
106    /// let mut times = vec![time::Timespec { sec: 42, nsec: 9919 }];
107    /// let mut s = State::new_with(move || times.pop().unwrap());
108    /// let mut ts = s.get_time();
109    /// assert_eq!(format!("{}", ts), "42.9919+0");
110    /// # }
111    /// ```
112    pub fn new_with(now: F) -> State<F> {
113        State {
114            s: HLTimespec { wall: time::Timespec { sec: 0, nsec: 0 }, logical: 0 },
115            now: now,
116        }
117    }
118
119    /// Generates a timestamp from the clock.
120    pub fn get_time(&mut self) -> HLTimespec {
121        let s = &mut self.s;
122        let wall = (self.now)();
123        if s.wall < wall {
124            s.wall = wall;
125            s.logical = 0;
126        } else {
127            s.logical += 1;
128        }
129        s.clone()
130    }
131
132    /// Assigns a timestamp to an event which happened at the given timestamp
133    /// on a remote system.
134    pub fn update(&mut self, event: HLTimespec) -> HLTimespec {
135        let (wall, s) = ((self.now)(), &mut self.s);
136
137        if wall > event.wall && wall > s.wall {
138            s.wall = wall;
139            s.logical = 0
140        } else if event.wall > s.wall {
141            s.wall = event.wall;
142            s.logical = event.logical+1;
143        } else if s.wall > event.wall {
144            s.logical += 1;
145        } else {
146            if event.logical > s.logical {
147                s.logical = event.logical;
148            }
149            s.logical += 1;
150        }
151        s.clone()
152    }
153}
154
155#[cfg(test)]
156mod tests {
157    extern crate time;
158    use {HLTimespec, State};
159
160    fn ts(s: i64, ns: i32) -> time::Timespec {
161        time::Timespec { sec: s, nsec: ns }
162    }
163
164    fn hlts(s: i64, ns: i32, l: u16) -> HLTimespec {
165        HLTimespec::new(s, ns, l)
166    }
167
168    #[test]
169    fn it_works() {
170        let zero = hlts(0, 0, 0);
171        let ops = vec![
172            // Test cases in the form (wall, event_ts, outcome).
173            // Specifying event_ts as zero corresponds to calling `get_time`,
174            // otherwise `update`.
175            (ts(1,0), zero, hlts(1,0,0)),
176            (ts(1,0), zero, hlts(1,0,1)), // clock didn't move
177            (ts(0,9), zero, hlts(1,0,2)), // clock moved back
178            (ts(2,0), zero, hlts(2,0,0)), // finally ahead again
179            (ts(3,0), hlts(1,2,3), hlts(3,0,0)), // event happens, but wall ahead
180            (ts(3,0), hlts(1,2,3), hlts(3,0,1)), // event happens, wall ahead but unchanged
181            (ts(3,0), hlts(3,0,1), hlts(3,0,2)), // event happens at wall, which is still unchanged
182            (ts(3,0), hlts(3,0,99), hlts(3,0,100)), // event with larger logical, wall unchanged
183            (ts(3,5), hlts(4,4,100), hlts(4,4,101)), // event with larger wall, our wall behind
184            (ts(5,0), hlts(4,5,0), hlts(5,0,0)), // event behind wall, but ahead of previous state
185            (ts(4,9), hlts(5,0,99), hlts(5,0,100)),
186            (ts(0,0), hlts(5,0,50), hlts(5,0,101)), // event at state, lower logical than state
187        ];
188
189        // Prepare fake clock and create State.
190        let mut times = ops.iter().rev().map(|op| op.0).collect::<Vec<time::Timespec>>();
191        let mut s = State::new_with(move || times.pop().unwrap());
192
193        for op in &ops {
194            let t = if op.1 == zero {
195                s.get_time()
196            } else {
197                s.update(op.1.clone())
198            };
199            assert_eq!(t, op.2);
200        }
201    }
202}