modern-multithreading-c-java
.pdf
REACHABILITY TESTING |
425 |
The OpenList contains program information that is used to compute the events that could have occurred besides r. For example, the OpenLists for the receive events in the example bounded buffer program above capture run-time information about the guard conditions (i.e., the list of accept alternatives that are open when a deposit or withdraw alternative is selected). This information enabled us to determine, for example, that a withdraw event could not be executed in place of the first deposit event. More OpenList examples are given below.
The individual fields of an event descriptor are referenced using dot notation. For example, operation op of sending event s is referred to as s.op. Tables 7.1 and 7.2 summarize the specific information that is contained in the event descriptors for the various synchronization constructs. Although the information is construct-specific, the format and general meaning of the fields in the event descriptors are the same for all constructs. This will allow us to present a single race analysis algorithm that operates on event descriptors and thus works for any construct. The values for most of the fields in the event descriptors are straightforward, except for the OpenList in receiving events. Below we describe how to compute OpenLists and provide examples of event descriptors for the various constructs.
Descriptors for Asynchronous Message Passing Events For asynchronous message passing, the OpenList of a receive event r contains a single port, which is the source port of r. A send event s is said to be open at a receive event r if port s.Destination is in the OpenList of r, which means that the ports of s and r match. For a sending event s to be in the race set of receive event r, it is necessary (but not sufficient) for s to be open at r. The race analysis algorithm presented in the next section can be used to determine whether s is involved in a race condition that could allow s to be the send partner of r.
Figure 7.18 shows a space-time diagram representing an execution with three threads. Thread T2 receives messages from ports p1 and p2. Thread T1 sends two messages to port p1. Thread T3 sends its first message to port p1 and its second message to port p2. Event descriptors are shown for each of the events.
TABLE 7.1 Event Descriptors for a Sending Event s
Synchronization |
|
|
|
|
Construct |
SendingThread |
Destination |
Operation |
i |
|
|
|
|
|
Asynchronous |
Sending thread |
Port ID |
Send |
Event index |
message passing |
|
|
|
|
Synchronous |
Sending thread |
Port ID |
Send |
Event index |
message passing |
|
|
|
|
Semaphores |
Calling thread |
Semaphore ID |
P or V |
Event index |
Locks |
Calling thread |
Lock ID |
Lock or unlock |
Event index |
Monitors |
Calling thread |
Monitor ID |
Method name |
Event index |
|
|
|
|
|
426 |
|
TESTING AND DEBUGGING CONCURRENT PROGRAMS |
|||||
TABLE 7.2 Event Descriptors for a Receiving Event r |
|
||||||
|
|
|
|
|
|
|
|
Synchronization |
|
|
|
|
|
|
|
Construct |
Destination |
|
|
OpenList |
i |
||
|
|
|
|
|
|
|
|
Asynchronous |
Receiving thread |
|
Port of r |
Event index |
|||
message passing |
|
|
|
|
|
|
|
Synchronous |
Receiving thread |
|
List of open ports |
Event index |
|||
message passing |
|
|
|
|
(including the port of r) |
|
|
Semaphores |
Semaphore ID |
|
List of open operations |
Event index |
|||
|
|
|
|
|
(P and/or V ) |
|
|
Locks |
Lock ID |
|
List of open operations |
Event index |
|||
|
|
|
|
|
(lock and/or unlock) |
|
|
Monitors |
Monitor ID |
|
List of the monitor’s |
Event index |
|||
|
|
|
|
|
methods |
|
|
|
|
|
|
|
|
|
|
|
T1 |
T2 |
T3 |
|
|||
s2 (T1,p1,send,1) |
r1 (T2,p1,1) |
|
|
|
s1 (T3,p1,send,1) |
|
|
|
|
r2 (T2,p1,2) |
|
||||
|
|
|
s3 (T3,p2,send,2) |
|
|||
r3 (T2,p2,3) |
|
|
|
||||
|
|
|
|
|
|
||
s4 (T1,p1,send,2) |
|
|
r4 (T2,p1,4) |
|
|
||
|
|
|
|
|
|||
|
|
|
|
|
|||
|
|
|
|
|
|
|
|
Figure 7.18 Sequence of asynchronous send/receive events.
Descriptors for Synchronous Message Passing Events Synchronous message passing may involve the use of selective waits. The OpenList of a receive event r is a list of ports that had open receive alternatives when r was selected. Note that this list always includes the source port of r. For a simple receive statement that is not in a selective wait, the OpenList contains a single port, which is the source port of the receive statement. Event s is said to be open at r if port s.Destination is in the OpenList of r.
Figure 7.19 shows a space-time diagram representing an execution with three threads. Thread T1 sends two messages to port p1, and thread T3 sends two messages to port p2. Thread T2 executes a selective wait with receive alternatives for p1 and p2. Assume that whenever p2 is selected, the alternative for p1 is open, and whenever p1 is selected, the alternative for p2 is closed. This is reflected in the OpenLists for the receive events, which are shown between braces {. . .} in the event descriptors. Note that each solid arrow is followed by a dotted arrow in the opposite direction. The dotted arrows represent the updating of timestamps when the synchronous communication completes. Timestamp schemes are described in Section 7.5.4.
Descriptors for Semaphore Events Figure 7.20 shows an execution involving threads T1 and T2 and semaphore s, where s is a binary semaphore initialized
REACHABILITY TESTING |
|
|
|
|
427 |
||||
T1 |
T2 |
T3 |
|||||||
s2 (T1,p1,send,1) |
|
r1 (T2,{p1,p2},1) |
|
|
|
|
s1 (T3,p2,send,1) |
||
|
|
|
|
||||||
|
r2 (T2,{p1},2) |
|
|
||||||
|
|
|
|
|
|
|
|||
|
r3 (T2,{p1,p2},3) |
|
|
s3 (T3,p2,send,2) |
|||||
s4 (T1,p1,send,2) |
|
|
|
|
|
||||
|
r4 (T2,{p1},4) |
|
|
||||||
|
|
|
|
|
|
|
|||
|
|
|
|
|
|
|
|||
|
|
|
|
|
|
|
|
||
|
|
|
|
|
|
|
|
|
|
Figure 7.19 Sequence of synchronous send/receive events.
T1 |
s |
T2 |
|
|
e1 (s,{P},1) |
|
p1 (T2,s,P,1) |
|
|
|
|
e2 (s,{V},2)
v1 (T2,s,V,2)
p2 (T1,s,P,1)
e3 (s,{P},3)
e4 (s,{V},4)
v2 (T1,s,V,2) 
Figure 7.20 Sequence of P and V operations.
to 1. There is one time line for each thread and each semaphore. A solid arrow represents the completion of a P() or V() operation. The open lists for the completion events model the fact that P and V operations on a binary semaphore must alternate. This means that the OpenList of a completion event for a binary semaphore always contains one of P or V but not both. OpenLists for binary and counting semaphores can easily be calculated at run time based on the semaphore invariant (see Sections 3.1 and 3.4.1). A call event c for a P or V operation is open at a completion event e if c and e are operations on the same semaphore (i.e., c.Destination = e.Destination, and operation c.op of c is in the OpenList of e).
Descriptors for Lock Events If a lock is owned by some thread T when a completion event e occurs, each operation in the OpenList of e is prefixed with T to indicate that only T can perform the operation. (Recall that if a thread T owns lock L, only T can complete a lock() or unlock() operation on L.) For example, if the OpenList of a completion event e on a lock L contains two operations lock() and unlock(), and if L is owned by thread T when e occurs, the OpenList of e is {T : lock , T : unlock }. A call event c on lock L that is executed by thread T is open at a completion event e if (1) c.Destination = e.Destination;
(2) operation c.op is in the OpenList of e, and (3) if L is already owned when e occurs then T is the owner.
Figure 7.21 shows a space-time diagram representing an execution with two threads and a mutex lock k. Thread T2 performs two lock() operations followed by two unlock() operations, and thread T1 performs one lock() operation followed
428 |
|
TESTING AND DEBUGGING CONCURRENT PROGRAMS |
|||
T1 |
|
k |
T2 |
||
|
|
e1 (k,{lock},1) |
|
l1 (T2,k,lock,1) |
|
|
|
|
|||
|
|
|
|||
|
|
|
|
||
|
e2 (k,{T2:lock,T2:unlock},2) |
|
l2 (T2,k,lock,2) |
||
|
|
||||
|
e3 (k,{T2:lock,T2:unlock},3) |
|
u1 (T2,k,unlock,3) |
||
|
|
||||
|
|
|
|||
|
e4 (k,{T2:lock,T2:unlock},4) |
|
u2 (T2,k,unlock,4) |
||
|
|
||||
l3 (T1,k,lock,1) |
|
|
|
e5 (k,{lock},5) |
|
|
|
|
|||
u3 (T1,k,unlock,2) |
|
|
|
e6 (k,{T1:lock,T1:unlock},6) |
|
|
|
|
|||
|
|
|
|
|
|
Figure 7.21 Sequence of lock and unlock operations.
by one unlock() operation. The OpenList for e2 reflects the fact that only thread T2 can complete a lock() or unlock() operation on k since T2 owns k when e occurs.
Descriptors for Monitor Events The invocation of a monitor method is modeled as a pair of monitor-call and monitor-entry events:
žSU monitors. When a thread T calls a method of monitor M, a monitor-call event c occurs on T . When T eventually enters M, a monitor-entry event e occurs on M, and then T starts to execute inside M.
žSC monitors. When a thread T calls a method of monitor M, a monitorcall event c occurs on T . A call event also occurs when T tries to reenter a monitor M after being signaled. When T eventually (re)enters M, a monitorentry event e occurs on M, and T starts to execute inside M.
In these scenarios, we say that T is the calling thread of c and e, and M is the destination monitor of c as well as the owning monitor of e. A call event c is open at an entry event e if the destination monitor of c is the owning monitor of e (i.e., c.Destination = e.Destination). The OpenList of an entry event always contains all the methods of the monitor since threads are never prevented from entering any monitor method (although they must enter sequentially and they may be blocked after they enter).
Figure 7.22 shows a space-time diagram representing an execution involving three threads T1, T2, and T3, an SC monitor m1 with methods a() and b(), and an SC monitor m2 with a single method c(). Thread T1 enters m1.a() first and executes a wait operation. The second call event performed by T1 occurs when T1 reenters m1.a() after being signaled by T2. Note that if m1 were an SU
REACHABILITY TESTING |
|
|
|
|
|
429 |
|||
|
T1 |
m1 |
T2 |
m2 |
T3 |
||||
c1 (T1,m1,a,1) |
|
|
e1 (m1,{a,b},1) |
|
|
|
|
||
|
|
|
|
|
|
||||
wait |
|
|
|
|
|
c2 (T2,m1,b,1) |
|
|
|
|
|
|
|
|
|
||||
|
|
|
|
|
|
|
|
|
|
|
|
e2 (m1,{a,b},2) |
|
|
|
||||
|
|
|
|
|
|
||||
|
|
|
signal |
|
|
|
|
|
|
c3 (T1,m1,a,2)
e3 (m1,{a,b},3)
c4 (T1,m2,c,3)
e4 (m2,{c},1)
e5 (m2,{c},2)
c5 (T3,m2,c,1)
c6 (T3,m1,b,2)
e6 (m1,{a,b},4)
Figure 7.22 Sequence of monitor call and entry events.
monitor, there would be no c3 event representing reentry. After T1 exits from m1, T1 enters and exits m2. This is followed by thread T3 entering and exiting m2 and then entering and exiting m1.
7.5.3 Race Analysis of SYN-Sequences
In this section we show how to perform race analysis on SYN-sequences. Section 7.5.2 showed a common event descriptor format for a variety of synchronization constructs. To illustrate race analysis, we will first consider a program CP that uses asynchronous ports. We assume that the messages sent from one thread to another may be received out of order. To simplify our discussion, we also assume that each thread has a single port from which it receives messages.
Let Q be an SR-sequence recorded during an execution of CP with input X. Assume that a → b is a synchronization pair in Q, c is a send event in Q that is not a, and c’s message is sent to the same thread that executed b. We need to determine whether sending events a and c have a race (i.e., whether c → b can happen instead of a → b during an execution of CP with input X). Furthermore, we need to identify races by analyzing Q, not CP.
To determine accurately all the races in an execution, the program’s semantics must be analyzed. Fortunately, for the purpose of reachability testing, we need only consider a special type of race, called a lead race. Lead races can be identified by analyzing the SYN-sequence of an execution (i.e., without analyzing the source code).
Definition 6.1 Let Q be the SYN-sequence exercised by an execution of a concurrent program CP with input X. Let a → b be a synchronization pair in Q and let c be another sending event in Q. There exists a lead race between c and < a, b > if c → b can form a synchronization pair during some other execution
430 |
TESTING AND DEBUGGING CONCURRENT PROGRAMS |
of CP with input X, provided that all the events that happened before c or b in Q are replayed in this other execution.
Note that Definition 6.1 requires all events that can potentially affect c or b in Q to be replayed in the other execution. If the events that happened before b are replayed, and the events that happened before c are replayed, we can be sure that b and c will also occur, without having to analyze the code.
Definition 6.2 The race set of a → b in Q is defined as the set of sending events c such that c has a (lead) race with a → b in Q.
We will refer to the receive event in Q that receives the message from c as receive event d, denoted by c → d. (If the message from c was not received in Q, d does not exist.) To determine whether a → b and c in Q have a message race, consider the 11 possible relationships that can hold between a, b, c, and d in Q:
(1)c d and d b.
(2)c d, b d, and b c.
(3)c is send event that is never received and b c.
(4)c d, b d, c||b, and a and c are send events of the same thread.
(5)c d, b d, c||b, and a and c are send events of different threads.
(6)c d, b d, c b, and a and c are send events of the same threads.
(7)c d, b d, c b, and a and c are send events of different threads.
(8)c is a send event that is not received, c||b, and a and c are send events of the same thread.
(9)c is a send event that is not received, c||b, and a and c are send events of different threads.
(10)c is a send event that is not received, c b, and a and c are send events of the same thread.
(11)c is a send event that is not received, c b, and a and c are send events of different threads.
The happened before relation e f was defined in Section 6.3.4. Recall that it is easy to examine a space-time diagram visually and determine the causal relations. For two events e and f in a space-time diagram, e f if and only if there is no message e ↔ f or f ↔ e and there exists a path from e to f that follows the vertical lines and arrows in the diagram.
Figure 7.23 shows 11 space-time diagrams that illustrate these 11 relations. Each of the diagrams contains a curve, called the frontier. Only the events happening before b or c are above the frontier. (A send event before the frontier may have its corresponding receive event below the frontier, but not vice versa.) For each of diagrams (4) through (11), if the send and receive events above the
432 |
|
|
|
|
TESTING AND DEBUGGING CONCURRENT PROGRAMS |
||||||||||||
Thread1 Thread2 |
Thread3 |
|
Thread4 |
Thread1 Thread2 |
Thread3 Thread4 |
||||||||||||
s1 |
|
{s1,s2,s3} r6 |
|
|
|
s6 |
|
|
|
s1 |
|
{s1} r6 |
|
|
|
s6 |
|
|
|
|
|
|
|
|
|
|
|
|
|
||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||||
|
|
s4 |
|
|
|
|
|
|
|
|
|
s4 |
|
|
|
|
|
s2 |
|
|
r1 |
|
|
|
|
|
r4 { } |
s2 |
|
|
r1 {s8} |
|
r4 { } |
||
|
|
{s2,s3,s8,s10} |
|
|
|
|
|
|
|||||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|||||
|
|
s5 |
|
|
|
r5 {s9} |
|
s8 |
|
|
s5 |
|
|
|
r5 {s9} |
s8 |
|
|
|
|
{s3,s7,s8,s10} |
|
|
|
|
r2 {s7,s8} |
|||||||||
|
|
|
r2 |
|
|
|
|
|
s9 |
|
|
|
|
s9 |
|||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||
|
|
{s3,s7,s10} r8 |
|
{ } r9 |
s7 |
|
|
s10 |
|
|
{s3,s7} r8 |
|
{ } r9 |
s7 |
s10 |
||
|
|
|
|
|
|
|
|
|
|
||||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|||
s3 |
|
|
|
|
|
|
|
|
s3 |
|
|
|
|
|
|
||
|
{s3,s10} r7 |
|
|
|
|
|
|
|
|
{s3,s10} r7 |
|
|
|
|
|
||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(a) |
|
|
|
|
|
|
|
|
(b) |
|
|||
Figure 7.24 Race sets for SR-sequences.
Thus, the definition of race set must also be modified for FIFO asynchronous SR-sequences.
Definition 6.4 Let Q be an SR-sequence of a program using FIFO asynchronous communication, and let a → b be a message in Q. The race set of a → b in Q is {c|c is a send event in Q; c has b’s thread as the receiver; not b c; if c → d, then b d; and all the messages that are sent from c’s thread to b’s thread before c is sent are received before b occurs}.
Figure 7.24b shows a FIFO asynchronous SR-sequence and the race set for each receive event in this SR-sequence. (Since the asynchronous SR-sequence in Fig. 7.24a satisfies FIFO ordering, it is also used in Fig. 7.24b.) Consider the nonreceived send event s3 in Fig. 7.24b. Send event s3 has Thread2 as the receiver and is in the race sets for receive events r7 and r8 in Thread2. Thread2 executes r2 immediately before executing r8. Since r2 has the same sender as s3 and s2 is sent to Thread2 before s3 is sent, s2 has to be received by Thread2 before s3 is received. Thus, s3 is not in the race set for receive event r2. (Note that in Fig. 7.24a, s3 is in the race sets for receive events r6, r1, r2, r8, and r7.)
In some parallel programs, nondeterminism is considered to be the result of a programming error [Empath et al. 1992]. If an execution of CP with input X is expected to be deterministic, the race set for each receive event in Q should be empty. If the race set for each receive event in Q is empty, all executions of CP with input X exercise the same SR-sequence and produce the same result.
In general, sending and receiving events may involve constructs such as semaphores, locks, and monitors, not just message passing. The following definition describes how to compute the race set of a receiving event assuming that all the constructs use FIFO semantics.
Definition 6.5 Let Q be a SYN-sequence exercised by program CP. A sending event s is in the race set of a receiving event r if (1) s is open at r; (2) r does
REACHABILITY TESTING |
433 |
not happen before s; (3) if < s, r > is a synchronization pair, r happens before r ; and (4) s and r are consistent with FIFO semantics (i.e., all the messages that were sent to the same destination as s, and were sent before s, are received before r).
Following are some examples of race sets:
ž Asynchronous message passing. The race sets for the receive events in Fig. 7.18 are as follows: race(r1 ) = {s2 } and race(r2 ) = race(r3 ) = race(r4 ) = {}. Note that s3 is not in the race set of r1 because s3 is sent to a different port and thus s3 is not open at r1. For the same reason, s4 is not in the race set of r3. Note also that s4 is not in the race set of r1, because FIFO semantics ensures that s2 is received before s4.
žSynchronous message passing. The race sets of the receive events in Fig. 7.19 are as follows: race(r1 ) = {s2 }, race(r2 ) = {}, race(r3 ) = {s4 }, and race(r4 ) = {}. Since the receive alternative for port p2 was open whenever thread T2 selected the receive alternative for port p1, the race set for r1 contains s2 and the race set for r3 contains s4. On the other hand, since the receive alternative for p1 was closed whenever thread T2 selected the receive alternative for p2, the race set for r2 does not contain s3.
žSemaphores. The race sets of the completion events in Fig. 7.20 are as follows: race(e1) = {p2} and race(e2) = race(e3) = race(e4) = {}. Note that since P () is not in the OpenList of e2, the race set for e2 does not contain p2. This captures the fact that the P () operation by T1 could start but not complete before the V () operation by T2 and hence that these operations do not race.
žLocks. The race sets of the completion events in Fig. 7.21 are as follows: race(e1) = {l3} and race(e2) = race(e3) = race(e4) = race(e5) = race(e6) = {}. Note that since T2 owned lock k when the operations for events e2, e3, and e4 were started, the race sets for e2, e3, and e4 are empty. This represents the fact that no other thread can complete a lock() operation on k while it is owned by T2.
ž Monitors. The race sets of the entry events in Fig. 7.22 are as follows: race(e1) = {c2}, race(e2) = race(e3) = {}race(e4) = {c5}, and race(e5) =
race(e6) = {}. Sending event c3 is not in the race set of e2 since c3 happened after e2. (Thread T2 entered monitor m at e2 and executed a signal operation that caused T1 to issue the call at c3.)
7.5.4 Timestamp Assignment
As we just saw, the definition of a race between sending events is based on the happened-before relation, which was defined in Section 6.3.3. In this section we present thread-centric and object-centric timestamp assignment schemes for capturing the happened-before relation during race analysis. A thread-centric
434 |
TESTING AND DEBUGGING CONCURRENT PROGRAMS |
timestamp has a dimension equal to the number of threads involved in an execution. An object-centric timestamp has a dimension equal to the number of synchronization objects involved. Therefore, a thread-centric scheme is preferred when the number of threads is smaller than the number of synchronization objects, and an object-centric scheme is preferred otherwise.
Thread-Centric Scheme The vector timestamp scheme described in Section 6.3.5 is thread-centric by our definition and can be used for race analysis. Recall that in this scheme each thread maintains a vector clock. A vector clock is a vector of integers used to keep track of the integer clock of each thread. The integer clock of a thread is initially zero and is incremented each time the thread executes a send or receive event. Each send and receive event is also assigned a copy of the vector clock as its timestamp.
Let T .v be the vector clock maintained by a thread T . Let f.ts be the vector timestamp of event f . The vector clock of a thread is initially a vector of zeros. The following rules are used to update vector clocks and assign timestamps to the send and receive events in asynchronous message passing programs:
1.When a thread Ti executes a nonblocking send event s, it performs the following operations: (a) Ti .v[i] = Ti .v[i] + 1; (b) s.ts = Ti .v. The message sent by s also carries the timestamp s.ts.
2.When a thread Tj executes a receive event r with synchronization partner s, it performs the following operations: (a) Tj .v[j ] = Tj .v[j ] + 1; (b) Tj .v = max(Tj .v, s.ts); (c) r.ts = Tj .v.
Figure 7.25a shows the timestamps for the asynchronous message passing program in Fig. 7.18. A timestamp scheme for synchronous message passing was also described in Section 6.3.5, but this scheme must be extended before it can be used for race analysis. The scheme in Section 6.3.5 assigns the same timestamp to send and receive events that are synchronization partners:
1.When a thread Ti executes a blocking send event s, it performs the operation Ti .v[i] = Ti .v[i] + 1. The message sent by s also carries the value of vector clock Ti .v.
T1 |
T2 |
T3 |
T1 |
T2 |
T3 |
||||||||||||
s2 [1,0,0] |
|
r1 [0,1,1] |
|
|
|
s1 [0,0,1] |
|
|
|
r1 [0,1,1] |
|
|
|
|
s1 [0,1,1] |
||
|
r2 [1,2,1] |
|
s2 [1,2,1] |
|
|
r2 [1,2,1] |
|
|
|||||||||
|
|
|
|
|
|
|
|
|
|
|
s3 [1,3,2] |
||||||
|
|
|
|
|
|
|
|
|
|||||||||
|
r3 [1,3,2] |
|
s3 [0,0,2] |
|
|
r3 [1,3,2] |
|
|
|||||||||
|
|
|
|
|
|
|
|
|
|
|
|
||||||
|
|
|
|
|
s4 [2,4,2] |
|
|
r4 [2,4,2] |
|
|
|||||||
s4 [2,0,0] |
|
r4 [2,4,2] |
|
|
|
||||||||||||
|
|
|
|
|
|
|
|
|
|
|
|
||||||
|
|
|
|
|
|
|
|
|
|
|
|
||||||
|
|
|
|
|
|
|
|
|
|
||||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(a) |
|
|
|
|
|
(b) |
|
|
|
|||||
Figure 7.25 Traditional timestamp schemes for asynchronous and synchronous message passing.
