Distributed Systems Playground Open the Playground

The Upon language#

Upon is the language you write algorithms in. It follows the event-driven notation of Cachin, Guerraoui and Rodrigues closely enough that most textbook algorithms can be copied line by line, and it is strict enough to be checked and executed.

This page is the complete reference. If you have never written Upon, start with Getting started and come back here when you need a detail. The full grammar is in section 5.2 of the specification.

A first program#

interface Greeting
  request Greet(name)
  indication Greeted(from, name)
end

algorithm Hello
  implements Greeting as g
  uses Net as net

  upon event ⟨g, Greet | name⟩ do
    forall q in neighbors do
      trigger ⟨net, Send | q, [HELLO, name]⟩
    end
  end

  upon event ⟨net, Deliver | p, [HELLO, name]⟩ do
    trigger ⟨g, Greeted | p, name⟩
  end
end

Read it from the top:

With the input line 0ms 1 Greet | "world", the neighbors of p1 report Greeted | 1, "world".

Lexical rules#

Keywords:

interface request indication algorithm implements as uses via params state stable
upon event where condition exists do end function return call
trigger if then elif else forall in while starttimer canceltimer assert log skip
and or not notin union inter minus subseteq true false nil

Interfaces#

interface Name
  request   EventName(param, param)
  indication EventName(param)
end

Two interfaces, Net and Rounds, are predefined (see Provided modules). You cannot redefine or implement them.

Algorithms#

algorithm Name
  implements Interface as alias
  uses OtherInterface as other          // zero or more
  uses PerfectLinks as pl via AckLinks  // "via" picks the implementation
  params
    f := 1                              // evaluated once per process
  state
    seen := ∅                           // one copy per process
    stable total := 0                   // survives a crash and recovery
  function helper(x) … end              // zero or more, anywhere after state
  upon event ⟨alias, Init⟩ do … end     // handlers
end

implements names the interface this algorithm provides and the local name under which it receives requests and emits indications.

uses names an interface this algorithm relies on and the local name to talk to it. Each uses creates its own instance of the chosen algorithm.

params are constants. Their initial expressions can use built-in names and earlier parameters. They cannot be assigned.

state variables belong to each process. Their initial expressions can use built-in names and parameters. stable variables keep their value across a crash and a recovery; the others are reset (see Faults).

Every name in params and state must differ from the built-in names and from each other.

Handlers#

A handler runs when its trigger matches. There are three kinds.

Event handlers#

upon event ⟨instance, Event | pattern, pattern⟩ do … end
upon event ⟨instance, Event | pattern⟩ where condition do … end

instance is one of:

When an event arrives, handlers are tried in the order they appear in the code. The first one whose patterns match and whose where condition is true runs; the others are skipped. If none matches, the event is ignored and a warning is logged once. Init, Recovery, RoundStart and RoundEnd without a handler are ignored silently.

A common idiom uses two handlers with opposite conditions:

upon event ⟨net, Deliver | p, [DATA, m]⟩ where m ∉ seen do … end
upon event ⟨net, Deliver | p, [DATA, m]⟩ where m ∈ seen do skip end

Condition handlers#

upon condition expression do … end

The handler runs whenever the expression is true, checked after every step of the process. Its body must make the condition false, directly or indirectly; a condition that stays true forever stops the run with an error.

Exists handlers#

upon exists x in collection where condition do … end

The handler runs for the first element of the collection, in sorted order, that satisfies the condition, with that element bound to x. It is checked after every step, like a condition handler, and is the natural way to write "when some pending message can be delivered":

upon exists e in pending where e[1] = next do
  pending := pending \ {e}
  next := next + 1
  trigger ⟨app, Deliver | e[0], e[2]⟩
end

When several condition or exists handlers of a module are enabled, they take turns.

Patterns#

Patterns appear in event handlers, one per event parameter.

PatternMatches
x (a new name)anything, and binds it to x
x (a name already known)a value equal to the variable, parameter or built-in x
HELLO (an atom)the atom HELLO
42, -1, "text", true, nilthat exact value
_anything, without binding
[A, b, _]a tuple of exactly three elements matching the three patterns

The same new name used twice must match equal values: ⟨net, Deliver | p, [ECHO, p]⟩ only matches when the message echoes its own sender.

In ⟨timer, Timeout | t⟩, t is the name of a timer, not a variable (see Timers).

Statements#

StatementEffect
x := eassigns a state variable, or creates a local variable
m[k] := e, t[i] := eupdates one entry of a map or tuple
trigger ⟨inst, Event \| a, b⟩sends an event down or up
if c then … elif c then … else … endconditional
forall x in c do … endloop over a collection
forall x in c where cond do … endloop over the elements that satisfy cond
while c do … endloop while c holds
starttimer(t, d)starts or restarts timer t with duration d
canceltimer(t)cancels timer t
call f(a, b)calls one of your functions and ignores the result
return e, returnleaves a function (only inside functions)
assert c, "message"logs a failed assertion if c is false
log a, b, cwrites a line to the event log
skipdoes nothing

Local variables. Assigning a name that is not a state variable creates a local variable, visible until the end of the handler or function. Reading it before the assignment is a runtime error. Loop variables and pattern variables are local too.

Triggers are asynchronous within the process. trigger puts the event in a queue; it is handled after the current handler finishes, within the same step. Code after a trigger does not see the effects of the event.

forall evaluates the collection once, before the loop starts. Assigning the collection inside the loop does not change the iteration.

assert does not stop the run unless Stop at the first failed assertion is checked in the Scenario tab.

log prints strings as they are and other values in their written form, separated by spaces.

Expressions#

From lowest to highest precedence:

LevelOperators
oror, ∨
andand, ∧
notnot, ¬
comparison=, ≠, <, ≤, >, ≥, ∈, ∉, ⊆
set∪, ∩, \
additive+, -
multiplicative*, /, %
unary-, #
postfixx[i], f(args)

Comparisons do not chain: write a < b and b < c, not a < b < c. To compare the result of a membership test, use parentheses: (x ∈ S) = false.

and and or are short-circuit and require booleans. if, while, where and conditions also require booleans; there is no truthiness.

OperatorWorks on
+numbers; strings (with anything else converted to text); two tuples (concatenation)
-, *, %numbers
/numbers; between two integers the result is truncated
∪, ∩, \, ⊆sets
∈, ∉an element of a set, of a tuple, or a key of a map
#xthe size of a set, map, tuple or string
=, ≠any two values, compared by content
<, ≤, >, ≥any two values, with the order described in Values

Literals:

FormValue
[a, b, c]a tuple
{a, b}, ∅, {}a set
{x in S where condition}the elements of S that satisfy the condition
map()an empty map

Values#

KindExamplesNotes
nilnilthe value of a missing map entry
booleantrue, false
number3, 0.5, 50msdurations are numbers of microseconds
string"hello"
atomHELLO
tuple[DATA, 3, "x"]indexed from 0
set{1, 2, 3}no duplicates, kept sorted
mapm[k]keys and values of any kind

All values are immutable. m[k] := v builds a new map and assigns it to m, so a copy taken earlier (old := m) does not change.

Values of different kinds are ordered as in the table: nil before booleans, booleans before numbers, and so on. Within a kind the order is the natural one; tuples, sets and maps compare element by element. This total order is what makes min, max, sort, choose and set iteration deterministic.

Process identifiers are numbers: self is 3 on process p3.

Built-in names#

NameValueAvailable
selfthis process's numberalways
Π, Procsthe set of all processesalways
Nthe number of processesalways
neighborsprocesses this process has an outgoing link toalways
roundthe current roundsynchronous rounds only
DELTA, PHI, RHOthe synchrony constantsonly when known in the assumed model

Π contains every process, including those without a link to this one. Sending to a process that is not a neighbor drops the message.

Built-in functions#

Collections

FunctionResultErrors
size(c)number of elements of a set, map, tuple or string
min(c), max(c)smallest or largest element of a set or tuple, or key of a mapempty collection
choose(c)the smallest element: a deterministic choiceempty collection
pick(c)a random element, from the process's seeded streamempty collection
keys(m), values(m)set of keys, set of valuesnot a map
get(m, k, d)m[k], or d if missing; also works on tuples by index, and on nil
remove(c, x)the set or map without x, or the tuple without the first x
toset(c)the elements of a tuple or set, or the keys of a map, as a set
sort(c)the elements as a sorted tuple
argmin(m), argmax(m)the key with the smallest or largest value; ties go to the smallest keyempty map

Sequences (a set is treated as its sorted tuple)

FunctionResultErrors
head(t), last(t)first or last elementempty
tail(t)all but the first element
reverse(t)elements in reverse order
slice(t, a, b)elements from index a up to, not including, b
append(t, x)the tuple with x added at the endnot a tuple
range(a, b)the tuple [a, a+1, …, b-1]more than 100,000 elements

Numbers

FunctionResultErrors
sum(c), mean(c)sum or average of the numbers in a set or tuple, or of the values of a mapnon-numbers; mean of nothing
abs, floor, ceil, roundas usual
sqrt(x)square rootnegative x
ln(x), exp(x), pow(x, y)natural logarithm, exponential, powerln of a number ≤ 0
random(a, b)a random integer between a and b, both included

Other

FunctionResult
now()the process's local clock, in microseconds (not in lockstep rounds)
str(v)the written form of a value

The logarithm is ln, because log is the statement that writes to the event log.

Functions#

interface Stats
  request Add(x)
  indication Summary(count, average, spread)
end

algorithm Collector
  implements Stats as s
  uses Net as net
  state
    samples := []

  function average()
    return mean(samples)
  end

  // standard deviation; mean() divides exactly, while "/" between
  // two whole numbers would truncate
  function spread()
    a := average()
    squares := []
    forall x in samples do
      squares := append(squares, pow(x - a, 2))
    end
    return sqrt(mean(squares))
  end

  function record(x)
    samples := append(samples, x)
    trigger ⟨s, Summary | #samples, average(), spread()⟩
  end

  upon event ⟨s, Add | x⟩ do
    call record(x)
  end
end

A function:

Use a function in an expression, x := average(), or as a statement with call record(x). call is only for your own functions; built-in functions are used in expressions.

A function name cannot be the name of a built-in function.

Timers#

starttimer(retry, 200ms)       // start, or restart, the timer named "retry"
canceltimer(retry)             // cancel it
upon event ⟨timer, Timeout | retry⟩ do … end

The checker warns about a Timeout handler for a timer the module never starts.

Writing help#

The editor knows the program you are writing, so it can offer what exists rather than what might:

Properties#

A property is a global invariant: one boolean expression over the state of every process, checked by the engine. It lives at the top level of the program, next to interfaces and algorithms.

interface Consensus
  request Propose(v)
  indication Decide(v)
end

algorithm Simple
  implements Consensus as c
  uses Net as net
  state
    proposal := nil
    decision := nil

  upon event ⟨c, Propose | v⟩ do
    proposal := v
    decision := v
    trigger ⟨c, Decide | v⟩
  end
end

property Agreement always
  #toset(values(defined(decision))) ≤ 1
end

property Validity always
  #{p in keys(defined(decision)) where decision[p] ≠ proposal[p]} = 0
end

property Termination eventually
  keys(defined(decision)) = correct
end

Inside a property, a state variable is a map from process to its value on that process, taken from the main algorithm. decision above is {1 ↦ 7, 2 ↦ nil, 3 ↦ 7}, so values(decision) are the values across the system and defined(decision) keeps only the processes where the variable is not nil. Everything else is the ordinary expression language.

KindMeaningWhen it is checked
alwayssafety: the expression must be true at all timesafter every step; the first moment it turns false is reported
eventuallyliveness: the expression must become true at some pointafter every step; the first moment it turns true is reported, and the property is satisfied from then on

Besides state variables, a property can use:

NameValue
Π, Procsthe set of all processes
Nthe number of processes
crashedthe processes that are down right now
upthe processes that are running right now
correctthe processes that never crashed in this run
tthe current time, in microseconds
defined(m)the map m without the entries whose value is nil

A property cannot use self, neighbors, round, DELTA, PHI, RHO, now(), random or pick: it looks at the whole system from outside, at one moment, and must not depend on chance. Only built-in functions are available; the functions of an algorithm are not.

What the engine reports. Each property ends the run as held or broken, with the instant and the process whose step made it fail. Broken properties appear in the event log under the Properties filter, are counted next to the other chips, and are listed by the command line tool. Setting haltOnProperty in the scenario stops the run at the first violation.

Limits worth knowing.

Provided modules#

Net#

interface Net
  request Send(q, m)
  indication Deliver(p, m)
end

Send(q, m) sends m to process q over the link between them. Deliver(p, m) reports a message m from p. The network behaves as the actual model says: it can lose, duplicate, delay and reorder messages. See The timing model.

Net is available with every synchrony model except synchronous rounds.

Rounds#

interface Rounds
  request Send(q, m)
  indication RoundStart(r)
  indication Deliver(p, m)
  indication RoundEnd(r)
end

In each round, a module that uses Rounds receives RoundStart(r), then the messages of that round, then RoundEnd(r). Send in RoundStart, collect in Deliver, decide in RoundEnd. The built-in round holds the current round number.

Rounds is available only with synchronous rounds. How rounds are executed is described in The timing model.

timer#

timer is not declared with uses: every module has it. It produces only Timeout(t).

Composing modules#

A program can contain several algorithms. The Main algorithm selector in the Code tab chooses the top one; its interface defines the inputs you can send and the outputs you see.

For each uses X as a, the playground creates an instance of an algorithm that implements X:

Instances form a tree, one per process. Every uses creates its own instance, so two modules that use PerfectLinks get two independent copies with separate state.

A module talks only to its direct neighbors in the tree: it sends requests to the modules it uses and indications to the module that uses it. It never sees their state.

interface PerfectLinks
  request Send(q, m)
  indication Deliver(p, m)
end

interface Echo
  request Ping(q)
  indication Pong(p)
end

algorithm DirectLinks
  implements PerfectLinks as pl
  uses Net as net
  upon event ⟨pl, Send | q, m⟩ do
    trigger ⟨net, Send | q, m⟩
  end
  upon event ⟨net, Deliver | p, m⟩ do
    trigger ⟨pl, Deliver | p, m⟩
  end
end

algorithm Pinger
  implements Echo as e
  uses PerfectLinks as pl via DirectLinks
  upon event ⟨e, Ping | q⟩ do
    trigger ⟨pl, Send | q, [PING]⟩
  end
  upon event ⟨pl, Deliver | p, [PING]⟩ do
    trigger ⟨pl, Send | p, [PONG]⟩
  end
  upon event ⟨pl, Deliver | p, [PONG]⟩ do
    trigger ⟨e, Pong | p⟩
  end
end

Ready-made modules for links and broadcast are in the module library.

What the checker verifies#

The checker runs while you type and again before every run. Errors stop the run; warnings do not.

Errors

Warnings

Runtime errors#

Some problems can only be found while running. The run stops, keeps everything up to that point, and shows the line in the editor.

ASCII syntax#

Every symbol has a plain-text spelling, and you can mix both.

UnicodeASCIIUnicodeASCII
⟨ ⟩< >∈ ∉in notin
∪ ∩ \union inter minus∅{}
≠ ≤ ≥!= <= >=∧ ∨ ¬and or not
ΠProcs⊆subseteq
←:=∀forall

With ASCII angle brackets, > inside a trigger would close the event. Wrap comparisons in parentheses there:

trigger <app, Result | (x > 3)>

The symbol buttons above the editor insert the Unicode characters at the cursor.

Common mistakes#

A handler never runs. Handlers are tried in order and only the first match runs. An earlier, more general handler may be catching the event. Put the specific one first, or add where clauses.

"Variable is not declared" for a message tag. Atoms need at least two capital letters: A is a variable name, AB is an atom.

A tag matches a variable instead. If a state variable is called DATA, the pattern [DATA, m] compares with its value instead of the atom. Use lower-case names for variables.

5 / 2 gives 2. Division between two whole numbers truncates, and a value such as 4.0 counts as whole. Use mean for averages, or multiply first (100 * a / b) when you need a percentage.

Nothing arrives. The recipient may not be a neighbor; check the topology, or look for no link in the event log.

A broadcast reaches only some processes. Library broadcast modules send to every process in Π, which requires a complete graph.

A guard stops the run. An upon condition handler must make its condition false. If it only sends a message, it will fire again at once.

An algorithm works but depends on the order of a set. Sets are iterated in sorted order and choose is deterministic here. Real systems give no such guarantee; try pick to see whether the algorithm still works.