In the following code sample, a Sender and a Receiver exchange --for an undetermined amount of time-- a number of packets. Each message, sent by the Sender, contains either 0
or 1
and a sequence number. Whenever the Receiver a message, it checks whether it is new and in such case it sends back to the Sender an acknowledgment. Upon receiving an ACK
, the Sender sends a message with new content.
In this model, whenever a message is sent, the message can either be lost, sent twice or sent normally.
mtype = { MESSAGE, ACK };
chan sender2receiver = [2] of { mtype, bit, int };
chan receiver2sender = [2] of { mtype, bit, int };
inline unreliable_send(channel, type, tag, seqno) {
bool loss = false;
bool duplicate = true;
if
:: channel!type(tag, seqno);
if
:: channel!type(tag, seqno);
:: duplicate = false;
fi
:: loss = true;
fi
}
active proctype Sender () {
bit in_bit, out_bit;
int seq_no;
do
:: unreliable_send(sender2receiver, MESSAGE, out_bit, seq_no) ->
receiver2sender?ACK(in_bit, 0);
if
:: in_bit == out_bit ->
out_bit = 1 - out_bit;
seq_no++;
:: else ->
skip;
fi;
od;
}
active proctype Receiver () {
bit in_bit, old_bit;
int seq_no;
do
:: sender2receiver?MESSAGE(in_bit, seq_no) ->
if
:: in_bit != old_bit ->
printf("received: %d\n", seq_no);
old_bit = in_bit;
:: else ->
skip;
fi;
unreliable_send(receiver2sender, ACK, in_bit, 0);
od;
}
In the previous model, the loss of a packet causes a deadlock.
To solve this issue, the lecturer discussed several approaches.
One of these approaches, is to make the Sender relentless, so that it keeps sending out a message up until when it is effectively delivered to the Receiver and its corresponding ack
is correctly received by the Sender. i.e.:
active proctype Sender () {
bit in_bit, out_bit;
int seq_no;
// solution 1: keep sending a message till an ack is received
do
:: unreliable_send(sender2receiver, MESSAGE, out_bit, seq_no);
:: receiver2sender?ACK(in_bit, 0);
if
:: in_bit == out_bit ->
out_bit = 1 - out_bit;
seq_no++;
:: else ->
skip;
fi;
od;
}
Although in practice this approach seems to work, meaning that simulating the model no longer results in a deadlock, the lecturer warned us that it would still fail a step of formal verification because there exits at least one unlucky execution path in which at some point all messages are lost, no matter how much one insists in sending the same message again.
The lecturer invited us to think how we could use Spin
to find one of the execution traces for which the proposed approach fails. Since we did not cover LTL
model checking yet, the solution should be based on assert
or labels.
The idea behind the protocol is that Sender and Receiver keep exchanging new messages for an infinite amount of time.
One can verify whether the proposed solution effectively respects this specification by means of progress labels.
Progress labels are used to define correctness claims. A progress label states the requirement that the labeled global state must be visited infinitely often in any infinite system execution. Any violation of this requirement can be reported by the verifier as a non-progress cycle.
In this model, the progress-state is the one in which we establish that a message was both correctly sent and acknowledged, so our progress-state is the one immediately after we realize this fact.
Thus, we edit the Sender code as follows, and add a progress:
label after in_bit == out_bit ->
:
active proctype Sender () {
bit in_bit, out_bit;
int seq_no;
// solution 1: keep sending a message till an ack is received
do
:: unreliable_send(sender2receiver, MESSAGE, out_bit, seq_no);
:: receiver2sender?ACK(in_bit, 0);
if
:: in_bit == out_bit ->
progress:
out_bit = 1 - out_bit;
seq_no++;
:: else ->
skip;
fi;
od;
}
We can then formally verify the model as follows:
~$ spin -a -DNP test.pml
~$ gcc -DNP pan.c -o run
~$ ./run -l -bfs
or, alternatively, with the following one-liner:
~$ spin -search -l test.pml
As expected, spin
finds a non-progress cycle:
pan:1: non-progress cycle (at depth 16)
pan: wrote test.pml.trail
(Spin Version 6.4.7 -- 19 August 2017)
Warning: Search not completed
+ Partial Order Reduction
Full statespace search for:
never claim + (:np_:)
assertion violations + (if within scope of claim)
non-progress cycles + (fairness disabled)
invalid end states - (disabled by never claim)
State-vector 88 byte, depth reached 41, errors: 1
21 states, stored
0 states, matched
21 transitions (= stored+matched)
0 atomic steps
hash conflicts: 0 (resolved)
Stats on memory usage (in Megabytes):
0.002 equivalent memory usage for states (stored*(State-vector + overhead))
0.287 actual memory usage for states
128.000 memory used for hash table (-w24)
0.534 memory used for DFS stack (-m10000)
128.730 total actual memory usage
pan: elapsed time 0 seconds
We can now simulate the counter-example and show the print the offending execution trace:
~$ spin -p -l -g -t test.pml
starting claim 2
spin: couldn't find claim 2 (ignored)
using statement merging
2: proc 0 (Sender:1) test.pml:8 (state 1) [loss = 0]
Sender(0):loss = 0
2: proc 0 (Sender:1) test.pml:9 (state 2) [duplicate = 1]
Sender(0):duplicate = 1
Sender(0):loss = 0
4: proc 0 (Sender:1) test.pml:10 (state 3) [sender2receiver!2,out_bit,seq_no]
queue 1 (sender2receiver): [MESSAGE,0,0]
6: proc 1 (Receiver:1) test.pml:44 (state 1) [sender2receiver?MESSAGE,in_bit,seq_no]
queue 1 (sender2receiver):
Receiver(1):seq_no = 0
Receiver(1):in_bit = 0
8: proc 1 (Receiver:1) test.pml:49 (state 5) [else]
queue 1 (sender2receiver):
10: proc 1 (Receiver:1) test.pml:50 (state 6) [(1)]
queue 1 (sender2receiver):
12: proc 1 (Receiver:1) test.pml:8 (state 9) [loss = 0]
queue 1 (sender2receiver):
Receiver(1):loss = 0
12: proc 1 (Receiver:1) test.pml:9 (state 10) [duplicate = 1]
queue 1 (sender2receiver):
Receiver(1):duplicate = 1
Receiver(1):loss = 0
14: proc 1 (Receiver:1) test.pml:10 (state 11) [receiver2sender!1,in_bit,0]
queue 1 (sender2receiver):
queue 2 (receiver2sender): [ACK,0,0]
16: proc 1 (Receiver:1) test.pml:12 (state 12) [receiver2sender!1,in_bit,0]
queue 1 (sender2receiver):
queue 2 (receiver2sender): [ACK,0,0][ACK,0,0]
<<<<<START OF CYCLE>>>>>
18: proc 0 (Sender:1) test.pml:12 (state 4) [sender2receiver!2,out_bit,seq_no]
queue 1 (sender2receiver): [MESSAGE,0,0]
queue 2 (receiver2sender): [ACK,0,0][ACK,0,0]
20: proc 1 (Receiver:1) test.pml:44 (state 1) [sender2receiver?MESSAGE,in_bit,seq_no]
queue 1 (sender2receiver):
queue 2 (receiver2sender): [ACK,0,0][ACK,0,0]
Receiver(1):seq_no = 0
Receiver(1):in_bit = 0
22: proc 1 (Receiver:1) test.pml:49 (state 5) [else]
queue 1 (sender2receiver):
queue 2 (receiver2sender): [ACK,0,0][ACK,0,0]
24: proc 1 (Receiver:1) test.pml:50 (state 6) [(1)]
queue 1 (sender2receiver):
queue 2 (receiver2sender): [ACK,0,0][ACK,0,0]
26: proc 1 (Receiver:1) test.pml:8 (state 9) [loss = 0]
queue 1 (sender2receiver):
queue 2 (receiver2sender): [ACK,0,0][ACK,0,0]
Receiver(1):loss = 0
26: proc 1 (Receiver:1) test.pml:9 (state 10) [duplicate = 1]
queue 1 (sender2receiver):
queue 2 (receiver2sender): [ACK,0,0][ACK,0,0]
Receiver(1):duplicate = 1
Receiver(1):loss = 0
28: proc 1 (Receiver:1) test.pml:15 (state 16) [loss = 1]
queue 1 (sender2receiver):
queue 2 (receiver2sender): [ACK,0,0][ACK,0,0]
Receiver(1):loss = 1
30: proc 0 (Sender:1) test.pml:8 (state 1) [loss = 0]
queue 1 (sender2receiver):
queue 2 (receiver2sender): [ACK,0,0][ACK,0,0]
Sender(0):loss = 0
30: proc 0 (Sender:1) test.pml:9 (state 2) [duplicate = 1]
queue 1 (sender2receiver):
queue 2 (receiver2sender): [ACK,0,0][ACK,0,0]
Sender(0):duplicate = 1
Sender(0):loss = 0
32: proc 0 (Sender:1) test.pml:10 (state 3) [sender2receiver!2,out_bit,seq_no]
queue 1 (sender2receiver): [MESSAGE,0,0]
queue 2 (receiver2sender): [ACK,0,0][ACK,0,0]
34: proc 1 (Receiver:1) test.pml:44 (state 1) [sender2receiver?MESSAGE,in_bit,seq_no]
queue 1 (sender2receiver):
queue 2 (receiver2sender): [ACK,0,0][ACK,0,0]
Receiver(1):seq_no = 0
Receiver(1):in_bit = 0
36: proc 1 (Receiver:1) test.pml:49 (state 5) [else]
queue 1 (sender2receiver):
queue 2 (receiver2sender): [ACK,0,0][ACK,0,0]
38: proc 1 (Receiver:1) test.pml:50 (state 6) [(1)]
queue 1 (sender2receiver):
queue 2 (receiver2sender): [ACK,0,0][ACK,0,0]
40: proc 1 (Receiver:1) test.pml:8 (state 9) [loss = 0]
queue 1 (sender2receiver):
queue 2 (receiver2sender): [ACK,0,0][ACK,0,0]
Receiver(1):loss = 0
40: proc 1 (Receiver:1) test.pml:9 (state 10) [duplicate = 1]
queue 1 (sender2receiver):
queue 2 (receiver2sender): [ACK,0,0][ACK,0,0]
Receiver(1):duplicate = 1
Receiver(1):loss = 0
42: proc 1 (Receiver:1) test.pml:15 (state 16) [loss = 1]
queue 1 (sender2receiver):
queue 2 (receiver2sender): [ACK,0,0][ACK,0,0]
Receiver(1):loss = 1
spin: trail ends after 42 steps
#processes: 2
queue 1 (sender2receiver):
queue 2 (receiver2sender): [ACK,0,0][ACK,0,0]
42: proc 1 (Receiver:1) test.pml:43 (state 20)
Receiver(1):loss = 1
42: proc 0 (Sender:1) test.pml:11 (state 6)
2 processes created