Chapter 4. Dynamic Termination Decision
4.3. With considering the termination-dependency relationship
The scheme presented in previous section is suitable for dealing with loops with no termination-dependency relationship. The modified loop signals itself might be an infinite loop only when the number of iterations exceeds the preset maximum number of iterations (i.e., ITERATION_LIMITATION). This protocol is quite imprecise. If the termination of loop Lx depends on loop Ly, we have to check whether loop Ly is infinite to decide if Lx is infinite. We can claim intuitively that Lx is an infinite loop only if we are certain that loop Ly cannot affect the iteration of Lx anymore. Besides, the termination-dependency relationship between loops can be used to test if several loops might cause a livelock. To explain this, we first present the scheme for handling programs without nested loops in this section (although it is not a general solution).
The schemes for the dynamic termination decision of programs with nested loops are presented in the next section.
30
Figure 9A shows a concurrent program that cannot be properly handled by the protocol shown in Figure 8. The program has three shared variables a, b, and c that are initialized to 0. Actually, whether loop L0 is infinite depends on whether loop L1 is infinite. Similarly, L1 depends on L2. According to Definition 2, L0 has static termination dependency with L1, and L1 has static termination dependency with L2.
Figure 9B shows their termination-dependency relationship. The dotted line edge labeled “s” indicates a static termination dependency between two loops. By applying the modified function New_Boolean_func() in Figure 8 to this program, any of the loops might report that itself is an infinite loop if it is scheduled to execute. This is because the number of iterations will always exceed ITERATION_LIMITATION in a single time slice [4]. Loop L0 is an example of this, whose termination depends on loop L1. Thus, for a more precise decision about whether an infinite loop is present, we should consider the static termination dependency between loops. In essence, we claim that a loop L might be infinite only if the number of executions of all the loops where L depends on following the static termination dependency exceeds ITERATION_LIMITATION.
Furthermore, in concurrent programs with dynamic termination dependency, we consider that the loops might form a livelock during their iterations. The literature contains different definitions for livelock, including starvation, infinite execution, and breach-of-safety behavior [59]. A livelock has also been defined as an infinite execution of several concurrent processes [60]:
“A concurrent program P is said to have a livelock if P can reach a state such that after entering this state, some process never becomes deadlocked or terminated, but does not make progress.”
a, b, and c are shared variables and initialized to 0.
Figure 9. (A) Example of a concurrent program with three processes and (B) its termination-dependency graph
In this thesis, we consider that the execution of several loops in a concurrent program P forms a livelock when it is possible for the scheduling or interleaving of these loops to keep the processing in the loop. The example in Figure 10 consists of two processes, each of which has a loop. The execution of this concurrent program has feasible execution sequences: {(S0,0, S1,0, S0,1, S1,1,)i S1,0, S0,0, S0,1, S1,1}, where 0
≦
i≦
∞. If the scheduling of the processes repeats the execution sequence (S0,0, S1,0, S0,1, S1,1,), then none of the processes can leave the loops. Actually, the dynamic termination-dependency relationships of these two loops form a cycle. We use a solid line edge labeled “d” to indicate a dynamic termination dependency between two loops.We now present a scheme for solving both of the problems shown in Figure 9 and Figure 10. We first need an algorithm that can determine the termination-dependency relationship before the execution of the target concurrent program. We construct a termination-dependency graph for the target concurrent program. Referring to
10 The update1(&a) is a function which changes the value of variable a.
32
Algorithm 1, the nodes in the graph are loops. The dynamic and static termination-dependency edges are added to the graph according to Definition 1 and Definition 2. For the flow-dependency edge, we should consider the situation that a loop might be located in a multiple-selection statement. Consider the program shown in Figure 11. After loop L1 is terminated, either loop L2 or L3 will be executed according to the evaluation of Boolean_func(). In this situation we add flow-dependency edges from L2 to L1 and from L3 to L1.
a is a shared variable.
Figure 10. (A) Example of a concurrent program that might form a livelock and (B) its termination-dependency graph
Figure 11. Loops in multiple-selection statements
The dynamic termination, static termination, and flow dependencies are represented by solid-line, dotted-line, and double-line edges, respectively.
Algorithm 1: Generate the termination-dependency graph of a concurrent program CP without nested loops.
Input: A concurrent program CP.
Output: A termination-dependency graph G.
(1) Initialize G as an empty graph.
(2) Add each loop of CP to G as a vertex. Assume that there n loops in CP: Li, where 0≤i<n. Let vertex(Li) represent the vertex associated with Li.
(3) For each pair of loops Li and Lj, where 0≤i<n, 0≤j<n, and i≠j:
If Li has dynamic termination dependency with Lj, then add a dynamic termination-dependency edge from vertex(Li) to vertex(Lj).
If Li has static termination dependency with Lj, then add a static termination-dependency edge from vertex(Li) to vertex(Lj).
If Li and Lj are in the same process, and Lj might be executed after the termination of Li without the need to wait for the termination of any other loop, then add a flow-dependency edge from vertex(Lj) to vertex(Li).
Now we can design a more precise protocol for the dynamic termination decision of infinite loops or the formation of livelock. The protocol should consider the statuses of the execution of loops. First, we need a vector to record the execution status of each loop – this is called the execution-status vector, which is an array. Each loop could have one of the following statuses:
“Active”: The loop is been executing.
“Not started”: The loop has not started, and will become ready when the previous loops in the same process are finished.
“Terminated”: The execution of the loop is finished and cannot to be executed anymore.
34
“Ready”: The loop is ready to execute immediately without the need to wait for the termination of any loop in the same process. That is, if there is a flow-dependency edge from L2 to L1, then L2 should be set to “Ready”
immediately after the termination of L1.
Figure 12 shows the execution-status transition of a loop. Initially, loops that can be executed when the processes are started without needing to wait for the termination of any loop in the same process should be set to “Ready”, and the others are set to “Not started”. When the execution of a loop is finished, it should set itself to “Terminated”, and some subsequent loops are set to “Ready”. Because it is possible to have loops embedded in multiple-selection statements, all the loops that might be executed after the termination of a loop need to be set. For the example in Figure 11, after the termination of loop L1, loops L2 and L3 are set to “Ready”. In general, a loop should be set to “Ready” before it is scheduled to execute and set to “Active” when it starts to execute, with any other loops with a “Ready” status then being set to “Terminated”. A transition from a “Ready” to a “Terminated” status means that a loop is skipped by the multiple-selection statement. For the example in Figure 11, if Boolean_func() returns
TRUE, then the statuses of loops L1 and L2 should be set to “Active” and “Terminated”, respectively.
We also need to generate termination-dependency subgraphs for each loop from the termination-dependency graph. The termination-dependency graph provides the global view of the termination-dependency relationship for all loops in the concurrent program. However, the termination-dependency subgraph of a loop L represents loops that might control the termination of L. Referring to Algorithm 2, nodes that are
connected from loop L are placed in the termination-dependency subgraph of L. We can generate the termination-dependency set of a loop L from the execution-status vector and the termination-dependency subgraph. Referring to Algorithm 3, only loops with an “Active” or “Ready” status are replaced in the termination-dependency set.
Since the contents of the execution-status vector changes during run-time, we have to dynamically generate different termination-dependency sets at different times. If all the numbers of iterations of L and the loops in the termination-dependency set of L exceeds the preset maximum, the protocol reports that loop L might be an infinite loop.
Not started
Ready = Terminated
Status transition is set by the execution of other loops.
=
Status transition is set by the execution of itself.
Active
Figure 12. Execution-status transitions of a loop without nesting
Algorithm 2: Generate the termination-dependency subgraph of a loop from a termination-dependency graph
Input: A termination-dependency graph G and a loop L.
Output: A termination-dependency subgraph G′.
(1) Initialize G′ as an empty graph. Add L as a node to G′.
(2) Add to G′ all the nodes in G that have a path to L.
(3) For each pair of nodes (X,Y) in G′, if there exists an edge E between X and Y in G, then add E to G′.
36
Algorithm 3: Generate the termination-dependency set of a loop from its termination-dependency subgraph and the execution-status vector.
Input: A loop L, its termination-dependency subgraph G, and the execution-status vector.
Output: A set of loops S which is the termination-dependency set of L.
(1) Initialize S as the empty set.
(2) Add to S each loop whose execution status in G is “Active” or “Ready”.
(3) Remove loop L′ from S if all the paths from L to L′ must pass through some loop whose execution status is “Terminated”.
The more complicated situation is that a loop L belongs to a livelock loop group.
Referring to Definition 3, if LG is a livelock loop group in the termination-dependency graph of concurrent program P, then certain scheduling might cause the execution of processes in LG to form a livelock. Also, the termination-dependency subgraphs of loops in LG are identical, as shown by Theorem 3. When the number of iterations of L exceeds the preset maximum, ITERATION_LIMITATION, the protocol checks if any loop in the group is “Terminated”. If this is the case, then it is impossible for these loops to form a livelock. Otherwise, the protocol checks if all the loops in the groups and its termination-dependency set exceed the maximum number of iterations. If this is the case, then the protocol reports that a livelock could occur.
Definition 3:For a termination-dependency graph G of a concurrent program, the set of elements in a cycle of G is called a livelock loop group if all the edges in the cycle are dynamic termination-dependency edges.
Figure 13 shows the detailed protocol. The original guard statement Boolean_func() is first changed to DTD_Boolean_func(), and then the execution-status vector should be
updated when it enters or leaves a loop by invoking Update_ExecStatus_Enter_Loop()
and Update_ExecStatus_Leave_Loop(), respectively. Update_ExecStatus_Enter_Loop() sets the loop itself to “Active” and other loops that are “Ready” to “Terminated”. Note that loops whose statuses are changed from “Ready” to “Terminated” are located in the multiple-selection statement and are not executed. Update_ExecStatus_Leave_Loop() sets the loop itself to “Terminated” and other loops that might be executed subsequently to
“Ready”.
In DTD_Boolean_func(), if function value_no_change(V0,V1,V2,...,Vn-1) cannot decide it is an infinite loop, a complicated dynamic decision process is invoked. This process initially increments the number of iterations by one and obtains the execution-status vector for the following references of this function, and then it derives the termination-dependency set according to Algorithm 3. If any one of the loops in the obtained termination-dependency set is “Ready”, no decision process is performed using this function at that time. This is because a “Ready” loop might not be executed since it is located in a multiple-selection statement. It will change from a “Ready” to either an “Active” or “Terminated” status, and hence skipping the decision process in this iteration does not reduce the accuracy of the termination decision. That is, if none of loops in the termination-dependency set is “Ready”, the protocol first checks for the formation of a livelock. If no livelock forms or Li does not belong to any livelock loop groups, all the livelock loop groups will be marked. Li is checked for whether it is an infinite loop by examining the ITERATION_LIMITATION of all loops in the termination-dependency set.
According to the halting problem, no algorithm can determine for certain whether a
38
loop can exit. However, the proposed protocol allows the programmer to specify a maximum number of iterations for each loop, by setting ITERATION_LIMITATION. Even if a loop exceeds the iteration limit, this usually does not mean that it is stuck in an infinite loop, because this loop might have to wait for other loops. If all the members in a termination-dependency set of a loop exceed their maximum iteration limits, it is very likely that their dependency relationship will result in more iterations. When any process reports an infinite loop or a livelock, the programmer can examine the recorded SYN-sequence – which is an event sequence of the tested concurrent program in this execution – to check if an infinite loop or livelock is present. If it is a false alarm, the programmer can adjust the value of ITERATION_LIMITATION and resume the dynamic testing. A more complicated Boolean predicate could be used to decide if the execution gets stuck in infinite loops.
Theorem 3: If L1 and L2 belong to the same livelock loop group, then they have an identical termination-dependency subgraph.
Proof: Since L1 and L2 belong to the same livelock loop group, there must exist paths from L1 to L2 and from L2 to L1 according to Definition 3. Hence nodes reachable from L1 must be reachable from L2, and vice versa. Thus, L1 and L2 have an identical termination-dependency subgraph. QED
1 2 3 4 5
/* The modified code for loop Li.
* ("DTD" is the acronym of "dynamic termination decision".) */
while(DTD_Boolean_Func(Li, { V0,V1,V2,...,Vn-1}) { Update_ExecStatus_Enter_Loop(Li);
/* loop body */
}
Update_ExecStatus_Leave_Loop(Li);
/* "Execstatus" is an array which stores the statuses of all loops.
1
* Execstatus[k] represents the status of the k-th loop.
* Execstatus[].lock is to guarantee a mutually exclusive access to the whole * array.
*/
Update_ExecStatus_Enter_Loop(Li) { Execstatus[].lock;
for each loop Lx located in the same process to Li and Execstatus[x] is "Ready"
Execstatus[x] = "Terminated";
Execstatus[i] = "Active";
Execstatus[].unlock;
}
Update_ExecStatus_Leave_Loop(Li) { Execstatus[].lock;
if(there exists a flow-dependency edge from Lx to Li in the termination-dependency subgraph of Li) {
Execstatus[x] = "Ready";
}
Execstatus[i] = "Terminated";
Execstatus[].unlock;
Obtain the current execution-status vector for the following references in this function.
40
Figure 13. Modified pretested loop that considers the loop-dependency relationship We use the concurrent program shown in Figure 14A to illustrate our algorithms. The corresponding termination-dependency graph is shown in Figure 14B. The example has four shared variables a, b, c, and d. Four loops (L1, L2, L3, and L4) are reachable from L0. L0 and L3 are before L1 and L4 in the context, respectively. At the beginning of program execution, the termination-dependency set of L2 is initialized as {L0, L2, L3} due to loops L1 and L4 being “Not started”. Note that if loop L3 finishes during run-time, the termination-dependency set of L2 would be changed to {L0, L2, L4}.
a, b, c, and d are shared variables.
Figure 14. (A) Example of a concurrent program comprising dynamic and static termination dependencies and (B) its termination-dependency graph
Figure 15A shows a more complicated example that contains a livelock loop group, {L0, L3}. As in Figure 9, the example has three shared variables a, b, and c that are
initialized to 0. There are four loops (L0, L1, L2, and L3) that are reachable from L0.
L0 is before L1 in the context. Assuming that the statuses of loops L0, L1, L2, and L3 are “Active”, “Not started”, “Active”, and “Active”, respectively, the termination-dependency set of L0 is {L0, L2, L3}.
a, b, and c are shared variables whose initial value are 0.
Process P0
Figure 15. (A) Example of a concurrent program comprising dynamic and static termination dependencies and (B) its termination-dependency graph
42
Chapter 5. Effective Test Set and Dynamic Effective