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}