The following algorithm has one global variable `x`

that is
shared by `N`

processes. Each process increments `x`

in two steps. It first reads the value of `x`

and stores
`x+1`

in its local variable `y`

. Then it writes the
value of `y`

to `x`

. The goal is to check that, when
the algorithm terminates, the value of `x`

has been incremented
by `N`

. This is stated by the property `Correctness`

.

EXTENDS Naturals, TLC CONSTANTS N (* Number of processes *) (* PlusCal options (-termination) *) (* --algorithm Atomicity { variables x = 0; process (P \in 1..N) variables y = 0; { l0: y := x+1; l1: x := y } } *) \* BEGIN TRANSLATION VARIABLES x, pc, y vars == << x, pc, y >> ProcSet == (1..N) Init == (* Global variables *) /\ x = 0 (* Process P *) /\ y = [self \in 1..N |-> 0] /\ pc = [self \in ProcSet |-> "l0"] l0(self) == /\ pc[self] = "l0" /\ y' = [y EXCEPT ![self] = x+1] /\ pc' = [pc EXCEPT ![self] = "l1"] /\ x' = x l1(self) == /\ pc[self] = "l1" /\ x' = y[self] /\ pc' = [pc EXCEPT ![self] = "Done"] /\ y' = y P(self) == l0(self) \/ l1(self) Next == (\E self \in 1..N: P(self)) \/ (* Disjunct to prevent deadlock on termination *) ((\A self \in ProcSet: pc[self] = "Done") /\ UNCHANGED vars) Spec == /\ Init /\ [][Next]_vars /\ \A self \in 1..N : WF_vars(P(self)) Termination == <>(\A self \in ProcSet: pc[self] = "Done") \* END TRANSLATION AllDone == \A self \in ProcSet: pc[self] = "Done" Correctness == [](AllDone => x=N)

- In the translation of the algorithm above, how are
`pc`

and`y`

defined? What is the variable`self`

? What is the meaning of the expression`y' = [y EXCEPT ![self] = x+1]`

? - How is defined the semantics of a multi-process algorithm? Give the semantics of this algorithm as a transition system.
- The property
`Correctness`

is not satisfied by the algorithm above. Explain why it is incorrect using the counter-example provided by the TLA toolbox. - Is incrementation of
`x`

atomic or not? Justify. - How are atomic and non-atomic steps modeled in a transition system? How is it specified in PlusCal and TLA+?
- Modify the labeling of the algorithm so that the increment of
`x`

becomes atomic. Give the semantics of this algorithm as a transition system. Prove that this new algorithm is correct using the TLA toolbox.

Labels hence define which blocs of statements are executed in an atomic
way. Labeling is not mandatory, in particular it is useless for sequential
programs. However, labeling is a key to the modeling of concurrent and
distributed algorithms where correctness heavily depends on which
statements can be executed in an atomic way. PlusCal puts restrictions on
labels in order to let the translation in TLA+ be as close as possible to
the algorithm. These constraints are described in section 3.7 of the
PlusCal user manual
(available at` ~herbrete/public/TLA/c-manual.pdf`

).

Adding `-label`

to the PlusCal options turns on the automatic
labeling of the algorithm by the translator. This is the default behavior
in the case of a uniprocess algorithm. The default labeling consists in
adding a minimal set of labels to guarantee the constraints mentioned
above. It thus results in maximizing the size of the atomic blocs of
statements.

We consider a program as above where a process needs to read and write a
variable in an atomic step, in a setting where there is only non-atomic
read/write operations. In such a situation semaphores can be used to make
blocks of non-atomic statements atomic. This is known as the problem of
mutual exclusion in concurrent algorithms. At any time, at most one process
is granted access to a shared resource (in the previous example, the global
variable `x`

). We use model-checking to design an algorithm that
solves this problem.

The first step in the design process is to write a formal specification of the expected algorithm.

- Write in natural language the properties that the algorithm above must satisfy in order to solve the mutual exclusion problem.
- What are the atomic propositions needed to express these properties? Formalize these specifications in LTL, and then as TLA+ specifications.

A classical way to implement synchronizations (in particular mutual
exclusion) is to use semaphores
(see
Wikipedia page). A semaphore `s`

is a shared integer
variable that can be
manipulated using two operations: `P(s)`

and `V(s)`

. The
operation `P(s)`

checks if the value
of `s`

is greater than zero, and decrements `s`

, in
a single atomic step. It is blocking if the value of `s`

is
smaller-than or equal-to zero. The operation `V(s)`

increments
`s`

. When `s`

gets back a positive value one process
waiting on operation `P(s)`

is unblocked (if any). The other
processes waiting on operation `P(s)`

remain blocked.

- The algorithm below shows how to use semaphores to ensure mutual
exclusion (where the access to the shared resource has been abstracted
away as the
`cs`

label and instruction`skip`

). Implement macros`P(s)`

and`V(s)`

as specified above and initialize variable`s`

properly. - Check that the algorithm is correct for the specification using the TLC model-checker.

EXTENDS Naturals, TLC CONSTANTS N (* Number of processes *) (* PlusCal options (-sf) *) (* --algorithm Semaphore { variables s = ...; (* Shared semaphore *) macro P(sem) { ... } macro V(sem) { ... } process (P \in 1..N) { loop: while (TRUE) { lock: P(s); cs: skip; (* In the critical section *) unlock: V(s) } } } *) \* BEGIN TRANSLATION \* END TRANSLATION