Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Example: Two-layered Cache #1549

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
185 changes: 185 additions & 0 deletions examples/tools/twolayeredcache
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this would fit into one of the existing folders. I think it could be under classic/distributed, WDYT?

Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
module twolayeredcache {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order to get some highlighting on GitHub while we don't have official support, we add this comment at the beginning which tells it to consider this as a bluespec file, which is the best matching highlighting for Quint at the moment.

Also, adding underscores to the module name will make it easier to read (which we should also do for the file name).

Suggested change
module twolayeredcache {
// -*- mode: Bluespec; -*-
module two_layered_cache {

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, if you can add a header comment here explaining the context and/or adding links about it, and include you as an author, it would be great. Like this one:

/**
* Quint spefication for EWD840: an algorithm for distributed termination
* detection on a ring, due to Dijkstra, published as EWD 840: Derivation
* of a termination detection algorithm for distributed computations (with
* W.H.J.Feijen and A.J.M. van Gasteren).
*
* Igor Konnov and Gabriela Moreira, Informal Systems, 2022-2023
*/

//**********************************************************
// TYPE DEFINITIONS
//**********************************************************
type CacheLayer = Set[int]
type ClientPID = int

// tpe: "read" or "write"
type HistoryEntry = { tpe: str, value: int}

//**********************************************************
// CONSTANTS
//**********************************************************
pure val DefaultExpireDuration = 3
pure val ClientProcesses: Set[ClientPID] = 1.to(10)
pure val MaxVal = 1000000
pure val Expired = -99
pure val NotFound = -98
Comment on lines +14 to +18
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of these are not used. Shall we remove them?

Suggested change
pure val DefaultExpireDuration = 3
pure val ClientProcesses: Set[ClientPID] = 1.to(10)
pure val MaxVal = 1000000
pure val Expired = -99
pure val NotFound = -98
pure val ClientProcesses: Set[ClientPID] = 1.to(10)
pure val NotFound = -98


//**********************************************************
// STATE MACHINE
// State-dependent definitions and actions
//**********************************************************

var l1: CacheLayer
var l2: CacheLayer
var num: int

// Global log of system events. Use to specify correctness
// properties below.
var history: List[HistoryEntry]

//**********************************************************
// FUNCTIONAL LAYER
// Values and functions that are state-independent
//**********************************************************

action writeLayer(layer: CacheLayer, v: int): bool = all {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is part of your functional layer section, but it is an action and interacts with state, so I think it should belong in either helpers or actions.

layer' = layer.union(Set(v))
}
Comment on lines +38 to +40
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Passing the state variable "as reference" here and assigning to a parameter is not valid Quint. I noticed Apalache is working with that, I guess because it's made for TLA+ and TLA+ allows arbitrary usage of the prime operator ('). In Quint, you can only use it directly with state variables, so you'd actually have to use something like this:

Suggested change
action writeLayer(layer: CacheLayer, v: int): bool = all {
layer' = layer.union(Set(v))
}
action writeL1(v: int): bool = all {
l1' = l1.union(Set(v))
}
action writeL2(v: int): bool = all {
l2' = l2.union(Set(v))
}

Using the Quint simulator (quint run) with the current definition results in a Runtime Error.


//**********************************************************
// HELPERS
// Functions for convenience
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor thing, but we refer to Quint definitions as operators

Suggested change
// Functions for convenience
// Operators for convenience

//**********************************************************

//**********************************************************
// ACTIONS
//**********************************************************

action writeCache(pid: ClientPID, v: int): bool = all {
writeLayer(l1, v),
writeLayer(l2, v),
history' = history.append({
tpe: "write",
value: v,
}),
}

action handleNotFound: bool = all {
l1' = l1,
l2' = l2,
history' = history.append({
tpe: "read",
value: NotFound,
}),
}

action handleL1Found(v: int): bool = all {
l1' = l1,
l2' = l2,
history' = history.append({
tpe: "read",
value: v,
}),
}

action handleL2Found(v: int): bool = all {
writeLayer(l1, v),
l2' = l2,
history' = history.append({
tpe: "read",
value: v,
}),
}

action handleFound(v: int): bool = {
if(l1.contains(v)) {
handleL1Found(v)
} else {
handleL2Found(v)
}
}

// If this val doesn't exist in L1, read from L2.
// If val exists in L2, write back to L1 then return.
// Otherwise, not found (false).
action readCache(pid: ClientPID, v: int): bool = {
if (not(l1.contains(v)) and not(l2.contains(v))) {
handleNotFound
} else {
handleFound(v)
}
Comment on lines +99 to +103
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is easier to read and follows the comment more closely

Suggested change
if (not(l1.contains(v)) and not(l2.contains(v))) {
handleNotFound
} else {
handleFound(v)
}
if (l1.contains(v) or l2.contains(v)) {
handleFound(v)
} else {
handleNotFound
}

}

action clientProc = all {
num' = num + 1,
nondet pid = ClientProcesses.oneOf()
nondet readVal = 1.to(num).oneOf()
any {
writeCache(pid, num),
readCache(pid, readVal),
},
}

action init = all {
num' = 0,
l1' = Set(),
l2' = Set(),
history' = [],
}

action stutter = all {
num' = num,
l1' = l1,
l2' = l2,
history' = history,
}

action step = any {
clientProc,
stutter,
}

//**********************************************************
// CORRECTNESS
// 1. Safety Properties / Invariants
//**********************************************************

// Read the latest write
val readAfterWrite: bool = {
val idx = history.indices()
idx.forall(i => {
idx.forall(j => {
i < j
and history[i].tpe == "write"
and history[j].tpe == "read"
implies history[i].value <= history[j].value
})
})
}

// Later write must contain a greater value
val writeAfterWrite: bool = {
val idx = history.indices()
idx.forall(i => {
idx.forall(j => {
i < j
and history[i].tpe == "write"
and history[j].tpe == "write"
implies history[i].value < history[j].value
})
})
}

val strongConsistency: bool = {
readAfterWrite
and writeAfterWrite
}

val safety: bool = {
strongConsistency
}

//**********************************************************
// CORRECTNESS
// 2. Liveness Properties / Temporal
//**********************************************************


//**********************************************************
// QUICK TESTS
//**********************************************************
// run initAndStepTest = init.then(step)
}