Here, http://spinroot.com/spin/Man/Manual.html, it is written that:
In Promela there is also another way to avoid the test and set
problem: atomic sequences. By prefixing a sequence of statements
enclosed in curly braces with the keyword atomic the user can indicate
that the sequence is to be executed as one indivisible unit,
non-interleaved with any other processes. It causes a run-time error
if any statement, other than the first statement, blocks in an atomic
sequence. This is how we can use atomic sequences to protect the
concurrent access to the global variable state in the earlier example.
And here, http://spinroot.com/spin/Man/atomic.html, it is written that:
If any statement within the atomic sequence blocks, atomicity is lost
If any statement within the atomic sequence blocks, atomicity is lost,
and other processes are then allowed to start executing statements.
When the blocked statement becomes executable again, the execution of
the atomic sequence can be resumed at any time, but not necessarily
immediately. Before the process can resume the atomic execution of the
remainder of the sequence, the process must first compete with all
other active processes in the system to regain control, that is, it
must first be scheduled for execution.
So, what is the true? From the first citation we can learn that it is not allowed to block in atomic ( not the first statement)
From the second citation we learn that it is ok to block in atomic. You just lose the atomicity and that is it.
Contradictory Documentation:
My guess is that the contradiction is merely the result of parts of the documentation not being updated to reflect changes that occurred within Spin over the years.
In fact, in the release notes of Spin v. 2.0 we can find the following piece of text (emphasis is mine):
2.3.1 Blocking Atomic Sequences
Until now it was considered an error if an atomic sequence contained
any statement that could block the execution of the sequence. This
caused much confusion and complicates modeling needlessly. Starting
with Spin version 2, it is legal for an atomic sequence to block. If a
process inside an atomic sequence blocks, control shifts
non-deterministically to another process. If the statement later
becomes executable, control can return to the process and the atomic
execution of the remainder of the sequence resumed.
This change in semantics makes it relatively easy to model, for
instance, co-routines where control does not pass from one process to
another until and unless the running process blocks.
Atomic Statements in Promela:
In the current version of Promela/Spin there exist two atomic sequences:
atomic: from the docs, emphasis is mine:
DESCRIPTION
If a sequence of statements is enclosed in parentheses and prefixed with the keyword atomic, this indicates that the sequence is to be executed as one indivisible unit, non-interleaved with other processes. In the interleaving of process executions, no other process can execute statements from the moment that the first statement of an atomic sequence is executed until the last one has completed. The sequence can contain arbitrary Promela statements, and may be non-deterministic.
If any statement within the atomic sequence blocks, atomicity is lost, and other processes are then allowed to start executing statements. When the blocked statement becomes executable again, the execution of the atomic sequence can be resumed at any time, but not necessarily immediately. Before the process can resume the atomic execution of the remainder of the sequence, the process must first compete with all other active processes in the system to regain control, that is, it must first be scheduled for execution.
[...]
d_step: from the docs, emphasis is mine:
DESCRIPTION
A d_step sequence is executed as if it were one single indivisible statement. It is comparable to an atomic sequence, but it differs from such sequences on the following three points:
- No goto jumps into or out of a d_step sequence are allowed.
- The sequence is executed deterministically. If non-determinism is present, it is resolved in a fixed and deterministic way, for instance, by always selecting the first true guard in every selection and repetition structure.
- It is an error if the execution of any statement inside the sequence can block. This means, for instance, that in most cases send and receive statements cannot be used inside d_step sequences.
[...]
Of course, one can actually test this behaviour with a simple Promela example.
TEST 1: loss of atomicity with atomic {}
Take the following Promela Model, in which two processes pippo
and pluto
execute an atomic {}
sequence of instructions up to 10
times. Each process saves its unique _pid
inside a global variable p
when it starts executing the atomic sequence, and then checks a flag
variable:
- if
flag
is true
, pippo
can execute but pluto
cannot, therefore pluto
should temporarily loose atomicity (in some execution trace)
- if
flag
is false
, pluto
can execute but pippo
cannot, therefore pippo
should temporarily loose atomicity (in some execution trace)
We check the latest case by adding an assert(p == _pid)
instruction at the end of the atomic sequence in pippo
. If the condition is not violated, then it means that there is no execution in which pippo
looses atomicity from within the atomic sequence and pluto
starts executing instead. Otherwise, we proved that the description in the linked documentation for atomic {}
is accurate.
bool flag;
pid p;
active proctype pippo ()
{
byte i;
do
:: i < 10 ->
atomic {
true ->
p = _pid;
flag; /* executable only if flag is true */
printf("pippo unblocked\n");
flag = !flag;
assert(p == _pid);
};
i++;
:: else -> break;
od;
};
active proctype pluto ()
{
byte i;
do
:: i < 10 ->
atomic {
true ->
p = _pid;
end:
!flag; /* executable only if flag is false */
printf("pluto unblocked\n");
flag = !flag;
};
i++;
:: else -> break;
od;
};
If we perform formal verification with Spin, it finds an execution trace that violates the property:
~$ spin -search -bfs test.pml # -bfs: breadth-first-search, results in a
# shorter counter-example
pan:1: assertion violated (p==_pid) (at depth 6)
pan: wrote test.pml.trail
(Spin Version 6.4.3 -- 16 December 2014)
Warning: Search not completed
+ Breadth-First Search
+ Partial Order Reduction
Full statespace search for:
never claim - (none specified)
assertion violations +
cycle checks - (disabled by -DSAFETY)
invalid end states +
State-vector 20 byte, depth reached 6, errors: 1
15 states, stored
10 nominal states (stored-atomic)
0 states, matched
15 transitions (= stored+matched)
5 atomic steps
hash conflicts: 0 (resolved)
Stats on memory usage (in Megabytes):
0.001 equivalent memory usage for states (stored*(State-vector + overhead))
0.290 actual memory usage for states
128.000 memory used for hash table (-w24)
128.195 total actual memory usage
pan: elapsed time 0 seconds
The assertion is violated, as stated in the documentation. You can replay the invalid execution trace as follows:
~$ spin -t -p -l -g test.pml
Double Check. Now, if you comment the instruction flag:
inside pippo
and you repeat the verification process, you'll see that there won't be any execution trace violating the assertion. This is because no other instruction in the atomic sequence of pippo
can block, and therefore atomicity is never lost.
TEST 2: blocking error with d_step {}
Now, take the same code example and substitute the atomic
keyword inside pippo
with d_step
:
bool flag;
pid p;
active proctype pippo ()
{
byte i;
do
:: i < 10 ->
d_step {
true ->
p = _pid;
flag; /* executable only if flag is true */
printf("pippo unblocked\n");
flag = !flag;
assert(p == _pid);
};
i++;
:: else -> break;
od;
};
active proctype pluto ()
{
byte i;
do
:: i < 10 ->
atomic {
true ->
p = _pid;
end:
!flag; /* executable only if flag is false */
printf("pluto unblocked\n");
flag = !flag;
};
i++;
:: else -> break;
od;
};
If you formally verify this model, you'll see that it still finds a counter-example, but this time with a different error:
~$ spin -search -bfs test.pml
pan:1: block in d_step seq (at depth 2)
pan: wrote test.pml.trail
(Spin Version 6.4.3 -- 16 December 2014)
Warning: Search not completed
+ Breadth-First Search
+ Partial Order Reduction
Full statespace search for:
never claim - (none specified)
assertion violations +
cycle checks - (disabled by -DSAFETY)
invalid end states +
State-vector 20 byte, depth reached 2, errors: 1
4 states, stored
4 nominal states (stored-atomic)
0 states, matched
4 transitions (= stored+matched)
0 atomic steps
hash conflicts: 0 (resolved)
Stats on memory usage (in Megabytes):
0.000 equivalent memory usage for states (stored*(State-vector + overhead))
0.290 actual memory usage for states
128.000 memory used for hash table (-w24)
128.195 total actual memory usage
pan: elapsed time 0 seconds
Which corresponds to the following execution trace:
~$ spin -t -p -l -g test.pml
using statement merging
1: proc 1 (pluto:1) test.pml:26 (state 1) [((i<10))]
2: proc 0 (pippo:1) test.pml:8 (state 1) [((i<10))]
3: proc 0 (pippo:1) test.pml:9 (state 8) [(1)]
3: proc 0 (pippo:1) test.pml:11 (state 3) [p = _pid]
spin: trail ends after 3 steps
#processes: 2
flag = 0
p = 0
3: proc 1 (pluto:1) test.pml:27 (state 7)
3: proc 0 (pippo:1)
2 processes created
The problem is that pippo
blocks inside the d_step
sequence, and this violates the third condition in the synopsis of d_step
documentation, exactly as described.
Double Check. Again, if you comment the flag;
instruction you'll see that there won't be any error trace.