Reference object
In this section, we’ll introduce the notion of linearizability as our concurrency semantics, and see how to define a monitor for linearizability. We’ll call this monitor the reference object.
Our service specification has requests and responses. We’ll refer to a
request/response pair as a transaction. Suppose that transactions
occur sequentially (that is, no transaction starts until the previous
transaciton is completed). In this case it’s easy to say what a
correct response is. Each set
transaction results in mutating the
value of one key in the abstract directory. Each response transaction
yields the current value of the given key. These properties define a
consistent sequential execution.
But what about the case when transactions can occur concurrently? In what order should changes to the directory occur? Which values should be returned?
Linearizability gives one possible way to answer these questions. It says that a concurrent execution is consistent if there exists a corresponding sequential execution such that each sequential trancation occurs within the time bounds of the corresponding concurrent transaction. In other words, although a transaction takes time, it appears as if it occurs at a single point in time.
Let’s try to make this idea a little more mathematically precise. We
have a set of transactions that have corresponding requests and
responses. The transactions are partially ordered by a relation we
call “happens before”. We as t1
happens before t2
if the response
of t1
occurs temporally before the request of t2
.
An execution is linearizable if there exists a total order <
on the
transactions such that:
- if
t1
happens beforet2
thent1 < t2
, and - the transactions ordered by
<
are consistent.
Consistency means that the value returned by a get
transaction t
for
key k
is the value of the most recent set
transaction to k
according to <
.
Linearizability is easy to describe on paper, but not so easy to
specify in IVy. This is because there is no way to state in IVY’s
first-order logic that there exists a relation <
having some
property. Instead, we will provide an object that constructs the
order <
by monitoring the behavior of the protocol. This is the
reference object.
The state of the reference object has several components:
- A set of transactions
- The “happens before” relation on transactions
- A subset of committed transactions
- An abstract directory state
The reference object monitors requests and responses, accumulating the
set of transactions, and keeping track of the “happens before”
relation. It also provides a “ghost” action called commit
. When
commit
is called, the given transaction is executed on the abstract
state and added to the committed set. The precondition to commit
transaction t
is that every transaction that happens before t
is
already committed. In this case, the order of committing transactions
is a consistent sequential order <
(and IVy can prove this).
The commit
action is special because we don’t actually indend to
execute it in reality. When we extract code to execute, the commit
actions will be abstracted away. This lets us decorate our code with
commit
actions to help in the proof.
The reference object’s specification starts like this:
module sht_reference(id,key,data) = {
type otype = {read, write}
ghost type txid
function type_(T:txid) : otype
function key_(T:txid) : key.t
function data_(T:txid) : data
We have an emumerated otype
to represent the operation type, and an
uninterpreted ghost type txid
to represent transactions. Because
txid
is a ghost type, values of this type can’t occur in extracted
code. We’ll see how ghost types are used shortly. The functions
type_
, key_
and data_
tell us respectively the operation type, key
and data value associate with the transaction.
This is the monitor state:
relation generated(T:txid)
after init {
generated(T) := false
}
relation committed(T:txid)
after init {
committed(T) := false
}
individual map(A:key.t) : data
after init {
map(A) := 0
}
We keep track of a set of generated
transactions (those that have
begun) as well as a set of committed
transactions, and the abstract state
map
of the directory. Initially, all keys map to value 0
.
The begin
action is called when a transaction starts. It allocates a
fresh transaction id, builds a transaction with that id and adds the
transaction to the set generated
. Being lazy, we assume that there
is always an unused transaction id available. We could prove this by
implementing txid
with, for example, the integers.
action begin(i:id, o:otype, k:key.t, d:data) returns (lt:txid) = {
assume exists T. ~generated(T);
if some c:txid. ~generated(c) {
type_(c) := o;
key_(c) := k;
data_(c) := d;
generated(c) := true;
lt := c
}
}
Notice that the begin
action returns a transaction id. This ghost
value can be used by the protocol implementation to indicate when a
transaction is being committed.
The commit action is called by the protocol to do just that. Here is its specification:
action commit(lt:txid)
before commit {
assert generated(lt) & ~committed(lt);
committed(lt) := true;
if type_(lt) = read {
data_(lt) := map(key_(lt))
}
else {
map(key_(lt)) := data_(lt)
}
}
The precondition for this operation is that the operation has begun,
but has not yet committed. We add the txid
to the commited set and
execute the transaction abstractly. If it is a read
, we set the
transaction’s data based on the current abstract state. If a write, we
set the abstract state based on the transaction’s data.
Finally, when a transaction ends, we have to verify that it has committed (that is, every transaction must commit between begin and end).
action end(lt:txid)
before end {
assert commited(lt)
}
}
In principle, we can now write a monitor for the reference object that maintains the “happens before” relation and checks that the actual commit order is consistent with “happens before”. We’ll leave that problem for later however, and just trust that our reference object is correct. The reference object is all we really need, since it can be used as a specification of our protocol by a client in order to prove the client correct.