atomic sequences in Promela. Contradictory in docu

2020-02-15 07:27发布

问题:

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.

回答1:

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.