CS195Y Lecture 20

4/06/16

Announcement: Final project proposals are due tomorrow. Reminder that you should speak to a TA or Tim about your project idea prior to handing in your proposal.

Modeling Traffic Light Continued: Introducing Spin

Spin is a tool for model checking, with Promela as its language. It uses C-like syntax, so ask questions if you’re unfamiliar with anything!

Today will be “Spin by immersion” - Tim’s going to go through the same traffic light system as Monday, this time in Spin/Promela.

#define RED 0
#define YELLOW 1
#define GREEN 2

byte colors[2];
byte turn = 0;

proctype Light(byte myId) {
    colors[myId] = RED; // start with a red light
    
    do 
    :: (turn == myId && colors[myId] == RED) ->
        colors[myId] = GREEN;
        printf("Light changed to %d\n", myId, colors[myId];
    :: (turn == myId && colors[myId] == YELLOW) ->
        colors[myId] = RED;
        turn = 1 - turn; // This is a trick to switch turn, 0 to 1, 1 to 0
        printf("Light changed to %d\n", myId, colors[myId];
    :: (turn == myId && colors[myId] == GREEN) ->
        colors[myId] = YELLOW;
        printf("Light changed to %d\n", myId, colors[myId];
    od
}

init {
    run Light(0);
    run Light(1);
}

There’s some fun syntax here: do/od is a do loop, where od indicates the end of the loop (like a left bracket, }). You can think of :: <term> -> as an if within the do loop, where if the first term matches, this block is executed.

Let’s run this! What’s unexpected? We get chunks of output like:

    Light 0 changed to 2
        Light 1 changed to 0
    Light 0 changed to 1

Spin indicates processes by indentation: an additional tab indicates a distinct process.

So what’s wrong with our code? We’re hitting a race condition after the line that changes turns. While this seems annoying, it’s actually useful - we want our tool to let things go horribly wrong, so that they can be used to find holes in our intuition.

Solution: the atomic keyword tells Spin to execute a chunk of code all-at-once, without letting another process do anything else until the entire chunk is complete.

    :: (turn == myId && colors[myId] == YELLOW) ->
        atomic {
            colors[myId] = RED;
            turn = 1 - turn; // This is a trick to switch turn, 0 to 1, 1 to 0
            printf("Light changed to %d\n", myId, colors[myId];
        }

Verifying properties: safety

Can we explore the properties of this traffic light system we came up with last class?

Spin has assertions. Here’s an example of one approach to checking safety (that at both lights are not simultaneously yellow or green):

init {
    run Light(0);
    run Light(1);
    
    do
    :: assert(colors[0] == RED || colors[1] == RED)
    od
}

What’s concerning about this? Because Spin works non-deterministically, do we actually know that this assert will be checked? We want to assert safety universally, not at some random point(s).

We need some way of not just making this an execution engine, but something that actually checks this property across all possible runs.

This alludes to a very real problem in distributed systems: non-determinism makes reproducing bugs really difficult. Model checking give us another tool to combat this.

Cool random fact: NASA used Spin to model check components of the software for a recent Mars rover!

So how do we get around this? Let’s check out the .c file generated by Spin. Rather than just giving one possible run of the system, this generated file actually runs all possible runs of the system with an exhaustive state-space search. We can compile and run this file.

When we do this for our current example, we find that our assertion holds! Spin also tells us that we never reach the end states of the process types (because in this case, we baked in infinite loops).

Q: If we have infinite loops, how does Spin do an exhaustive search?
A: Spin remembers previous states its seen, so it stops once if finds no new states.

Verifying properties: liveness

What about liveness; that cars eventually can get through this intersection because all lights eventually turn green?

In general, assertions often won’t be helpful for liveness, because a counterexample to liveness is infinite.

Reminder: what’s an instance in Spin? For now, Tim’s defining it as an infinite-length trace of system execution.

Solution to verifying liveness in Spin, an ltl property:

ltl always_eventually_green {
    always (eventually (colors[1] == GREEN) &&
            eventually (colors[0] == GREEN))
}

This doesn’t look like an assertion, so how do we actually check this?

Notes: (1) Some syntactic sugar in Spin: [] == always and <> == eventually. (2) Tim’s going through a lot of syntax quickly, but you won’t need to worry about it immediately. It’s useful for showing this example, but we’ll spend more time on much of it in later lectures.

If we compile and re-run the C code, its output now includes some stuff about the property, but it won’t actually check the property on it’s own. If we add an -a flag to the run statement, it will check this assertion. And for this example, it fails!

Spin gives us a cycle that violated liveness: because it finds a cycle, that means there’s some trace that repeats this cycle infinitely, which violates liveness.

So what’s the counterexample? A scheduler could start both processes, then check the assert… then check the assert… then check the assert infinitely many times. The two processes with the lights themselves don’t get to make progress. This is a valid choice the scheduler could make, so right now this is a counterexample.

The solution to this: another flag to the run command, -f, indicated fairness. This enforces that the scheduler doesn’t just leave some process (like the lights themselves) handing forever.