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 1..N: 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.
- Is incrementation of
`x`

atomic or not? Justify. - The property
`Correctness`is not satisfied by the algorithm above. Explain why it is incorrect using the counter-example provided by the TLA toolbox. - 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

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 again the algorithm above, and we assume that it is not possible to read and write the global variable `x`

in a single atomic step. 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 (here the global variable `x`

). We use model-checking to design an algorithm that solves
this problem. We start from a design that we prove incorrect. Then, we use the counter-example provided by the TLC model-checker to improve
the design, until we reach an algorithm that matches the specification.

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.
- Formalize these requirements 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 semaphore `s` has a value
greater than zero, then it decreases the value of `s`. If the
value of `s` is instead less or equal to 0, the calling process is
blocked until `s` has a positive value. The operation `V(s)`
increments `s`. When s
gets back a positive value only one process waiting on operation P(s)
is unblocked. The other processes waiting on operation P(s)
remain blocked.

- The algorithm below shows how to use semaphores to ensure mutual
exclusion in the previous algorithm
`. 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 MutexSemaphore {

variables x = 0;

variables s; (* Shared semaphore *)

macro P(sem) { ... }

macro V(sem) { ... }

process (P \in 1..N)

variables y = 0;

{

loop: while (TRUE) { nocs: either goto nocs (* Doing something else *) or skip; lock: P(s); l0: y := x+1; l1: x := y; unlock: V(s); } } } *) \* BEGIN TRANSLATION

\* END TRANSLATION

(* Add the requirements as TLA+ specifications *)