• Aucun résultat trouvé

A trace-based technique

Dans le document Message-Passing Systems (Page 179-184)

Chapter 7: Graph Algorithms

9.3 Breakpoint detection

9.3.2 A trace-based technique

In this section, we discuss a trace-based technique to halt the execution of an asynchronous algorithm at the earliest conjunctive breakpoint that occurs. It is trace-based because, as in

Section 9.2, it is based on two phases, the first one being the recording of a trace and the second one a re-execution with special attention to the detection of the earliest conjunctive breakpoint. The trace recording is achieved precisely as in Algorithm A_Record_Trace of Section 9.2, where the variables queuei and first_in_queuei, respectively a queue of node references and a pointer to its first element, are employed by node ni to record the

neighborhood-wide order according to which it receives comp_msg's. The difference in this case is that not only a deterministic re-execution is sought, but a re-execution that does not progress beyond the earliest conjunctive breakpoint. As in Section 9.2, G's edges are assumed to be FIFO. Also, for the sake of simplicity when writing the algorithm, we assume that a node's local predicate can only become true after the node has computed and sent messages out (unless the node does not participate in the breakpoint, in which case its local predicate is perpetually true).

The approach that we adopt has the following essential ingredients. During the re-execution phase, node ni may be active or inactive. It is active initially and becomes inactive when lpi

becomes true. A node only receives comp_msg's (and therefore only computes and sends comp_msg's out) if it is active, so that becoming inactive when its local predicate becomes true is a means of cooperating for the execution to halt at the earliest conjunctive breakpoint.

The problem with such a naïve way of cooperating is that, by becoming inactive and therefore not sending any comp_msg's out, a node may be precluding other nodes from reaching local states in which their local predicates can become true as well, which is an absolute must if the execution is to halt at a conjunctive breakpoint.

One way to go around this difficulty is the following. For nj ∊ Neigi, node ni maintains two

counters, and , both initially set to zero, to indicate

respectively the number of comp_msg's received from nj and the number of comp_msg's sent to nj. Whenever ni is finished with computing and sending comp_msg's out, either initially or in response to the reception of a comp_msg, and has remained active, it sends a request( + 1) message to nj such that nj = first_in_queuei. This message is intended to activate nj if it is inactive, so that it can send ni the comp_msg that it needs to proceed according to the trace. When the request message reaches nj with an x parameter, then it must be that x − 1 ≤ (if ni is requesting the xth comp_msg, then it must have received exactly x − 1 comp_msg's prior to sending the

request and nj must have sent at least as many comp_msg's prior to receiving the request). If x − 1 = , then nj does indeed owe ni a comp_msg. If it is not active, it then becomes active and sends a request itself, and only becomes inactive when its local predicate becomes true after having sent that comp_msg to ni. Because request's may accumulate before a node's computation is such that the corresponding comp_msg's get sent, node ni employs the variable , initially equal to zero, to indicate the number of comp_msg's that need to be sent to nj∊ Neigi before it may become inactive.

The problem that still persists with this approach is that, because edges are FIFO (as they must be for correct re-execution) and a node only receives comp_msg's when it is active and the origin of the comp_msg coincides with the node's first_in_queue variable, it may happen that a request never reaches its destination. The final fix is then to allow a node to receive all comp_msg's that reach it, and then to queue them up internally (along with their origins) on edge-specific queues if the node happens to be inactive or the comp_msg that arrived is not the one that was expected for re-execution. Upon receipt of a request from nj, an inactive node ni works on the messages in those queues until the re-execution can no longer

progress or = 0 and lpi = true. If > 0 or lpi = false when ni exits this loop, then ni becomes active and sends out a request. The reader should reflect on the reasons why this procedure ensures that the re-execution halts (i.e., all nodes become inactive) at the earliest conjunctive breakpoint (cf. Exercise 2).

Algorithm A_Replay_&_Halt_CB ("CB" for Conjunctive Breakpoint), given next, realizes the procedure we just described. In addition to the variables we already introduced, node ni also employs the Boolean activei, initialized to not lpi to indicate whether it is active. Also, for each nj ∊ Neigi, the queue where comp_msg's from nj may have to be queued is

, initially set to nil, whose first element is assumed to be the pair ( ,

), initially equal to (nil, nil), at all times. In this algorithm, as in Algorithm A_Replay, the set N0 is given as determined by Algorithm A_Record_Trace.

Algorithm A_Replay_&_Halt_CB:

Variables:

queuei;

first_in_queuei;

= 0 for all nj ∊Neigi; = 0 for all nj ∊ Neigi: = 0 for all nj ∊ Neigi:

activei = not lpi;

msg_queuei = nil for all nj ∊ Neigi;

first_origin_in_msg_queue = nil for all nj ∊ Neigi; first_msg_in_msg_queuei = nil for all nj ∊ Neigi;

Other variables used by ni, and their initial values, are listed here.

Listing 9.5 Input:

msgi = nil.

Action if ni ∊ No:

Do some computation;

Send one comp_msg on each edge of a (possibly empty) subset of Inci and update the 's accordingly;

if lpi then

activei := false.

Listing 9.6 Input:

msgi = comp_msg such that origini (msgi) = (ni, nj. Action:

if activei and nj = first_in_queuei then begin

:= + 1;

Remove first_in_queuei from queuei; Do some computation;

Send one comp_msg on each edge of a (possibly empty)

subset of Inci and update the 's and the 's accordingly;

if = 0 for all nk ∊ Neigi then if lpi then

activei := false;

if activei then begin

Let nk = first_in_queuei;

Send request ( + 1) to nk

end end else

Append (nj, msgi) to .

Listing 9.7 Input:

msgi = request(x) such that origini(msgi) = (ni, nj).

Action:

if not activei and x − 1 = then begin

:= + 1;

while ( > 0 or not lpi) and there exists nk ∊ Neigi

such that first_in_queuei = do

begin

:= + 1;

Remove first_in_queuei from queuei; Remove the pair

( ,

) from ;

Do some computation;

Send one comp_msg on each edge of a (possibly

empty) subset of Inci and update the 's and the 's accordingly

end;

if > 0 or not lpi then begin

activei := true;

Let nk = first_in_queuei

Send request( + 1) to nk

end end.

There is some correspondence between the actions in this algorithm and those in Algorithm A_Replay for simple re-execution, but they differ greatly, too. Specifically, actions (9.3) and (9.5) are related to each other, although (9.5) also undertakes the incrementing of

when a comp_msg is sent to neighbor nj and checks lpi to see if ni must become inactive. Likewise, actions (9.4) and (9.6) are also related to each other. The differences are in the internal queueing of comp_msg's and in that (9.6) increments

to account for the receipt of the triggering msgi on (ni, nj), increments (decrements , if positive) upon sending a comp_msg to neighbor nk, and in addition checks lpi to possibly set activei to false (if activei remains true, then (9.6) includes the sending of a request as well). Action (9.7) deals with the

reception of a request from nj. This request, if indeed corresponding to a comp_msg that was not sent, and if ni is inactive, causes to be incremented and ni to compute on its internal queues of messages. If remains positive or lpi = false after this, then ni

becomes active and sends a request.

As in the case of Algorithm A_Replay, the message and time complexities of Algorithm A_Replay_&_Halt_CB are the same as those of Algorithm A_Record_Trace. This is so because the additional request messages only increase the total number of messages exchanged and the longest causal chain of messages by a constant factor. However, the new algorithm's bit complexity may be higher, because request messages carry integers that depend on how many comp_msg's were received by the sending node during the trace-recording phase.

We finalize the section with a couple of observations leading to issues that the reader may find worth pursuing further. The first observation is that devising a procedure similar to Algorithm A_Replay_&_Halt_CB to halt at the earliest disjunctive breakpoint during a re-execution is a very different matter. The reader is encouraged to pursue a proof that no such procedure exists, be it trace-based or otherwise (cf. Exercise 3).

As the second observation, notice that Algorithm A_Replay_&_Halt_CB does not entirely conform to the standards set by Algorithm A_Template, in the sense that in both (9.6) and (9.7) a request may follow a comp_msg to the same node, whereas Algorithm A_Template only allows one message to be sent to a node per action. An instructive exercise is to rewrite Algorithm A_Replay_&_Halt_CB so that this constraint is respected (cf. Exercise 4).

Dans le document Message-Passing Systems (Page 179-184)