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.