Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Table of Contents

...

MODEL, SPECIFICATION, AND VERIFICATION OF MUSIC

open util/ordering [Time] as trace
open util/integer

...

open util/ordering [Time] as trace
open util/integer
open util/ordering [LockRef] as queue
/* ========================================================================
TEMPORAL STRUCTURE
Note that if time has a scope of x, there will be exactly x times and
x-1 real events.
======================================================================== */

sig Time { }

abstract sig Event { pre: Time, post: Time }
sig Null extends Event { } -- null events can only be at beginning of trace
fact NullEvent { all e: Null | no s: SystemState | e.pre = s.time }

fact TemporalStructure {
all t: Time - trace/last | one e: Event | e.pre = t
all t: trace/last | no e: Event | e.pre = t
all e: Event | e.post = (e.pre).next }

/* ========================================================================
ABOUT LOCKREFS AND TIMESTAMPS
======================================================================== */

sig LockRef { }

pred PastQueue [g: set LockRef, f: lone LockRef, -- (g, f) is older
q: set LockRef, p: lone LockRef] { -- than (q, p)
(g = q)
=> (f != p && ( (some f && some p && queue/lt [f, p])
|| (some f && no p) ) )
else ( (g in q)
&& ( (some f && some p && queue/lte [f, p])
|| (some f && no p)
|| (no f && some p && p in (q - g))
|| (no f && no p) ) ) }

pred PastPtr [f: LockRef, s: SystemState] {
f in s.trueQset && (no s.trueQptr || queue/gt [s.trueQptr, f]) }

pred CurrentOrPastPtr [f: LockRef, s: SystemState] {
f in s.trueQset && (no s.trueQptr || queue/gte [s.trueQptr, f]) }

sig Timestamp {ref: LockRef, tme: Time}

fact TimestampsAreRecords { all p0, p1: Timestamp |
(p0.ref = p1.ref && p0.tme = p1.tme) => p0 = p1 }

pred EarlierTimestamp [t0, t1: Timestamp] {-- true if t0 is earlier than t1
queue/lt [t0.ref, t1.ref] || (t0.ref = t1.ref && lt [t0.tme, t1.tme]) }

pred EarlierOrEqualTimestamp [t0, t1: Timestamp] {
queue/lt [t0.ref, t1.ref] || (t0.ref = t1.ref && lte [t0.tme, t1.tme]) }

pred PastTimestamp [t: Timestamp, s: SystemState ] {
PastPtr [t.ref, s] || (t.ref = s.trueQptr && lt [t.tme, s.time]) }

pred CurrentOrPastTimestamp [t: Timestamp, s: SystemState ] {
PastPtr [t.ref, s] || (t.ref = s.trueQptr && lte [t.tme, s.time]) }

/* ========================================================================
ABOUT MESSAGES
======================================================================== */

abstract sig Message { stamp: Timestamp, val: Value }

abstract sig RepMsg extends Message { cli: Client }
sig RepPutMsg, RepGetMsg extends RepMsg { }

fact RepMsgsAreRecords { all m0, m1: RepMsg |
(m0.cli = m1.cli && m0.stamp = m1.stamp && m0.val = m1.val) => m0 = m1 }

abstract sig ClientMsg extends Message { rep: Replica }
abstract sig ClientPutMsg, ClientGetMsg extends ClientMsg { }
sig ClientPutAck, ClientPutNack, ClientPutNohold extends ClientPutMsg {}
sig ClientGetAck, ClientGetNack, ClientGetNohold extends ClientGetMsg {}

fact ClientMsgsAreRecords { all m0, m1: ClientMsg |
(m0.rep = m1.rep && m0.stamp = m1.stamp && m0.val = m1.val) => m0 = m1 }

/* ========================================================================
SYSTEM STATE AND TRACES
There is only one key, which is not explicit.
Replicas of the lock store may not be the same as MUSIC replicas, but
there is a one-to-one correspondence between replicas of the two types.
Also, operations on the lock store are atomic, reliable, and sequential.
If a lock store replica is dead, it is modeled as a replica whose value is
very out-of-date, as only a quorum of replicas need have the true value.
======================================================================== */

sig Client { }

sig Value { } one sig True, False extends Value { }

sig Replica { } fact { # Replica = 5 }

abstract sig ClientState { }
one sig Dead, Live, Enqueued, Critical, MustGet, MustPut, MustPut4Synch,
SynchGetting, SynchPutting, Getting, Putting extends ClientState { }

abstract sig ReplicaState { }
one sig Failed, Started extends ReplicaState { }

sig SystemState {
time: Time,
-- lock store
-- real state
Qset: Replica -> LockRef,
Qptr: Replica -> lone LockRef,
trueDataSynchFlag: True + False, -- true value in eventu.consis. data
-- history variables
trueQset: set LockRef,
trueQptr: lone LockRef,
synchFlag: True + False,
-- client state
-- real state
clientRef: Client -> lone LockRef,
clientState: Client -> one ClientState,
clientTask: Client -> lone (ClientPutAck + ClientGetAck),
-- what client is waiting for
clientInputs: Client -> ClientMsg,
-- history variables
trueTimestamp: Timestamp,
trueValue: Value,
-- music state
repState: Replica -> one ReplicaState,
repInputs: Replica -> RepMsg,
repTask: Replica -> lone RepMsg, -- what replica is working on
-- music's view of data store
-- real state
writePending: Timestamp -> lone Value,
writeSucceeded: Timestamp -> lone Value,
defined: set Time,
-- derived variables
trueDataTimestamp: Timestamp,
trueDataValue: Value,
} {
some (writePending + writeSucceeded)

-- the true data tuple is the written tuple with the highest timestamp
all t: Timestamp, v: Value |
( (t -> v) in (writePending + writeSucceeded)
&& (no t': Timestamp, v': Value |
(t' -> v') in (writePending + writeSucceeded)
&& EarlierTimestamp [t, t'] )
) => (t = trueDataTimestamp && v = trueDataValue)
}

pred DataStoreDefined [s: SystemState] {
-- the datastore is well-defined when it has a true tuple in writeSucceeded
some s.trueDataTimestamp
&& (s.trueDataTimestamp -> s.trueDataValue) in s.writeSucceeded }

fact UpdateOfDefinedSet {
all e: TraceEvent, s: e.prest, s': e.postst |
DataStoreDefined [s] => s'.defined = s.defined + s.time
else s'.defined = s.defined }

pred InitialState [s: SystemState] {
s.trueQset = queue/first && no s.trueQptr
s.Qset = (Replica -> queue/first) && no s.Qptr
Client.(s.clientState) in Dead && some s.trueValue
some s.trueTimestamp
(s.trueTimestamp).ref = queue/first && lt [(s.trueTimestamp).tme,s.time]
s.synchFlag = False && s.trueDataSynchFlag = False
no s.clientRef && no s.clientTask && no s.clientInputs
Replica.(s.repState) in Failed && no s.repInputs && no s.repTask
no s.writePending && no s.defined
s.writeSucceeded = (s.trueTimestamp -> s.trueValue) }

pred LockStoreInvariant [s: SystemState] {
-- LockRefs are ordered. A queue is represented by a set of lockRefs and
-- an optional pointer. The set contains all the lockRefs that have ever
-- been enqueued (ordered implicitly by the order on lockRefs, which is
-- also the order of use). If there is a pointer, it points to the current
-- lockHolder. If there is no pointer, all lockRefs in the set have been
-- used and released.
-- Unused lockRefs are always larger than queued ones.
all f0: s.trueQset, f1: LockRef - s.trueQset | queue/lt [f0, f1]
-- The trueQptr is in the trueQset.
s.trueQptr in s.trueQset
-- A replica's Qset must be the trueQset or a subset of it that is a
-- "prefix" of it, in the sense of the ordering on lockRefs.
all r: Replica |
( r.(s.Qset) in s.trueQset
&& all f0: r.(s.Qset), f1: s.trueQset - r.(s.Qset) | queue/lt [f0, f1] )
-- A replica's Qptr is in the replica's Qset.
all r: Replica | r.(s.Qptr) in r.(s.Qset)
-- A replica's queue must be a current or past truth.
all r: Replica |
( (r.(s.Qset) = s.trueQset && r.(s.Qptr) = s.trueQptr)
|| PastQueue [r.(s.Qset), r.(s.Qptr), s.trueQset, s.trueQptr] )
-- At least a quorum of replicas have the true queue.
let correctQueues =
{ r: Replica | r.(s.Qset) = s.trueQset && r.(s.Qptr) = s.trueQptr } |
gte [ # correctQueues, 3]
-- this is a basic safety property for the implementation of the synch flag
s.trueDataSynchFlag = False => s.synchFlag = False
}

pred ClientStateInvariant [s: SystemState] {
-- includes client's relation to music
-- A client's lockRef is found in the trueQset.
all c: Client | c.(s.clientRef) in s.trueQset
-- A client's lockRef is unique among clients, and is always in the
-- trueQset.
let repMsgRef = { c: Client, f: LockRef |
some m: Replica.(s.repInputs + s.repTask) |
(c = m.cli && f = m.stamp.ref) } |
let clientMsgRef = { c: Client, f: LockRef |
some m: c.(s.clientInputs + s.clientTask) |
f = m.stamp.ref && f = c.(s.clientRef) } |
( (all f: LockRef | lone (s.clientRef + repMsgRef + clientMsgRef).f)
&& (all f: Client.(s.clientRef + repMsgRef + clientMsgRef) |
f in s.trueQset )
)
-- properties of client states
all c: Client | c.(s.clientState) in (Dead + Live)
=> ( no c.(s.clientRef)
&& no c.(s.clientTask) )
all c: Client | c.(s.clientState) in Enqueued
=> ( some c.(s.clientRef)
&& ! PastPtr [c.(s.clientRef), s]
&& no c.(s.clientTask) )
all c: Client | c.(s.clientState) in Critical + MustGet
=> ( some c.(s.clientRef)
&& CurrentOrPastPtr [c.(s.clientRef), s]
&& no c.(s.clientTask) )
all c: Client | c.(s.clientState) in MustPut + MustPut4Synch
=> ( some c.(s.clientRef)
&& CurrentOrPastPtr [c.(s.clientRef),s]
&& some c.(s.clientTask) -- only the task value is significant
&& c.(s.clientTask) in ClientPutAck )
all c: Client | c.(s.clientState) in SynchGetting + Getting
=> ( some c.(s.clientRef)
&& CurrentOrPastPtr [c.(s.clientRef), s]
&& some c.(s.clientTask) -- only replica and stamp are significant
&& c.(s.clientTask) in ClientGetAck )
all c: Client | c.(s.clientState) in SynchPutting + Putting
=> ( some c.(s.clientRef)
&& CurrentOrPastPtr [c.(s.clientRef), s]
&& some c.(s.clientTask) && c.(s.clientTask) in ClientPutAck )
-- the client task must be in the client's current epoch
all c: Client, m: ClientMsg |
m in c.(s.clientTask) => m.stamp.ref = c.(s.clientRef)
}

pred MusicStateInvariant [s: SystemState] {
-- includes Music's relation to datastore
-- If a replica has a task, it is started.
all r: Replica | some r.(s.repTask) => r.(s.repState) = Started
-- writes being waited for are writes pending or succeeded
all r: Replica, m: RepPutMsg |
(r -> m) in s.repTask
=> (m.stamp -> m.val) in s.writePending + s.writeSucceeded
}

pred DataStoreInvariant [s: SystemState] {
-- datastore writes are in the past
all t: (s.writeSucceeded).Value | PastTimestamp [t, s]
-- writes pending are in the past or present
all t: (s.writePending).Value | CurrentOrPastTimestamp [t, s]
-- if the "write sets" have a timestamp overlap, then the values are
-- consistent
all t: Timestamp, v, v': Value |
( (t -> v) in s.writePending && (t -> v') in s.writeSucceeded )
=> v = v'
-- all times in the defined set are in the past
all t: s.defined | lt [t, s.time]
}

pred GlobalConsistencyInvariant [s: SystemState] {
-- in general, these constraints cover all clients and all lockRefs, not
-- just the lockholding client and trueQptr
-- no current or future true timestamps
PastTimestamp [s.trueTimestamp, s]
-- no future messages or tasks
all m:
Client.(s.clientTask+s.clientInputs) + Replica.(s.repInputs+s.repTask) |
CurrentOrPastTimestamp [m.stamp, s]
-- no current or future data store writes
all t: (s.writePending + s.writeSucceeded).Value |
CurrentOrPastTimestamp [t, s]
-- if a client has a lockRef, and the true timestamp is in the same epoch,
-- then the true timestamp is in the past with respect to this epoch;
-- NOTE this is a subtle constraint, implying that whatever local clock is
-- being used, it can distinguish well enough between sequential actions
-- of the same client
all c: Client |
( some c.(s.clientRef) && s.trueTimestamp.ref = c.(s.clientRef) )
=> lt [s.trueTimestamp.tme, s.time]
-- same as above for datastore writes
all c: Client, t: (s.writePending + s.writeSucceeded).Value |
( some c.(s.clientRef) && t.ref = c.(s.clientRef) )
=> lt [t.tme, s.time]
-- if a client has a task, it is in the present or past with respect to
-- the client's epoch
all c: Client, m: c.(s.clientTask) | lte [m.stamp.tme, s.time]
-- if a client has a task, then all repInputs and repTasks with the same
-- lockRef have the same time or an earlier one
all c: Client, m: c.(s.clientTask),
m': Replica.(s.repInputs + s.repTask) |
m'.stamp.ref = m.stamp.ref => trace/lte [m'.stamp.tme, m.stamp.tme]
-- same as above for datastore writes
all c: Client, m: c.(s.clientTask), t: Timestamp |
( t in (s.writePending + s.writeSucceeded).Value
&& t.ref = m.stamp.ref )
=> lte [t.tme, m.stamp.tme]
-- if a client has a lockRef but no task, then all repInputs and repTasks
-- with the same lockRef are in the present or past
all c: Client, m: Replica.(s.repInputs + s.repTask) |
( some c.(s.clientRef) && no c.(s.clientTask)
&& m.stamp.ref = c.(s.clientRef) )
=> trace/lte [m.stamp.tme, s.time]
-- same as above for datastore writes
all c: Client, t: (s.writePending + s.writeSucceeded).Value |
( some c.(s.clientRef) && no c.(s.clientTask)
&& t.ref = c.(s.clientRef) )
=> trace/lte [t.tme, s.time]
-- across the global state, consistent pairings of timestamps and values
let msgPairings = { t: Timestamp, v: Value |
some m: ClientPutMsg + RepPutMsg |
( m in Client.(s.clientTaskopen util/ordering [LockRef] as queue


TEMPORAL STRUCTURE


Note that if time has a scope of x, there will be exactly x times and
x-1 real events.

sig Time { }

abstract sig Event { pre: Time, post: Time }
sig Null extends Event { } -- null events can only be at beginning of trace
fact NullEvent { all e: Null | no s: SystemState | e.pre = s.time }

fact TemporalStructure {
all t: Time - trace/last | one e: Event | e.pre = t
all t: trace/last | no e: Event | e.pre = t
all e: Event | e.post = (e.pre).next }


ABOUT LOCKREFS AND TIMESTAMPS

sig LockRef { }

pred PastQueue [g: set LockRef, f: lone LockRef, -- (g, f) is older
q: set LockRef, p: lone LockRef] { -- than (q, p)
(g = q)
=> (f != p && ( (some f && some p && queue/lt [f, p])
|| (some f && no p) ) )
else ( (g in q)
&& ( (some f && some p && queue/lte [f, p])
|| (some f && no p)
|| (no f && some p && p in (q - g))
|| (no f && no p) ) ) }

pred PastPtr [f: LockRef, s: SystemState] {
f in s.trueQset && (no s.trueQptr || queue/gt [s.trueQptr, f]) }

pred CurrentOrPastPtr [f: LockRef, s: SystemState] {
f in s.trueQset && (no s.trueQptr || queue/gte [s.trueQptr, f]) }

sig Timestamp {ref: LockRef, tme: Time}

fact TimestampsAreRecords { all p0, p1: Timestamp |
(p0.ref = p1.ref && p0.tme = p1.tme) => p0 = p1 }

pred EarlierTimestamp [t0, t1: Timestamp] {-- true if t0 is earlier than t1
queue/lt [t0.ref, t1.ref] || (t0.ref = t1.ref && lt [t0.tme, t1.tme]) }

pred EarlierOrEqualTimestamp [t0, t1: Timestamp] {
queue/lt [t0.ref, t1.ref] || (t0.ref = t1.ref && lte [t0.tme, t1.tme]) }

pred PastTimestamp [t: Timestamp, s: SystemState ] {
PastPtr [t.ref, s] || (t.ref = s.trueQptr && lt [t.tme, s.time]) }

pred CurrentOrPastTimestamp [t: Timestamp, s: SystemState ] {
PastPtr [t.ref, s] || (t.ref = s.trueQptr && lte [t.tme, s.time]) }

ABOUT MESSAGES

abstract sig Message { stamp: Timestamp, val: Value }

abstract sig RepMsg extends Message { cli: Client }
sig RepPutMsg, RepGetMsg extends RepMsg { }

fact RepMsgsAreRecords { all m0, m1: RepMsg |
(m0.cli = m1.cli && m0.stamp = m1.stamp && m0.val = m1.val) => m0 = m1 }

abstract sig ClientMsg extends Message { rep: Replica }
abstract sig ClientPutMsg, ClientGetMsg extends ClientMsg { }
sig ClientPutAck, ClientPutNack, ClientPutNohold extends ClientPutMsg {}
sig ClientGetAck, ClientGetNack, ClientGetNohold extends ClientGetMsg {}

fact ClientMsgsAreRecords { all m0, m1: ClientMsg |
(m0.rep = m1.rep && m0.stamp = m1.stamp && m0.val = m1.val) => m0 = m1 }

SYSTEM STATE AND TRACES


There is only one key, which is not explicit.
Replicas of the lock store may not be the same as MUSIC replicas, but
there is a one-to-one correspondence between replicas of the two types.
Also, operations on the lock store are atomic, reliable, and sequential.
If a lock store replica is dead, it is modeled as a replica whose value is
very out-of-date, as only a quorum of replicas need have the true value.
======================================================================== */

sig Client { }

sig Value { } one sig True, False extends Value { }

sig Replica { } fact { # Replica = 5 }

abstract sig ClientState { }
one sig Dead, Live, Enqueued, Critical, MustGet, MustPut, MustPut4Synch,
SynchGetting, SynchPutting, Getting, Putting extends ClientState { }

abstract sig ReplicaState { }
one sig Failed, Started extends ReplicaState { }

sig SystemState {
time: Time,
-- lock store
-- real state
Qset: Replica -> LockRef,
Qptr: Replica -> lone LockRef,
trueDataSynchFlag: True + False, -- true value in eventu.consis. data
-- history variables
trueQset: set LockRef,
trueQptr: lone LockRef,
synchFlag: True + False,
-- client state
-- real state
clientRef: Client -> lone LockRef,
clientState: Client -> one ClientState,
clientTask: Client -> lone (ClientPutAck + ClientGetAck),
-- what client is waiting for
clientInputs: Client -> ClientMsg,
-- history variables
trueTimestamp: Timestamp,
trueValue: Value,
-- music state
repState: Replica -> one ReplicaState,
repInputs: Replica -> RepMsg,
repTask: Replica -> lone RepMsg, -- what replica is working on
-- music's view of data store
-- real state
writePending: Timestamp -> lone Value,
writeSucceeded: Timestamp -> lone Value,
defined: set Time,
-- derived variables
trueDataTimestamp: Timestamp,
trueDataValue: Value,
} {
some (writePending + writeSucceeded)

-- the true data tuple is the written tuple with the highest timestamp
all t: Timestamp, v: Value |
( (t -> v) in (writePending + writeSucceeded)
&& (no t': Timestamp, v': Value |
(t' -> v') in (writePending + writeSucceeded)
&& EarlierTimestamp [t, t'] )
) => (t = trueDataTimestamp && v = trueDataValue)
}

pred DataStoreDefined [s: SystemState] {
-- the datastore is well-defined when it has a true tuple in writeSucceeded
some s.trueDataTimestamp
&& (s.trueDataTimestamp -> s.trueDataValue) in s.writeSucceeded }

fact UpdateOfDefinedSet {
all e: TraceEvent, s: e.prest, s': e.postst |
DataStoreDefined [s] => s'.defined = s.defined + s.time
else s'.defined = s.defined }

pred InitialState [s: SystemState] {
s.trueQset = queue/first && no s.trueQptr
s.Qset = (Replica -> queue/first) && no s.Qptr
Client.(s.clientState) in Dead && some s.trueValue
some s.trueTimestamp
(s.trueTimestamp).ref = queue/first && lt [(s.trueTimestamp).tme,s.time]
s.synchFlag = False && s.trueDataSynchFlag = False
no s.clientRef && no s.clientTask && no s.clientInputs
Replica.(s.repState) in Failed && no s.repInputs && no s.repTask
no s.writePending && no s.defined
s.writeSucceeded = (s.trueTimestamp -> s.trueValue) }

pred LockStoreInvariant [s: SystemState] {
-- LockRefs are ordered. A queue is represented by a set of lockRefs and
-- an optional pointer. The set contains all the lockRefs that have ever
-- been enqueued (ordered implicitly by the order on lockRefs, which is
-- also the order of use). If there is a pointer, it points to the current
-- lockHolder. If there is no pointer, all lockRefs in the set have been
-- used and released.
-- Unused lockRefs are always larger than queued ones.
all f0: s.trueQset, f1: LockRef - s.trueQset | queue/lt [f0, f1]
-- The trueQptr is in the trueQset.
s.trueQptr in s.trueQset
-- A replica's Qset must be the trueQset or a subset of it that is a
-- "prefix" of it, in the sense of the ordering on lockRefs.
all r: Replica |
( r.(s.Qset) in s.trueQset
&& all f0: r.(s.Qset), f1: s.trueQset - r.(s.Qset) | queue/lt [f0, f1] )
-- A replica's Qptr is in the replica's Qset.
all r: Replica | r.(s.Qptr) in r.(s.Qset)
-- A replica's queue must be a current or past truth.
all r: Replica |
( (r.(s.Qset) = s.trueQset && r.(s.Qptr) = s.trueQptr)
|| PastQueue [r.(s.Qset), r.(s.Qptr), s.trueQset, s.trueQptr] )
-- At least a quorum of replicas have the true queue.
let correctQueues =
{ r: Replica | r.(s.Qset) = s.trueQset && r.(s.Qptr) = s.trueQptr } |
gte [ # correctQueues, 3]
-- this is a basic safety property for the implementation of the synch flag
s.trueDataSynchFlag = False => s.synchFlag = False
}

pred ClientStateInvariant [s: SystemState] {
-- includes client's relation to music
-- A client's lockRef is found in the trueQset.
all c: Client | c.(s.clientRef) in s.trueQset
-- A client's lockRef is unique among clients, and is always in the
-- trueQset.
let repMsgRef = { c: Client, f: LockRef |
some m: Replica.(s.repInputs + s.repTask) |
(c = m.cli && f = m.stamp.ref) } |
let clientMsgRef = { c: Client, f: LockRef |
some m: c.(s.clientInputs + s.clientTask) |
f = m.stamp.ref && f = c.(s.clientRef) } |
( (all f: LockRef | lone (s.clientRef + repMsgRef + clientMsgRef).f)
&& (all f: Client.(s.clientRef + repMsgRef + clientMsgRef) |
f in s.trueQset )
)
-- properties of client states
all c: Client | c.(s.clientState) in (Dead + Live)
=> ( no c.(s.clientRef)
&& no c.(s.clientTask) )
all c: Client | c.(s.clientState) in Enqueued
=> ( some c.(s.clientRef)
&& ! PastPtr [c.(s.clientRef), s]
&& no c.(s.clientTask) )
all c: Client | c.(s.clientState) in Critical + MustGet
=> ( some c.(s.clientRef)
&& CurrentOrPastPtr [c.(s.clientRef), s]
&& no c.(s.clientTask) )
all c: Client | c.(s.clientState) in MustPut + MustPut4Synch
=> ( some c.(s.clientRef)
&& CurrentOrPastPtr [c.(s.clientRef),s]
&& some c.(s.clientTask) -- only the task value is significant
&& c.(s.clientTask) in ClientPutAck )
all c: Client | c.(s.clientState) in SynchGetting + Getting
=> ( some c.(s.clientRef)
&& CurrentOrPastPtr [c.(s.clientRef), s]
&& some c.(s.clientTask) -- only replica and stamp are significant
&& c.(s.clientTask) in ClientGetAck )
all c: Client | c.(s.clientState) in SynchPutting + Putting
=> ( some c.(s.clientRef)
&& CurrentOrPastPtr [c.(s.clientRef), s]
&& some c.(s.clientTask) && c.(s.clientTask) in ClientPutAck )
-- the client task must be in the client's current epoch
all c: Client, m: ClientMsg |
m in c.(s.clientTask) => m.stamp.ref = c.(s.clientRef)
}

pred MusicStateInvariant [s: SystemState] {
-- includes Music's relation to datastore
-- If a replica has a task, it is started.
all r: Replica | some r.(s.repTask) => r.(s.repState) = Started
-- writes being waited for are writes pending or succeeded
all r: Replica, m: RepPutMsg |
(r -> m) in s.repTask
=> (m.stamp -> m.val) in s.writePending + s.writeSucceeded
}

pred DataStoreInvariant [s: SystemState] {
-- datastore writes are in the past
all t: (s.writeSucceeded).Value | PastTimestamp [t, s]
-- writes pending are in the past or present
all t: (s.writePending).Value | CurrentOrPastTimestamp [t, s]
-- if the "write sets" have a timestamp overlap, then the values are
-- consistent
all t: Timestamp, v, v': Value |
( (t -> v) in s.writePending && (t -> v') in s.writeSucceeded )
=> v = v'
-- all times in the defined set are in the past
all t: s.defined | lt [t, s.time]
}

pred GlobalConsistencyInvariant [s: SystemState] {
-- in general, these constraints cover all clients and all lockRefs, not
-- just the lockholding client and trueQptr
-- no current or future true timestamps
PastTimestamp [s.trueTimestamp, s]
-- no future messages or tasks
all m:
Client.(s.clientTask+s.clientInputs) + Replica.(s.repInputs+s.repTask)
&& t = m.stamp && v = m.val ) }||
CurrentOrPastTimestamp [m.stamp, s]
-- no current or future data store writes
all t: Timestamp |
lone t. ( msgPairings + s.writePending + s.writeSucceeded
+ (s.trueTimestamp -> s.trueValue) ) ).Value |
CurrentOrPastTimestamp [t, s]
-- all messages associated with if a client have the client's lockref or one
-- earlier than the client's lockref, or the client has no lockrefhas a lockRef, and the true timestamp is in the same epoch,
-- then the true timestamp is in the past with respect to this epoch;
-- NOTE this is a subtle constraint, implying that whatever local clock is
-- being used, it can distinguish well enough between sequential actions
-- of the same client
all c: Client , m: Message |
( m in Replica.(s.repInputs + some c.(s.repTaskclientRef) && m.cli = c )
=> ( m.stamps.trueTimestamp.ref = c.(s.clientRef) )
|| queue/=> lt [ms.stamptrueTimestamp.reftme, c.( s.clientRef)] || no c.(s.clientRef) )
-- if a client has a valid Nohold message in its input queue, then the
-- client is a past lockholder.time]
-- same as above for datastore writes
all c: Client, f: LockRef, m: ClientPutNohold + ClientGetNohold t: (s.writePending + s.writeSucceeded).Value |
( f = some c.(s.clientRef) && m in t.ref = c.(s.clientInputs) && m.stamp.ref = f)
=> PastPtr [f, s]
}pred SynchingInvariant [s: SystemState] {clientRef) )
=> lt [t.tme, s.time]
-- if a client is in a pre-synching state, there should be no put messageshas a task, it is in the present or past with respect to
-- within the same client's epoch
all c: Client |
, m: c.(s.clientState) in Enqueued + MustGet + SynchGetting
=> (all m: RepPutMsg | m in Replica.(s.repInputs + s.repTask)
=> m.stamp.ref != c.(s.clientRef) )
-- same as above for data store writes
all c: Client, t: (s.writePending + s.writeSucceeded).Value |
c.(s.clientState) in Enqueued + MustGet + SynchGetting
=> t.ref != c.(s.clientRef)
-- synch flag NEEDED if there is an active client with a past lockRef later
-- than or equal to that of the true timestamp
(some c: Client, f: LockRef | c.(s.clientRef) = f && PastPtr [f, s]
&& queue/lte [s.trueTimestamp.ref, f] )
=> s.synchFlag = True
-- synch flag NEEDED if there are old tasks in the system that could change
-- the key value
(some m: RepPutMsg |
m in clientTask) | lte [m.stamp.tme, s.time]
-- if a client has a task, then all repInputs and repTasks with the same
-- lockRef have the same time or an earlier one
all c: Client, m: c.(s.clientTask),
m': Replica.(s.repInputs + s.repTask) |
m'.stamp.ref = m.stamp.ref => trace/lte [m'.stamp.tme, m.stamp.tme]
-- same as above for datastore writes
all c: Client, m: c.(s.clientTask), t: Timestamp |
( t in (s.writePending + s.writeSucceeded).Value
&& t.ref = m.stamp.ref )
=> lte [t.tme, m.stamp.tme]
-- if a client has a lockRef but no task, then all repInputs and repTasks
-- with the same lockRef are in the present or past
all c: Client, m: Replica.(s.repInputs + s.repTask) |
( some c.(s.clientRef) && PastPtr [m.stamp.ref, s]no c.(s.clientTask)
&& EarlierTimestamp [s.trueTimestamp, m.stamp] .ref = c.(s.clientRef) )
=> trace/lte [m.stamp.tme, s.synchFlag = Truetime]
-- synch flag NEEDED if there is a true data timestamp later than the true
-- timestamp and not in the current epoch
( EarlierTimestamp [s.trueTimestamp, s.trueDataTimestamp]
&& s.trueDataTimestamp.ref != s.trueQptr )
=> s.synchFlag = True
-- if the synch flag is on and there is a lockholding client, it must be
-- synching
( s.synchFlag = True
&& (some c: Client | some s.trueQptr && c.(s.clientRef) = s.trueQptr) )
=> (all c: Client |
c.(s.clientRef) = s.trueQptr
=> c.(s.clientState) in
Enqueued+MustGet+SynchGetting+MustPut4Synch+SynchPutting )
-- if the synch flag is off, then the datastore is synched or the
-- lockholding client is putting or the lockholder has just failed and
-- there has been no forced release yet
s.synchFlag = False
=> ( ( DataStoreDefined [s]
&& s.trueDataTimestamp = s.trueTimestamp
&& s.trueDataValue = s.trueValue )
|| (some c: Client |
c.(s.clientRef) = s.trueQptr
&& c.(s.clientState) in MustPut+MustPut4Synch+SynchPutting+Putting
)
|| (some s.trueQptr && (no c: Client | c.(s.clientRef) = s.trueQptr))
)

}

pred LockHolderInvariant [s: SystemState] {
-- when the lockholder is pre-synching, the true timestamp is not in
-- current epoch
all c: Client |
( c.(s.clientRef) = s.trueQptr
&& c.(s.clientState) in
Enqueued+MustGet+SynchGetting+MustPut4Synch+SynchPutting )
=> queue/lt[(s.trueTimestamp).ref, s.trueQptr]
-- if lockholding client is in a non-putting state, there should be no put
-- messages more recent than the trueTimestamp
all c: Client, m: RepPutMsg & same as above for datastore writes
all c: Client, t: (s.writePending + s.writeSucceeded).Value |
( some c.(s.clientRef) && no c.(s.clientTask)
&& t.ref = c.(s.clientRef) )
=> trace/lte [t.tme, s.time]
-- across the global state, consistent pairings of timestamps and values
let msgPairings = { t: Timestamp, v: Value |
some m: ClientPutMsg + RepPutMsg |
( m in Client.(s.clientTask) + Replica.(s.repInputs + s.repTask)
&& t = m.stamp && v = m.val ) }|
all t: Timestamp |
lone t.( msgPairings + s.writePending + s.writeSucceeded
+ (s.trueTimestamp -> s.trueValue) )
-- all messages associated with a client have the client's lockref or one
-- earlier than the client's lockref, or the client has no lockref
all c: Client, m: Message |
( m in Replica.(s.repInputs + s.repTask) && m.cli = c )
=> ( m.stamp.ref = c.(s.clientRef)
|| queue/lt [m.stamp.ref, c.(s.clientRef)] || no c.(s.clientRef) )
-- if a client has a valid Nohold message in its input queue, then the
-- client is a past lockholder.
all c: Client, f: LockRef, m: ClientPutNohold + ClientGetNohold |
(f = c.(s.clientRef) && m in c.(s.clientInputs) && m.stamp.ref = f)
=> PastPtr [f, s]
}

pred SynchingInvariant [s: SystemState] {
-- if a client is in a pre-synching state, there should be no put messages
-- within the same epoch
all c: Client |
c.(s.clientState) in Enqueued + MustGet + SynchGetting
=> (all m: RepPutMsg | m in Replica.(s.repInputs + s.repTask) |
( c.(s.clientRef) = s.trueQptr
&& c.(s.clientState) in Critical + Getting
&& => m.stamp.ref != c.(s.clientRef) )
=> EarlierOrEqualTimestamp [m.stamp, s.trueTimestamp]
-- same as above for data store writes
all c: Client, t: (s.writePending + s.writeSucceeded).Value |
( c.(s.clientRef) = s.trueQptr && t.ref = c.(s.clientRef)
&& c.(s.clientState) in Critical + Getting )
=> EarlierOrEqualTimestamp [t, s.trueTimestamp]
-- critical put correctness
all c: Client |
-- precondition of lockholder's AcceptPut
( c.(s.clientState) in SynchPutting + Putting
&& clientState) in Enqueued + MustGet + SynchGetting
=> t.ref != c.(s.clientRef)
-- synch flag NEEDED if there is an active client with a past lockRef later
-- than or equal to that of the true timestamp
(some c: Client, f: LockRef | c.(s.clientRef) = s.trueQptr f && PastPtr [f, s]
&& c.(s.clientTask) in c.(s.clientInputs) )
-- data store is well-defined and correct
=> ( DataStoreDefined [s]
&& s.trueDataTimestamp = (c.(s.clientTask)).stamp
&& s.trueDataValue = (c.(s.clientTask)).val )
-- during getting, the datastore is continually defined
all c: Client |
( c.(s.clientRef) = s.trueQptr
&& c.(s.clientState) in Getting )
=> ( DataStoreDefined [s]
&& (all t: Time |
lte [(c.(s.clientTask)).stamp.tme, t] && lt [t, s.time]
=> t in s.defined ) )
-- critical get correctness
all c: Client, m: ClientGetAck |
-- precondition of lockholder's AcceptGet
( c.(s.clientState) in Getting
&& c.(s.clientRef) = s.trueQptr
&& m in c.(s.clientInputs) && m.rep = (c.(s.clientTask)).rep
&& m.stamp = (c.(s.clientTask)).stamp )
-- returned value is the true one
=> m.val = s.trueValue
-- synch getting correctness
all c: Client, m: ClientGetAck |
-- precondition of lockholder's AcceptGet
( c.(s.clientState) in SynchGetting
&& c.(s.clientRef) = s.trueQptr
&& m in c.(s.clientInputs) && m.rep = (c.(s.clientTask)).rep
&& m.stamp = (c.(s.clientTask)).stamp )
-- returned value is the true one, or a proper exceptional value
=> ( m.val = s.trueValue
|| ( s.synchFlag = True
&& ( some t: Timestamp |
EarlierTimestamp [s.trueTimestamp, t]
&& t.ref != s.trueQptr
&& t in (s.writePending + s.writeSucceeded).(m.val) queue/lte [s.trueTimestamp.ref, f] )
=> s.synchFlag = True
-- synch flag NEEDED if there are old tasks in the system that could change
-- the key value
(some m: RepPutMsg |
m in Replica.(s.repInputs + s.repTask) && PastPtr [m.stamp.ref, s]
&& EarlierTimestamp [s.trueTimestamp, m.stamp] )
=> s.synchFlag = True
-- synch flag NEEDED if there is a true data timestamp later than the true
-- timestamp and not in the current epoch
( EarlierTimestamp [s.trueTimestamp, s.trueDataTimestamp]
&& s.trueDataTimestamp.ref != s.trueQptr )
=> s.synchFlag = True
-- if the synch flag is on and there is a lockholding client, it must be
-- synching
( s.synchFlag = True
&& (some c: Client | some s.trueQptr && c.(s.clientRef) = s.trueQptr) )
=> (all c: Client |
c.(s.clientRef) = s.trueQptr
=> c.(s.clientState) in
Enqueued+MustGet+SynchGetting+MustPut4Synch+SynchPutting )
-- if the synch flag is off, then the datastore is synched or the
-- lockholding client is putting or the lockholder has just failed and
-- there has been no forced release yet
s.synchFlag = False
=> ( ( DataStoreDefined [s]
&& s.trueDataTimestamp = s.trueTimestamp
&& s.trueDataValue = s.trueValue )
|| (some c: Client |
c.(s.clientRef) = s.trueQptr
&& c.(s.clientState) in MustPut+MustPut4Synch+SynchPutting+Putting
)
|| (some s.trueQptr && (no c: Client | c.(s.clientRef) = s.trueQptr))
)

}

pred SystemStateInvariant LockHolderInvariant [s: SystemState] {
LockStoreInvariant [s]
ClientStateInvariant [s]
MusicStateInvariant [s]
DataStoreInvariant [s]
GlobalConsistencyInvariant [s]
SynchingInvariant [s]
LockHolderInvariant [s]
}
pred SystemStateInvariant2 [s: SystemState] {
LockStoreInvariant [s]
ClientStateInvariant [s]
MusicStateInvariant [s]
DataStoreInvariant [s]
GlobalConsistencyInvariant [s]
SynchingInvariant [s]
LockHolderInvariant [s]
}

fact SystemStateVariant { all disj s, s': SystemState | s.time != s'.time }

/* ========================================================================
EVENT SIGNATURES
======================================================================== */

abstract sig TraceEvent extends Event { prest, postst: SystemState }

abstract sig LockstoreEvent extends TraceEvent {
disj rep0, rep1, rep2: Replica }

abstract sig ClientEvent extends TraceEvent { client: Client }

abstract sig MusicEvent extends TraceEvent { replica: Replica }

/* ========================================================================
LOCKSTORE OPERATIONS
======================================================================== */

pred LockstoreClientFrame [s, s': SystemState] {
s'.clientTask = s.clientTask
s'.trueTimestamp = s.trueTimestamp && s'.trueValue = s.trueValue
s'.repState = s.repState && s'.repTask = s.repTask
s'.repInputs = s.repInputs
s'.writePending = s.writePending && s'.writeSucceeded = s.writeSucceeded
}

pred LockstoreOnlyFrame [s, s': SystemState] {
LockstoreClientFrame [s, s']
s'.clientInputs = s.clientInputs
s'.clientRef = s.clientRef && s'.clientState = s.clientState }

sig EnqueueClient extends LockstoreEvent {liveClient:Client,newRef:LockRef}
fact EnqueueClientEvent {
all e: EnqueueClient, s: e.prest, s': e.postst,
r0: e.rep0, r1: e.rep1, r2: e.rep2, c: e.liveClient, f: e.newRef | {
s.time = e.pre && s'.time = e.post
-- preconditions
c.(s.clientState) = Live
-- there is an unused lockRef; constraint in model only
f ! in s.trueQset
-- postconditions
-- change queue store
f = queue/min [LockRef - s.trueQset]
s'.trueQset = s.trueQset + f
s'.Qset = (Replica - (r0 + r1 + r2)) <: (s.Qset)
+ (r0 -> s'.trueQset) + (r1 -> s'.trueQset) + (r2 -> s'.trueQset)
(no s.trueQptr => s'.trueQptr = f else s'.trueQptr = s.trueQptr)
s'.Qptr = (Replica - (r0 + r1 + r2)) <: (s.Qptr)
+ (r0 -> s'.trueQptr) + (r1 -> s'.trueQptr) + (r2 -> s'.trueQptr)
s'.synchFlag = s.synchFlag
s'.trueDataSynchFlag = s.trueDataSynchFlag
-- client update
s'.clientState = s.clientState ++ (c -> Enqueued)
s'.clientRef = s.clientRef ++ (c -> f)
s'.clientInputs = s.clientInputs
LockstoreClientFrame [s, s']
} }
/* The possibility that the client requests EnqueueClient then dies before
receiving the lockRef is not modeled. It is exactly the same as if the
client received the lockRef and then died immediately after. */

sig ReleaseLock extends LockstoreEvent { lockClient: Client }
fact ReleaseLockEvent {
all e: ReleaseLock-- when the lockholder is pre-synching, the true timestamp is not in
-- current epoch
all c: Client |
( c.(s.clientRef) = s.trueQptr
&& c.(s.clientState) in
Enqueued+MustGet+SynchGetting+MustPut4Synch+SynchPutting )
=> queue/lt[(s.trueTimestamp).ref, s.trueQptr]
-- if lockholding client is in a non-putting state, there should be no put
-- messages more recent than the trueTimestamp
all c: Client, m: RepPutMsg & Replica.(s.repInputs + s.repTask) |
( c.(s.clientRef) = s.trueQptr
&& c.(s.clientState) in Critical + Getting
&& m.stamp.ref = c.(s.clientRef) )
=> EarlierOrEqualTimestamp [m.stamp, s.trueTimestamp]
-- same as above for data store writes
all c: Client, t: (s.writePending + s.writeSucceeded).Value |
( c.(s.clientRef) = s.trueQptr && t.ref = c.(s.clientRef)
&& c.(s.clientState) in Critical + Getting )
=> EarlierOrEqualTimestamp [t, s.trueTimestamp]
-- critical put correctness
all c: Client |
-- precondition of lockholder's AcceptPut
( c.(s.clientState) in SynchPutting + Putting
&& c.(s.clientRef) = s.trueQptr
&& c.(s.clientTask) in c.(s.clientInputs) )
-- data store is well-defined and correct
=> ( DataStoreDefined [s]
&& s.trueDataTimestamp = (c.(s.clientTask)).stamp
&& s.trueDataValue = (c.(s.clientTask)).val )
-- during getting, the datastore is continually defined
all c: Client |
( c.(s.clientRef) = s.trueQptr
&& c.(s.clientState) in Getting )
=> ( DataStoreDefined [s]
&& (all t: Time |
lte [(c.(s.clientTask)).stamp.tme, t] && lt [t, s.time]
=> t in s.defined ) )
-- critical get correctness
all c: Client, m: ClientGetAck |
-- precondition of lockholder's AcceptGet
( c.(s.clientState) in Getting
&& c.(s.clientRef) = s.trueQptr
&& m in c.(s.clientInputs) && m.rep = (c.(s.clientTask)).rep
&& m.stamp = (c.(s.clientTask)).stamp )
-- returned value is the true one
=> m.val = s.trueValue
-- synch getting correctness
all c: Client, m: ClientGetAck |
-- precondition of lockholder's AcceptGet
( c.(s.clientState) in SynchGetting
&& c.(s.clientRef) = s.trueQptr
&& m in c.(s.clientInputs) && m.rep = (c.(s.clientTask)).rep
&& m.stamp = (c.(s.clientTask)).stamp )
-- returned value is the true one, or a proper exceptional value
=> ( m.val = s.trueValue
|| ( s.synchFlag = True
&& ( some t: Timestamp |
EarlierTimestamp [s.trueTimestamp, t]
&& t.ref != s.trueQptr
&& t in (s.writePending + s.writeSucceeded).(m.val) ) )
)
}

pred SystemStateInvariant [s: SystemState] {
LockStoreInvariant [s]
ClientStateInvariant [s]
MusicStateInvariant [s]
DataStoreInvariant [s]
GlobalConsistencyInvariant [s]
SynchingInvariant [s]
LockHolderInvariant [s]
}
pred SystemStateInvariant2 [s: SystemState] {
LockStoreInvariant [s]
ClientStateInvariant [s]
MusicStateInvariant [s]
DataStoreInvariant [s]
GlobalConsistencyInvariant [s]
SynchingInvariant [s]
LockHolderInvariant [s]
}

fact SystemStateVariant { all disj s, s': SystemState | s.time != s'.time }

EVENT SIGNATURES

abstract sig TraceEvent extends Event { prest, postst: SystemState }

abstract sig LockstoreEvent extends TraceEvent {
disj rep0, rep1, rep2: Replica }

abstract sig ClientEvent extends TraceEvent { client: Client }

abstract sig MusicEvent extends TraceEvent { replica: Replica }


LOCKSTORE OPERATIONS

pred LockstoreClientFrame [s, s': SystemState] {
s'.clientTask = s.clientTask
s'.trueTimestamp = s.trueTimestamp && s'.trueValue = s.trueValue
s'.repState = s.repState && s'.repTask = s.repTask
s'.repInputs = s.repInputs
s'.writePending = s.writePending && s'.writeSucceeded = s.writeSucceeded
}

pred LockstoreOnlyFrame [s, s': SystemState] {
LockstoreClientFrame [s, s']
s'.clientInputs = s.clientInputs
s'.clientRef = s.clientRef && s'.clientState = s.clientState }

sig EnqueueClient extends LockstoreEvent {liveClient:Client,newRef:LockRef}
fact EnqueueClientEvent {
all e: EnqueueClient, s: e.prest, s': e.postst,
r0: e.rep0, r1: e.rep1, r2: e.rep2, c: e.liveClient, f: e.lockClient newRef | {
s.time = e.pre && s'.time = e.post
-- preconditions
c.(s.clientState) = Critical
-- postconditions
-- change queue store
let lockRef = c.(s.clientRef) |
( s'.trueQset = s.trueQset
&& s'.Qset
c.(s.clientState) = Live
-- there is an unused lockRef; constraint in model only
f ! in s.trueQset
-- postconditions
-- change queue store
f = queue/min [LockRef - s.trueQset]
s'.trueQset = s.trueQset + f
s'.Qset = (Replica - (r0 + r1 + r2)) <: (s.Qset)
+ (r0 -> s'.trueQset) + (r1 -> s'.trueQset) + (r2 -> s'.trueQset)
(no s.trueQptr => s'.trueQptr = f else s'.trueQptr = s.trueQptr)
s'.Qptr = (Replica - (r0 + r1 + r2)) <: (s.QsetQptr)
+ (r0 -> s'.trueQptr) + (r1 -> s'.trueQsettrueQptr) + (r1 -> s'.trueQset) + (r2 -> s'.trueQset)
&& (s.trueQptr = lockRef
=> s'.trueQptr = (queue/next [s.trueQptr] & s.trueQset)
else s'.trueQptr = s.trueQptr )
&& s'.Qptr = (Replica - (r0 + r1 + r2)) <: (s.Qptr)
+ (r0 -> s'.trueQptr) + (r1 -> s'.trueQptr) + (r2 -> s'.trueQptr)
&& s'.synchFlag = s.synchFlag -- if client is the lockholder
-- and client has already synched, flag will already be false
)
s'.trueDataSynchFlag = s.trueDataSynchFlag
-- client update
s'.clientState = s.clientState ++ (c -> Live)
s'.clientRef = (Client - c) <: s.clientRef
s'.clientInputs = (Client - c) <: s.clientInputs
LockstoreClientFrame [s, s']
} }

sig ForcedRelease extends LockstoreEvent { releaseRef: LockRef }
fact ForcedReleaseEvent {
all e:ForcedRelease, s:e.prest, s':e.postst,
r0: e.rep0, r1: e.rep1, r2: e.rep2, f:e.releaseRef | {
s.time = e.pre && s'.time = e.post
-- preconditions
r0.(s.repState) = Started && no r0.(s.repTask)
r0.(s.Qptr) = f
-- postconditions, lockRef may be current or past holder
s.trueQptr = f
-- case 1: preempting the lockHolder
=> s'.trueQptr = (queue/next [s.trueQptr] & s.trueQset)
-- case 2: not preempting the lockHolder
else s'.trueQptr = s.trueQptr
-- lockstore update, both cases
s'.Qset = (Replica - (r0 + r1 + r2)) <: s.Qset
+ (r0 -> s'.trueQset) + (r1 -> s'.trueQset) + (r2 -> s'.trueQset)
s'.Qptr = (Replica - (r0 + r1 + r2)) <: (s.Qptr)
+ (r0 -> s'.trueQptr) + (r1 -> s'.trueQptr) + (r2 -> s'.trueQptr)
s'.trueQset = s.trueQset
-- setting the synch flag
f = s.trueQptr
=> s'.synchFlag = True else s'.synchFlag = s.synchFlag
-- flag may already be true, if lockholder never synched
-- f may be current or past lockholder, but regardless, MUSIC replica
-- will attempt to set synch flag using lockRef + delta
f = s.trueQptr -- f is current lockholder; this will
=> s'.trueDataSynchFlag = True -- over-ride any concurrent synch
else -- f is past lockholder
(s.synchFlag = True
=> -- there was a previous forced release, no real change
s'.trueDataSynchFlag = True
else -- there was a previous release when synched,
s'.trueDataSynchFlag = True -- but false gets over-written
-- by true anyway
)
LockstoreOnlyFrame [s, s']
} }

sig PropagateLockstore extends LockstoreEvent { }
fact PropagateLockstoreEvent {
all e:PropagateLockstore, s:e.prest,s':e.postst, r0:e.rep0,r1:e.rep1 | {
s.time = e.pre && s'.time = e.post
-- precondition
PastQueue [r0.(s.Qset), r0.(s.Qptr), r1.(s.Qset), r1.(s.Qptr)]
-- postconditions
(Replica - r0) <: s'.Qptr = (Replica - r0) <: s.Qptr
r0.(s'.Qptr) = r1.(s.Qptr)
(Replica - r0) <: s'.Qset = (Replica - r0) <: s.Qset
r0.(s'.Qset) = r1.(s.Qset)
s'.trueQset = s.trueQset && s'.trueQptr = s.trueQptr
s'.synchFlag = s.synchFlag
s'.trueDataSynchFlag = s.trueDataSynchFlagr2 -> s'.trueQptr)
s'.synchFlag = s.synchFlag
s'.trueDataSynchFlag = s.trueDataSynchFlag
-- client update
s'.clientState = s.clientState ++ (c -> Enqueued)
s'.clientRef = s.clientRef ++ (c -> f)
s'.clientInputs = s.clientInputs
LockstoreClientFrame [s, s']
} }
/* The possibility that the client requests EnqueueClient then dies before
receiving the lockRef is not modeled. It is exactly the same as if the
client received the lockRef and then died immediately after. */

sig ReleaseLock extends LockstoreEvent { lockClient: Client }
fact ReleaseLockEvent {
all e: ReleaseLock, s: e.prest, s': e.postst,
r0: e.rep0, r1: e.rep1, r2: e.rep2, c: e.lockClient | {
s.time = e.pre && s'.time = e.post
-- preconditions
c.(s.clientState) = Critical
-- postconditions
-- change queue store
let lockRef = c.(s.clientRef) |
( s'.trueQset = s.trueQset
&& s'.Qset = (Replica - (r0 + r1 + r2)) <: (s.Qset)
+ (r0 -> s'.trueQset) + (r1 -> s'.trueQset) + (r2 -> s'.trueQset)
&& (s.trueQptr = lockRef
=> s'.trueQptr = (queue/next [s.trueQptr] & s.trueQset)
else s'.trueQptr = s.trueQptr )
&& s'.Qptr = (Replica - (r0 + r1 + r2)) <: (s.Qptr)
+ (r0 -> s'.trueQptr) + (r1 -> s'.trueQptr) + (r2 -> s'.trueQptr)
&& s'.synchFlag = s.synchFlag -- if client is the lockholder
-- and client has already synched, flag will already be false
)
s'.trueDataSynchFlag = s.trueDataSynchFlag
-- client update
s'.clientState = s.clientState ++ (c -> Live)
s'.clientRef = (Client - c) <: s.clientRef
s'.clientInputs = (Client - c) <: s.clientInputs
LockstoreClientFrame [s, s']
} }

sig ForcedRelease extends LockstoreEvent { releaseRef: LockRef }
fact ForcedReleaseEvent {
all e:ForcedRelease, s:e.prest, s':e.postst,
r0: e.rep0, r1: e.rep1, r2: e.rep2, f:e.releaseRef | {
s.time = e.pre && s'.time = e.post
-- preconditions
r0.(s.repState) = Started && no r0.(s.repTask)
r0.(s.Qptr) = f
-- postconditions, lockRef may be current or past holder
s.trueQptr = f
-- case 1: preempting the lockHolder
=> s'.trueQptr = (queue/next [s.trueQptr] & s.trueQset)
-- case 2: not preempting the lockHolder
else s'.trueQptr = s.trueQptr
-- lockstore update, both cases
s'.Qset = (Replica - (r0 + r1 + r2)) <: s.Qset
+ (r0 -> s'.trueQset) + (r1 -> s'.trueQset) + (r2 -> s'.trueQset)
s'.Qptr = (Replica - (r0 + r1 + r2)) <: (s.Qptr)
+ (r0 -> s'.trueQptr) + (r1 -> s'.trueQptr) + (r2 -> s'.trueQptr)
s'.trueQset = s.trueQset
-- setting the synch flag
f = s.trueQptr
=> s'.synchFlag = True else s'.synchFlag = s.synchFlag
-- flag may already be true, if lockholder never synched
-- f may be current or past lockholder, but regardless, MUSIC replica
-- will attempt to set synch flag using lockRef + delta
f = s.trueQptr -- f is current lockholder; this will
=> s'.trueDataSynchFlag = True -- over-ride any concurrent synch
else -- f is past lockholder
(s.synchFlag = True
=> -- there was a previous forced release, no real change
s'.trueDataSynchFlag = True
else -- there was a previous release when synched,
s'.trueDataSynchFlag = True -- but false gets over-written
-- by true anyway
)
LockstoreOnlyFrame [s, s']
} }

sig PropagateLockstore extends LockstoreEvent { }/* ========================================================================
CLIENT OPERATIONS
======================================================================== */
fact PropagateLockstoreEvent {
all e:PropagateLockstore, s:e.prest,s':e.postst, r0:e.rep0,r1:e.rep1 | {
s.time = e.pre && s'.time = e.post
-- precondition
PastQueue [r0.(s.Qset), r0.(s.Qptr), r1.(s.Qset), r1.(s.Qptr)]
-- postconditions
(Replica - r0) <: s'.Qptr = (Replica - r0) <: s.Qptr
r0.(s'.Qptr) = r1.(s.Qptr)
(Replica - r0) <: s'.Qset = (Replica - r0) <: s.Qset
r0.(s'.Qset) = r1.(s.Qset)
s'.trueQset = s.trueQset && s'.trueQptr = s.trueQptr
s'.synchFlag = s.synchFlag
s'.trueDataSynchFlag = s.trueDataSynchFlag
LockstoreOnlyFrame [s, s']
} }


CLIENT OPERATIONS

pred ClientMusicFrame [s, s': SystemState] {
s'.Qset = s.Qset && s'.Qptr = s.Qptr
s'.trueQset = s.trueQset && s'.trueQptr = s.trueQptr
s'.synchFlag = s.synchFlag
s'.trueDataSynchFlag = s.trueDataSynchFlag
s'.repState = s.repState && s'.repTask = s.repTask
s'.writePending = s.writePending && s'.writeSucceeded = s.writeSucceeded
}

...

sig AcceptPut extends ClientEvent { }
fact AcceptPutEvent {
all e:AcceptPut, s:e.prest, s':e.postst, c:e.client | {
s.time = e.pre && s'.time = e.post
-- preconditions, these are constraints on client's behavior
c.(s.clientState) in SynchPutting + Putting
c.(s.clientTask) in c.(s.clientInputs)
-- postconditions
s'.clientRef = s.clientRef
s'.clientState = s.clientState ++ (c -> Critical)
s'.clientTask = (Client - c) <: s.clientTask
s'.clientInputs = (Client - c) <: s.clientInputs
c.(s.clientRef) = s.trueQptr
=> ( s'.trueTimestamp = (c.(s.clientTask)).stamp
&& s'.trueValue = (, s:e.prest, s':e.postst, c:e.client | {
s.time = e.pre && s'.time = e.post
-- preconditions, these are constraints on client's behavior
c.(s.clientState) in SynchPutting + Putting
c.(s.clientTask) ).val )
else ( in c.(s.clientInputs)
-- postconditions
s'.trueTimestamp clientRef = s.trueTimestampclientRef
&& s'.trueValue clientState = s.trueValue )
(c.(s.clientRef) = s.trueQptr && c.(s.clientState) = SynchPutting)
=> s'.synchFlag = False
else s'.synchFlag = s.synchFlag
c.(s.clientState) = SynchPutting
=> (clientState ++ (c -> Critical)
s'.clientTask = (Client - c) <: s.clientTask
s'.clientInputs = (Client - c) <: s.clientInputs
c.(s.clientRef) = s.trueQptr
=> ( s'.trueDataSynchFlag = False
else-- client will do a reset, but if it is not actually the
-- lockholder, the flag's timestamp will already be later
s'.trueDataSynchFlag = s.trueDataSynchFlag
)
else s'.trueDataSynchFlag = s.trueDataSynchFlag
ClientLockstoreFrame [s, s']
} }sig PutAborts extends ClientEvent { }
fact PutAbortsEvent {
all e:PutAborts, s:e.prest, s':e.postst, c: e.client | {
s.time = e.pre trueTimestamp = (c.(s.clientTask)).stamp
&& s'.trueValue = (c.(s.clientTask)).val )
else ( s'.trueTimestamp = s.trueTimestamp
&& s'.time trueValue = es.post
-- preconditions
trueValue )
(c.(s.clientState) in Putting + SynchPutting
-- postconditions
s'.clientRef clientRef) = s.clientRef
trueQptr && c.(s.clientState) = SynchPutting)
=> s'.clientState = s.clientState ++ (c -> MustPut4Synch)synchFlag = False
else s'.clientState = synchFlag = s.synchFlag
c.(s.clientState ++ (c -> MustPut)
s'.clientTask ) = SynchPutting
=> (c.(s.clientRef) = s.clientTask trueQptr
s'.clientInputs = (Client - c) <: s.clientInputs
-- there could have been a proper nack in here;
-- there could have been an ack or nack that was lost in transit!
s'.trueTimestamp = s.trueTimestamp
s'.trueValue = s.trueValue
ClientOnlyFrame => s'.trueDataSynchFlag = False
else-- client will do a reset, but if it is not actually the
-- lockholder, the flag's timestamp will already be later
s'.trueDataSynchFlag = s.trueDataSynchFlag
)
else s'.trueDataSynchFlag = s.trueDataSynchFlag
ClientLockstoreFrame [s, s']
} }

sig PutRevealsNotHolder PutAborts extends ClientEvent { msg: ClientPutNohold }
fact PutRevealsNotHolderEvent PutAbortsEvent {
all e:PutRevealsNotHolderPutAborts, s:e.prest, s':e.postst, c: e.client ,m:e.msg | {
s.time = e.pre && s'.time = e.post
-- preconditions, these are constraints on client's behavior
c.(s.clientState) in Putting + SynchPutting
m in c.(s.clientInputs)
m.stamp = (
-- postconditions
s'.clientRef = s.clientRef
c.(s.clientTask)).stamp
-- postconditions
s'.clientRef = (Client - c) <: s.clientRef
clientState) = SynchPutting
=> s'.clientState = s.clientState ++ (c -> MustPut4Synch)
else s'.clientState = s.clientState ++ (c -> LiveMustPut)
s'.clientTask = (Client - c) <: s.clientTask
s'.clientInputs = (Client - c) <: s.clientInputs
-- there could have been a proper nack in here;
-- there could have been an ack or nack that was lost in transit!
s'.trueTimestamp = s.trueTimestamp
s'.trueValue = s.trueValue
ClientOnlyFrame [s, s']
} }

sig RequestGet PutRevealsNotHolder extends ClientEvent { requestRep: Replica,
msg: RepGetMsg, msg': ClientGetAck, ts: Timestamp ClientPutNohold }
fact RequestGetEvent PutRevealsNotHolderEvent {
all e:RequestGetPutRevealsNotHolder,s:e.prest,s':e.postst,c:e.client, r:e.requestRep,
m: e.msg,m':e.msg ', t: e.ts | {
s.time = e.pre && s'.time = e.post
-- preconditions, these are constraints on client's behavior
s behavior
c.(s.clientState) in Putting + SynchPutting
m in c.(s.clientInputs)
m.stamp = (c.(s.clientState) in Critical + MustGetclientTask)).stamp
-- postconditions
s'.clientRef = s.clientRef
(Client - c.(s.clientState) in MustGet
=> s'.clientState = s.clientState ++ (c -> SynchGetting)
else ) <: s.clientRef
s'.clientState = s.clientState ++ (c -> Getting) Live)
s'.clientInputs clientTask = (Client - c) <: s.clientInputs -- trash cleanupclientTask
s'.trueValue = s.trueValue
s'.trueTimestamp = s.trueTimestamp
m.cli = c && m'.rep = r
t.ref = c.(s.clientRef) && t.tme = s'.time
m.stamp = t && m'.stamp = t
s'.clientTask = s.clientTask ++ (c -> m')
( s'.repInputs = s.repInputs + (r -> m)
|| s'.repInputs = s.repInputs ) -- if message lost
ClientMusicFrame clientInputs = (Client - c) <: s.clientInputs
s'.trueTimestamp = s.trueTimestamp
s'.trueValue = s.trueValue
ClientOnlyFrame [s, s']
} }

sig AcceptGet RequestGet extends ClientEvent { requestRep: Replica,
msg: ClientGetAckRepGetMsg, msg': ClientPutAckClientGetAck, ts: Timestamp }
fact AcceptGetEvent RequestGetEvent {
all e: AcceptGetRequestGet, s:e.prest, s':e.postst, c:e.client, r:e.requestRep,
m: e.msg, m': e.msg', t: e.ts | {
s.time = e.pre && s'.time = e.post
-- preconditions, these are constraints on client's behavior
c.(s.clientState) in SynchGetting + Getting
m in c.(s.clientInputs)
m.rep = (c.(s.clientTask)).rep && m.stamp = (c.(s.clientTask)).stamp
-- postconditions
s'.clientRef = s.clientRef
( .time = e.pre && s'.time = e.post
-- preconditions, these are constraints on client's behavior
c.(s.clientState) in SynchGetting
=> s'.clientState = s.clientState ++ (c -> MustPut4Synch)
else Critical + MustGet
-- postconditions
s'.clientState clientRef = s.clientState ++ (c -> Critical) )clientRef
( c.(s.clientState) in SynchGetting MustGet
=> ( t.ref = c.( s.clientRef) && t.tme = s'.time
&& m'.stamp = t && m'.val = m.val
&& s'.clientTask = s.clientTask '.clientState = s.clientState ++ (c -> m') SynchGetting)
else s'.clientTask clientState = s.clientState ++ (Client c - c) <: s.clientTask > Getting) )
s'.clientInputs = (Client - c) <: s.clientInputs
s'.trueTimestamp = s.trueTimestamp-- trash cleanup
s'.trueValue = s.trueValue
ClientOnlyFrame [s, s']
} }sig GetAborts extends ClientEvent { }
fact GetAbortsEvent {
all e:GetAborts, s:e.prest, s':e.postst, c: e.client | {
s.time = e.pre && s'.time = e.post
-- preconditions
.trueTimestamp = s.trueTimestamp
m.cli = c && m'.rep = r
t.ref = c.(s.clientState) in SynchGetting + Getting
-- postconditions
s'.clientRef = s.clientRef
( c.(s.clientState) in SynchGetting
=> s'.clientState = s.clientState clientRef) && t.tme = s'.time
m.stamp = t && m'.stamp = t
s'.clientTask = s.clientTask ++ (c -> MustGetm')
else ( s'.clientState repInputs = s.clientState +repInputs + (c r -> Critical) m)
|| s'.clientTask = (Client - c) <: s.clientTask
s'.clientInputs = (Client - c) <: s.clientInputs
-- there could have been a proper nack in here;
-- there could have been an ack or nack that was lost in transit!
s'.trueTimestamp = s.trueTimestamp
s'.trueValue = s.trueValue
ClientOnlyFrame repInputs = s.repInputs ) -- if message lost
ClientMusicFrame [s, s']
} }

sig GetRevealsNotHolder AcceptGet extends ClientEvent { msg: ClientGetNohold
msg: ClientGetAck, msg': ClientPutAck, ts: Timestamp }
fact GetRevealsNotHolderEvent AcceptGetEvent {
all e: GetRevealsNotHolderAcceptGet, s: e.prest, s': e.postst, c: e.client,
m: e.msg, m': e.msg', t: e.ts | {
s.time = e.pre && s'.time = e.post
-- preconditions, these are constraints on client's behavior
c.(s.clientState) in SynchGetting + Getting
m in c.(s.clientInputs)
m in m.rep = (c.(s.clientTask)).rep && m.stamp = (c.(s.clientTask)).stamp
-- postconditions
s'.clientRef = s.clientRef
( c.(s.clientState) in SynchGetting
=> s'.clientState = s.clientState ++ (c -> MustPut4Synch)
else s'.clientState = s.clientState ++ (c -> Critical) )
( c.(s.clientInputsclientState)
m.stamp = (in SynchGetting
=> ( t.ref = c.(s.clientTask)).stamp
-- postconditions
s'.clientRef = (Client - c) <: s.clientRef
s'.clientState = s.clientState clientRef) && t.tme = s'.time
&& m'.stamp = t && m'.val = m.val
&& s'.clientTask = s.clientTask ++ (c -> Livem') )
else s'.clientTask = (Client - c) <: s.clientTask )
s'.clientInputs = (Client - c) <: s.clientInputs
s'.trueTimestamp = s.trueTimestamp
s'.trueValue = s.trueValue
ClientOnlyFrame [s, s']
} }

sig ClientFailsOrRestarts GetAborts extends ClientEvent { }
fact ClientFailsOrRestartsEvent GetAbortsEvent {
all e:ClientFailsOrRestartsGetAborts, s:e.prest, s':e.postst, c: e.client | {
s.time = e.pre && s'.time = e.post
-- preconditions
c.(s.clientState) in SynchGetting + Getting
-- postconditions
s'.clientRef = (Client - c) <: s.clientRef
( c.(s.clientState) != Deadin SynchGetting
=> s'.clientState = s.clientState ++ (c -> DeadMustGet)
else s'.clientState = s.clientState ++ (c -> LiveCritical) )
s'.clientTask = (Client - c) <: s.clientTask
s'.clientInputs = (Client - c) <: s.clientInputsClient - c) <: s.clientInputs
-- there could have been a proper nack in here;
-- there could have been an ack or nack that was lost in transit!
s'.trueTimestamp = s.trueTimestamp
s'.trueValue = s.trueValue
ClientOnlyFrame [s, s']
} }

sig GetRevealsNotHolder extends ClientEvent { msg: ClientGetNohold }/* ========================================================================
MUSIC OPERATIONS
Note that the timestamps in messages are used to uniquely identify
responses to requests.
======================================================================== */
fact GetRevealsNotHolderEvent {
all e:GetRevealsNotHolder, s:e.prest,s':e.postst,c:e.client,m:e.msg | {
s.time = e.pre && s'.time = e.post
-- preconditions, these are constraints on client's behavior
c.(s.clientState) in SynchGetting + Getting
m in c.(s.clientInputs)
m.stamp = (c.(s.clientTask)).stamp
-- postconditions
s'.clientRef = (Client - c) <: s.clientRef
s'.clientState = s.clientState ++ (c -> Live)
s'.clientTask = (Client - c) <: s.clientTask
s'.clientInputs = (Client - c) <: s.clientInputs
s'.trueTimestamp = s.trueTimestamp
s'.trueValue = s.trueValue
ClientOnlyFrame [s, s']
} }

sig ClientFailsOrRestarts extends ClientEvent { }
fact ClientFailsOrRestartsEvent {
all e:ClientFailsOrRestarts, s:e.prest, s':e.postst, c: e.client | {
s.time = e.pre && s'.time = e.post
-- preconditions
-- postconditions
s'.clientRef = (Client - c) <: s.clientRef
c.(s.clientState) != Dead
=> s'.clientState = s.clientState ++ (c -> Dead)
else s'.clientState = s.clientState ++ (c -> Live)
s'.clientTask = (Client - c) <: s.clientTask
s'.clientInputs = (Client - c) <: s.clientInputs
s'.trueTimestamp = s.trueTimestamp
s'.trueValue = s.trueValue
ClientOnlyFrame [s, s']
} }

MUSIC OPERATIONS


Note that the timestamps in messages are used to uniquely identify
responses to requests.

pred MusicClientFrame [s, s': SystemState] {
s'.Qset = s.Qset && s'.Qptr = s.Qptr
s'.trueQset = s.trueQset && s'.trueQptr = s.trueQptr
s'.clientRef = s.clientRef && s'.clientState = s.clientState
s'.clientTask = s.clientTask
s'.trueTimestamp = s.trueTimestamp
s'.trueValue = s.trueValue
s'.synchFlag = s.synchFlag
s'.trueDataSynchFlag = s.trueDataSynchFlag }

...

sig MusicFailsOrRestarts extends MusicEvent { }
fact MusicFailsOrRestartsEvent {
all e:MusicFailsOrRestarts, r:e.replica, s:e.prest, s':e.postst | {
s.time = e.pre && s'.time = e.post
-- preconditions
-- postconditions
s'.repInputs = (Replica - r) <: s.repInputs
r.(s.repState) = Started
=> s'.repState = s.repState ++ (r -> Failed)
else s'.repState = s.repState ++ (r -> Started)
s'.repTask = (Replica - r) <: s.repTask
s'.writePending = s.writePending
s'.writeSucceeded = s.writeSucceeded
MusicOnlyFrame [s, s']
} }/* ========================================================================


VALIDATION AND VERIFICATION

[x SystemState, x+2 Time, x+1 Event]

...