todc_utils/
linearizability.rs

1//! Checking [linearizability](https://en.wikipedia.org/wiki/Linearizability) of a
2//! history of operations applied to a shared object.
3//!
4//! For more information, see the documentation of the [`WGLChecker`] and [`History`] structs.
5use std::collections::HashSet;
6use std::marker::PhantomData;
7
8use crate::linearizability::history::{Entry, History};
9use crate::specifications::Specification;
10
11pub mod history;
12
13/// A linearizability checker.
14///
15/// An implementation of the algorithm originally defined by Jeannette Wing and Chun Gong
16/// [\[WG93\]](https://www.cs.cmu.edu/~wing/publications/WingGong93.pdf), and
17/// extended by Gavin Lowe [\[L17\]](http://www.cs.ox.ac.uk/people/gavin.lowe/LinearizabiltyTesting/).
18/// This particular implementation is based on the description given by Alex Horn
19/// and Daniel Kroenig [\[HK15\]](https://arxiv.org/abs/1504.00204).
20///
21/// Given a history of operations, the algorithm works by linearizing each operation
22/// as soon as possible. When an operation cannot be linearized, it backtracks and
23/// proceeds with the next operation. Memoization occurs by caching each partial
24/// linearization, and preventing the algorithm from continuing its search when it
25/// is already known that the state of the object and remaining operations have no
26/// valid linearization.
27///
28///
29/// # Examples
30///
31/// Consider the following [`Specification`] of a register containing `u32` values.
32///
33/// ```
34/// use todc_utils::specifications::Specification;
35///
36/// #[derive(Copy, Clone, Debug)]
37/// enum RegisterOp {
38///     Read(Option<u32>),
39///     Write(u32),
40/// }
41///
42/// use RegisterOp::{Read, Write};
43///
44/// struct RegisterSpec;
45///
46/// impl Specification for RegisterSpec {
47///     type State = u32;
48///     type Operation = RegisterOp;
49///     
50///     fn init() -> Self::State {
51///         0
52///     }
53///
54///     fn apply(operation: &Self::Operation, state: &Self::State) -> (bool, Self::State) {
55///         match operation {
56///             // A read is valid if the value returned is equal to the
57///             // current state. Reads always leave the state unchanged.
58///             Read(value) => match value {
59///                 Some(value) => (value == state, *state),
60///                 None => (false, *state)
61///             },
62///             // Writes are always valid, and update the state to be
63///             // equal to the value being written.
64///             Write(value) => (true, *value),
65///         }
66///     }
67/// }
68/// ```    
69///
70/// Using the [`Action::Call`](history::Action::Call) and
71/// [`Action::Response`](history::Action::Response) types, we can model read
72/// and write operations as follows:
73/// * The call of a read operation is modeled by `Call(Read(None))` and a
74///   response containing the value `x` is modeled by `Response(Read(Some(x)))`.
75///   We are required to use an [`Option`] because the value being read cannot
76///   be known until the register responds.
77/// * Similarily, the call of a write operation with the value `y` is modeled
78///   by `Call(Write(y))` and the response is modeled by `Response(Write(y))`.
79///
80/// Next, we can define a linearizability for this specification, and check some
81/// histories.
82/// ```
83/// # use todc_utils::specifications::Specification;
84/// # #[derive(Copy, Clone, Debug)]
85/// # enum RegisterOp {
86/// #     Read(Option<u32>),
87/// #     Write(u32),
88/// # }
89/// # use RegisterOp::{Read, Write};
90/// # struct RegisterSpec;
91/// # impl Specification for RegisterSpec {
92/// #     type State = u32;
93/// #     type Operation = RegisterOp;
94/// #     fn init() -> Self::State {
95/// #         0
96/// #     }
97/// #     fn apply(operation: &Self::Operation, state: &Self::State) -> (bool, Self::State) {
98/// #         match operation {
99/// #             // A read is valid if the value returned is equal to the
100/// #             // current state. Reads always leave the state unchanged.
101/// #             Read(value) => match value {
102/// #                 Some(value) => (value == state, *state),
103/// #                 None => (false, *state)
104/// #             },
105/// #             // Writes are always valid, and update the state to be
106/// #             // equal to the value being written.
107/// #             Write(value) => (true, *value),
108/// #         }
109/// #     }
110/// # }
111/// use todc_utils::linearizability::{WGLChecker, history::{History, Action::{Call, Response}}};
112///
113/// type RegisterChecker = WGLChecker<RegisterSpec>;
114///
115/// // A history of sequantial operations is always linearizable.
116/// // PO |------|          Write(0)
117/// // P1          |------| Read(Some(0))
118/// let history = History::from_actions(vec![
119///     (0, Call(Write(0))),
120///     (0, Response(Write(0))),
121///     (1, Call(Read(None))),
122///     (1, Response(Read(Some(0)))),
123/// ]);
124/// assert!(RegisterChecker::is_linearizable(history));
125///
126/// // Concurrent operations might not be linearized
127/// // in the order in which they are called.
128/// // PO |--------------|  Write(0)
129/// // P1  |--------------| Write(1)
130/// // P2    |---|          Read(Some(1))
131/// // P3           |---|   Read(Some(0))
132/// let history = History::from_actions(vec![
133///     (0, Call(Write(0))),
134///     (1, Call(Write(1))),
135///     (2, Call(Read(None))),
136///     (2, Response(Read(Some(1)))),
137///     (3, Call(Read(None))),
138///     (3, Response(Read(Some(0)))),
139///     (0, Response(Write(0))),
140///     (1, Response(Write(1))),
141/// ]);
142/// assert!(RegisterChecker::is_linearizable(history));
143///
144/// // A sequentially consistent history is **not**
145/// // necessarily linearizable.
146/// // PO |---|             Write(0)
147/// // P1 |---|             Write(1)
148/// // P2       |---|       Read(Some(1))
149/// // P3             |---| Read(Some(0))
150/// let history = History::from_actions(vec![
151///     (0, Call(Write(0))),
152///     (1, Call(Write(1))),
153///     (0, Response(Write(0))),
154///     (1, Response(Write(1))),
155///     (2, Call(Read(None))),
156///     (2, Response(Read(Some(1)))),
157///     (3, Call(Read(None))),
158///     (3, Response(Read(Some(0)))),
159/// ]);
160/// assert!(!RegisterChecker::is_linearizable(history));
161/// ```    
162///   
163/// For examples of using [`WGLChecker`] to check the linearizability of more
164/// complex histories, see
165/// [`todc-mem/tests/snapshot/common.rs`](https://github.com/kaymanb/todc/blob/main/todc-mem/tests/snapshot/common.rs)
166/// or
167/// [`todc-utils/tests/linearizability/etcd.rs`](https://github.com/kaymanb/todc/blob/main/todc-utils/tests/linearizability/etcd.rs).
168///
169/// # Implementations in Other Languages
170///
171/// For an implementation in C++, see [`linearizability-checker`](https://github.com/ahorn/linearizability-checker).
172/// For an implementation in Go, see [`porcupine`](https://github.com/anishathalye/porcupine).
173pub struct WGLChecker<S: Specification> {
174    data_type: PhantomData<S>,
175}
176
177type OperationEntry<S> = Entry<<S as Specification>::Operation>;
178type OperationCall<S> = (
179    (OperationEntry<S>, OperationEntry<S>),
180    <S as Specification>::State,
181);
182
183impl<S: Specification> WGLChecker<S> {
184    /// Returns whether the history of operations is linearizable with respect to the specification.
185    pub fn is_linearizable(mut history: History<S::Operation>) -> bool {
186        let mut state = S::init();
187        let mut linearized = vec![false; history.len()];
188        let mut calls: Vec<OperationCall<S>> = Vec::new();
189        let mut cache: HashSet<(Vec<bool>, S::State)> = HashSet::new();
190        let mut curr = 0;
191        loop {
192            if history.is_empty() {
193                return true;
194            }
195            match &history[curr] {
196                Entry::Call(call) => match &history[history.index_of_id(call.response)] {
197                    Entry::Call(_) => panic!("Response cannot be a call entry"),
198                    Entry::Response(response) => {
199                        let (is_valid, new_state) = S::apply(&response.operation, &state);
200                        let mut changed = false;
201                        if is_valid {
202                            let mut tmp_linearized = linearized.clone();
203                            tmp_linearized[call.id] = true;
204                            changed = cache.insert((tmp_linearized, new_state.clone()));
205                        }
206                        if changed {
207                            linearized[call.id] = true;
208                            let call = history.lift(curr);
209                            calls.push((call, state));
210                            state = new_state;
211                            curr = 0;
212                        } else {
213                            curr += 1;
214                        }
215                    }
216                },
217                Entry::Response(_) => match calls.pop() {
218                    None => return false,
219                    Some(((call, response), old_state)) => {
220                        state = old_state;
221                        linearized[call.id()] = false;
222                        let (call_index, _) = history.unlift(call, response);
223                        curr = call_index + 1;
224                    }
225                },
226            }
227        }
228    }
229}
230
231#[cfg(test)]
232mod test {
233    use super::*;
234    use history::Action::*;
235
236    #[derive(Copy, Clone, Debug)]
237    enum RegisterOperation {
238        Read(u32),
239        Write(u32),
240    }
241
242    use RegisterOperation::*;
243
244    struct IntegerRegisterSpec;
245
246    impl Specification for IntegerRegisterSpec {
247        type State = u32;
248        type Operation = RegisterOperation;
249
250        fn init() -> Self::State {
251            0
252        }
253
254        fn apply(operation: &Self::Operation, state: &Self::State) -> (bool, Self::State) {
255            match operation {
256                Read(value) => (value == state, *state),
257                Write(value) => (true, *value),
258            }
259        }
260    }
261
262    type RegisterChecker = WGLChecker<IntegerRegisterSpec>;
263
264    mod is_linearizable {
265        use super::*;
266
267        #[test]
268        fn accepts_sequential_read_and_write() {
269            let history = History::from_actions(vec![
270                (0, Call(Write(1))),
271                (0, Response(Write(1))),
272                (0, Call(Read(1))),
273                (0, Response(Read(1))),
274            ]);
275            assert!(RegisterChecker::is_linearizable(history));
276        }
277
278        #[test]
279        fn rejects_invalid_reads() {
280            let history = History::from_actions(vec![
281                (0, Call(Write(1))),
282                (0, Response(Write(1))),
283                (0, Call(Read(2))),
284                (0, Response(Read(2))),
285            ]);
286            assert!(!RegisterChecker::is_linearizable(history));
287        }
288
289        #[test]
290        fn accepts_writes_in_reverse_order() {
291            // Accepts the following history, in which processes
292            // P1, P2, and P3 must linearize their writes in the
293            // reverse order in which they are called.
294            // P0 |--------------------| Write(1)
295            // P1 |--------------------| Write(2)
296            // P2 |--------------------| Write(3)
297            // P3   |--|                 Read(3)
298            // P3          |--|          Read(2)
299            // P3                 |--|   Read(1)
300            let history = History::from_actions(vec![
301                (0, Call(Write(1))),
302                (1, Call(Write(2))),
303                (2, Call(Write(3))),
304                (3, Call(Read(3))),
305                (3, Response(Read(3))),
306                (3, Call(Read(2))),
307                (3, Response(Read(2))),
308                (3, Call(Read(1))),
309                (3, Response(Read(1))),
310                (0, Response(Write(1))),
311                (1, Response(Write(2))),
312                (2, Response(Write(3))),
313            ]);
314            assert!(RegisterChecker::is_linearizable(history));
315        }
316
317        #[test]
318        fn rejects_sequentially_consistent_reads() {
319            // Rejects the following history, in which P1 and P2 read
320            // different values while overlapping with P0s write. Notice
321            // that this history is _sequentially consistent_, as P2s
322            // read could be re-ordered to complete prior to any other
323            // operation.
324            // P0 |-------------------| Write(1)
325            // P1      |--|             Read(1)
326            // P2              |--|     Read(0)
327            let history = History::from_actions(vec![
328                (0, Call(Write(1))),
329                (1, Call(Read(1))),
330                (1, Response(Read(1))),
331                (2, Call(Read(0))),
332                (2, Response(Read(0))),
333                (0, Response(Write(1))),
334            ]);
335            assert!(!RegisterChecker::is_linearizable(history));
336        }
337    }
338}