Rest
A specification of linearizability for maps.
Takes three parameters:
- txid_t : the thype of transaction identifiers
- key_t : the type of keys
- data_t : the type of values
A map has the following operations:
- put(key,data)
- get(key) -> data
Both actions can fail (i.e., abort) with no effect.
Each operations has to corresponding actions: 'begin' and 'end'. the 'begin' action corresponds to the initiation or request for the transaction and contains the input parameters. The 'end' action corresponds to the reutrn or termination of the transaction and contains the joutput parameters, including a flag to indicate successful completion.
Each action also has an associated transaction id that allow begin and end actions to be matched. A transaction id may be used only once.
The specication requires that the transactions be linearizable, in the sense that each transaction can be assigned a commit point between its 'begin' and 'end' action, the output of all transactions being consitent with the transactions executing atomically at their commit points. The specificaiton fails at the point where all continuations of the action sequence are provably not linearizable (thus it can fail at a point where some transactions are not yet completed).
The specification also requires that no data value is put twice. This can be made true by adding a ghost field to the data. The purpose of this requirement is to make the traces unambigous in the sense that the put associated to a get is uniquely determined. This makes checking linearizability tractable.
module linmap(txid_t,data_t) = {
action put(txid:txid_t, data:data_t)
action end_put(txid:txid_t, ok:bool)
action get(txid:txid_t)
action end_get(txid:txid_t, ok:bool, data:data_t)
The relation hb(T,U) holds when transaction T provably happens before transaction U in the commit order.
relation hb(T:txid_t, U:txid_t)
relation pending(T:txid_t)
relation completed(T:txid_t)
relation failed(T:txid_t)
relation used(T:txid_t,U:txid_t)
relation is_put(T:txid_t)
function value(T:txid_t) : data_t
after init {
hb(T:txid_t, U:txid_t) := false;
pending(T:txid_t) := false;
completed(T:txid_t) := false;
failed(T:txid_t) := false;
used(T:txid_t,U:txid_t) := false;
}
¤
Specification of put operation.
The precondion of 'begin' is that the txid has not been used before and the data value doesn't match any existing put.
In addition, if the txid type is bounded, it is possible that a test can deadlock, when all available txid's are used for concurrent puts. To prvent this deadlock we require that on a put there is at least one other txid that is not a put.
before put {
require ~pending(txid) & ~completed(txid) & ~failed(txid);
require ~((pending(T) | completed(T)) & is_put(T) & value(T) = data);
require exists T. T ~= txid & ~((pending(T) | completed(T)) & is_put(T));
}
after put {
pending(txid) := true;
is_put(txid) := true;
value(txid) := data;
hb(T,txid) := completed(T);
}
before end_put {
require pending(txid);
require used(txid,U) -> ok;
}
after end_put {
pending(txid) := false;
completed(txid) := ok;
failed(txid) := ~ok;
call scrub;
}
¤
Specification of put operation.
The precondion of 'begin' is that the txid has not been used before.
before get {
require ~pending(txid) & ~completed(txid) & ~failed(txid);
}
after get {
pending(txid) := true;
is_put(txid) := false;
hb(T,txid) := completed(T);
}
before end_get {
require pending(txid);
if ok {
used(T,txid) := is_put(T) & (pending(T) | completed(T)) & value(T) = data;
assert exists T. used(T,txid);
hb(T,txid) := hb(T,txid) | used(T,txid);
call update_hb
}
}
after end_get {
pending(txid) := false;
completed(txid) := ok;
failed(txid) := ~ok;
call scrub;
}
relation old_hb(T:txid_t,U:txid_t)
action update_hb_step returns(changed:bool) = {
old_hb(T,U) := hb(T,U);
hb(T,U) := hb(T,U) | exists V. hb(T,V) & hb(V,U);
hb(R,Z) := hb(R,Z) | exists W. hb(W,Z) & used(W,R) & is_put(Z) & W ~= Z;
hb(Z,W) := hb(Z,W) | exists R. hb(Z,R) & used(W,R) & is_put(Z) & W ~= Z;
changed := ~forall T,U. (hb(T,U) <-> old_hb(T,U));
}
action update_hb = {
var changed := true;
while changed {
changed := update_hb_step;
};
assert ~hb(T,T);
}
relation dead(T:txid_t)
action scrub = {
dead(T) :=
failed(T)
| is_put(T) & completed(T)
& (forall G. is_put(G) & pending(G) -> hb(T,G))
& (exists U. is_put(U) & completed(U) & hb(T,U))
& (forall G. ~is_put(G) & pending(G) -> exists U. is_put(U) & completed(U) & hb(T,U) & hb(U,G))
| ~is_put(T) & exists U. completed(U) & used(U,T);
pending(T) := pending(T) & ~dead(T);
completed(T) := completed(T) & ~dead(T);
failed(T) := failed(T) & ~dead(T);
used(T,U) := used(T,U) & ~dead(T) & ~dead(U);
hb(T,U) := hb(T,U) & ~dead(T) & ~dead(U);
}
}