Struct WGLChecker

Source
pub struct WGLChecker<S: Specification> { /* private fields */ }
Expand description

A linearizability checker.

An implementation of the algorithm originally defined by Jeannette Wing and Chun Gong [WG93], and extended by Gavin Lowe [L17]. This particular implementation is based on the description given by Alex Horn and Daniel Kroenig [HK15].

Given a history of operations, the algorithm works by linearizing each operation as soon as possible. When an operation cannot be linearized, it backtracks and proceeds with the next operation. Memoization occurs by caching each partial linearization, and preventing the algorithm from continuing its search when it is already known that the state of the object and remaining operations have no valid linearization.

§Examples

Consider the following Specification of a register containing u32 values.

use todc_utils::specifications::Specification;

#[derive(Copy, Clone, Debug)]
enum RegisterOp {
    Read(Option<u32>),
    Write(u32),
}

use RegisterOp::{Read, Write};

struct RegisterSpec;

impl Specification for RegisterSpec {
    type State = u32;
    type Operation = RegisterOp;
     
    fn init() -> Self::State {
        0
    }

    fn apply(operation: &Self::Operation, state: &Self::State) -> (bool, Self::State) {
        match operation {
            // A read is valid if the value returned is equal to the
            // current state. Reads always leave the state unchanged.
            Read(value) => match value {
                Some(value) => (value == state, *state),
                None => (false, *state)
            },
            // Writes are always valid, and update the state to be
            // equal to the value being written.
            Write(value) => (true, *value),
        }
    }
}

Using the Action::Call and Action::Response types, we can model read and write operations as follows:

  • The call of a read operation is modeled by Call(Read(None)) and a response containing the value x is modeled by Response(Read(Some(x))). We are required to use an Option because the value being read cannot be known until the register responds.
  • Similarily, the call of a write operation with the value y is modeled by Call(Write(y)) and the response is modeled by Response(Write(y)).

Next, we can define a linearizability for this specification, and check some histories.

use todc_utils::linearizability::{WGLChecker, history::{History, Action::{Call, Response}}};

type RegisterChecker = WGLChecker<RegisterSpec>;

// A history of sequantial operations is always linearizable.
// PO |------|          Write(0)
// P1          |------| Read(Some(0))
let history = History::from_actions(vec![
    (0, Call(Write(0))),
    (0, Response(Write(0))),
    (1, Call(Read(None))),
    (1, Response(Read(Some(0)))),
]);
assert!(RegisterChecker::is_linearizable(history));

// Concurrent operations might not be linearized
// in the order in which they are called.
// PO |--------------|  Write(0)
// P1  |--------------| Write(1)
// P2    |---|          Read(Some(1))
// P3           |---|   Read(Some(0))
let history = History::from_actions(vec![
    (0, Call(Write(0))),
    (1, Call(Write(1))),
    (2, Call(Read(None))),
    (2, Response(Read(Some(1)))),
    (3, Call(Read(None))),
    (3, Response(Read(Some(0)))),
    (0, Response(Write(0))),
    (1, Response(Write(1))),
]);
assert!(RegisterChecker::is_linearizable(history));

// A sequentially consistent history is **not**
// necessarily linearizable.
// PO |---|             Write(0)
// P1 |---|             Write(1)
// P2       |---|       Read(Some(1))
// P3             |---| Read(Some(0))
let history = History::from_actions(vec![
    (0, Call(Write(0))),
    (1, Call(Write(1))),
    (0, Response(Write(0))),
    (1, Response(Write(1))),
    (2, Call(Read(None))),
    (2, Response(Read(Some(1)))),
    (3, Call(Read(None))),
    (3, Response(Read(Some(0)))),
]);
assert!(!RegisterChecker::is_linearizable(history));

For examples of using WGLChecker to check the linearizability of more complex histories, see todc-mem/tests/snapshot/common.rs or todc-utils/tests/linearizability/etcd.rs.

§Implementations in Other Languages

For an implementation in C++, see linearizability-checker. For an implementation in Go, see porcupine.

Implementations§

Source§

impl<S: Specification> WGLChecker<S>

Source

pub fn is_linearizable(history: History<S::Operation>) -> bool

Returns whether the history of operations is linearizable with respect to the specification.

Auto Trait Implementations§

§

impl<S> Freeze for WGLChecker<S>

§

impl<S> RefUnwindSafe for WGLChecker<S>
where S: RefUnwindSafe,

§

impl<S> Send for WGLChecker<S>
where S: Send,

§

impl<S> Sync for WGLChecker<S>
where S: Sync,

§

impl<S> Unpin for WGLChecker<S>
where S: Unpin,

§

impl<S> UnwindSafe for WGLChecker<S>
where S: UnwindSafe,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.