use std::collections::HashSet;
use std::marker::PhantomData;
use crate::linearizability::history::{Entry, History};
use crate::specifications::Specification;
pub mod history;
pub struct WGLChecker<S: Specification> {
data_type: PhantomData<S>,
}
type OperationEntry<S> = Entry<<S as Specification>::Operation>;
type OperationCall<S> = (
(OperationEntry<S>, OperationEntry<S>),
<S as Specification>::State,
);
impl<S: Specification> WGLChecker<S> {
pub fn is_linearizable(mut history: History<S::Operation>) -> bool {
let mut state = S::init();
let mut linearized = vec![false; history.len()];
let mut calls: Vec<OperationCall<S>> = Vec::new();
let mut cache: HashSet<(Vec<bool>, S::State)> = HashSet::new();
let mut curr = 0;
loop {
if history.is_empty() {
return true;
}
match &history[curr] {
Entry::Call(call) => match &history[history.index_of_id(call.response)] {
Entry::Call(_) => panic!("Response cannot be a call entry"),
Entry::Response(response) => {
let (is_valid, new_state) = S::apply(&response.operation, &state);
let mut changed = false;
if is_valid {
let mut tmp_linearized = linearized.clone();
tmp_linearized[call.id] = true;
changed = cache.insert((tmp_linearized, new_state.clone()));
}
if changed {
linearized[call.id] = true;
let call = history.lift(curr);
calls.push((call, state));
state = new_state;
curr = 0;
} else {
curr += 1;
}
}
},
Entry::Response(_) => match calls.pop() {
None => return false,
Some(((call, response), old_state)) => {
state = old_state;
linearized[call.id()] = false;
let (call_index, _) = history.unlift(call, response);
curr = call_index + 1;
}
},
}
}
}
}
#[cfg(test)]
mod test {
use super::*;
use history::Action::*;
#[derive(Copy, Clone, Debug)]
enum RegisterOperation {
Read(u32),
Write(u32),
}
use RegisterOperation::*;
struct IntegerRegisterSpec;
impl Specification for IntegerRegisterSpec {
type State = u32;
type Operation = RegisterOperation;
fn init() -> Self::State {
0
}
fn apply(operation: &Self::Operation, state: &Self::State) -> (bool, Self::State) {
match operation {
Read(value) => (value == state, *state),
Write(value) => (true, *value),
}
}
}
type RegisterChecker = WGLChecker<IntegerRegisterSpec>;
mod is_linearizable {
use super::*;
#[test]
fn accepts_sequential_read_and_write() {
let history = History::from_actions(vec![
(0, Call(Write(1))),
(0, Response(Write(1))),
(0, Call(Read(1))),
(0, Response(Read(1))),
]);
assert!(RegisterChecker::is_linearizable(history));
}
#[test]
fn rejects_invalid_reads() {
let history = History::from_actions(vec![
(0, Call(Write(1))),
(0, Response(Write(1))),
(0, Call(Read(2))),
(0, Response(Read(2))),
]);
assert!(!RegisterChecker::is_linearizable(history));
}
#[test]
fn accepts_writes_in_reverse_order() {
let history = History::from_actions(vec![
(0, Call(Write(1))),
(1, Call(Write(2))),
(2, Call(Write(3))),
(3, Call(Read(3))),
(3, Response(Read(3))),
(3, Call(Read(2))),
(3, Response(Read(2))),
(3, Call(Read(1))),
(3, Response(Read(1))),
(0, Response(Write(1))),
(1, Response(Write(2))),
(2, Response(Write(3))),
]);
assert!(RegisterChecker::is_linearizable(history));
}
#[test]
fn rejects_sequentially_consistent_reads() {
let history = History::from_actions(vec![
(0, Call(Write(1))),
(1, Call(Read(1))),
(1, Response(Read(1))),
(2, Call(Read(0))),
(2, Response(Read(0))),
(0, Response(Write(1))),
]);
assert!(!RegisterChecker::is_linearizable(history));
}
}
}