• 沒有找到結果。

非決定性並行程式之動態測試

N/A
N/A
Protected

Academic year: 2021

Share "非決定性並行程式之動態測試"

Copied!
131
0
0

加載中.... (立即查看全文)

全文

(1)國立臺灣師範大學 資訊工程研究所博士學位論文. 指導教授:. 黃冠寰. 博士. 非決定性並行程式之動態測試 Dynamic Testing for Nondeterministic Concurrent Programs. 研究生:林哲生 撰 中華民國 一百零二 年 一 月.

(2) Table of Contents Table of Contents ............................................................................................................. i List of Figures .............................................................................................................. iii 中文摘要. vi. Abstract. vii. Chapter 1.. Introduction ............................................................................................. 1. 1.1.. Different types of testing for concurrent programs ................................. 2. 1.2.. Model-checking free techniques ............................................................. 4. Chapter 2.. Related Works ....................................................................................... 10. 2.1.. Reachability testing ............................................................................... 11. 2.2.. CHESS and dynamic partial order reduction ........................................ 13. Chapter 3.. Problems in Concurrent Testing ............................................................ 15. 3.1.. The termination problem....................................................................... 15. 4.1.. Boundless state reiteration problem ...................................................... 17. Chapter 4.. Dynamic Termination Decision ............................................................ 24. 4.1.. Termination-dependency relationship ................................................... 24. 4.2.. Without considering the termination-dependency relationship ............ 26. 4.3.. With considering the termination-dependency relationship ................. 29. Chapter 5.. Effective Test Set and Dynamic Effective Testing ................................ 42. 5.1.. The execution state and effective test set .............................................. 42. 5.2.. The framework for dynamic effective testing ....................................... 46. 5.3.. Transformation of SYN-sequences to implement dynamic effective testing .................................................................................................... 47. 5.4.. Implementation of transformation function Γ based on the partial ordering of synchronization events ....................................................... 65 5.4.1.. The information required about synchronization events for dynamic effective testing .............................................................. 65. 5.4.2.. Deriving a unique totally ordered sequence of execution states from a SYN-sequence ................................................................... 69 i.

(3) 5.5.. Event compression for busy-waiting loops ........................................... 75. 5.6.. Derivation of A Simplified Reachable State Graph .............................. 79. Chapter 6.. Extension for Single-Port-Multiple-Receivers Message-Passing Concurrent Programs ............................................................................ 85. 6.1.. The shortcoming of traditional race-table algorithm ............................ 86. 6.2.. The proposed solution ........................................................................... 88. 6.3.. A Running Example .............................................................................. 91. 6.4.. The correctness of the proposed scheme .............................................. 93. Chapter 7.. Implementation and experimental results ........................................... 102. 7.1.. Experimental results of dynamic termination decision ....................... 102. 7.2.. Experimental results of dynamic effective testing .............................. 103. Chapter 8.. Conclusions and future work .............................................................. 110. References. 112. Appendix A. 120. Appendix B. 122. ii.

(4) List of Figures Figure 1. Two concurrent programs with infinite SYN-sequences ................................ 5 Figure 2. RV-diagrams for SYN-sequence S2 and Sj .................................................. 22 Figure 3. Pretested and posttested loops ...................................................................... 24 Figure 4. Example of a concurrent program with two processes ................................. 25 Figure 5. Example of dynamic termination dependency ............................................. 26 Figure 6. Example of static termination dependency ................................................... 26 Figure 7. Loops in multiple-selection statements ........................................................ 26 Figure 8. Modified pretested loop ................................................................................ 28 Figure 9. (A) Example of a concurrent program with three processes and (B) its termination-dependency graph.............................................................. 31 Figure 10. (A) Example of a concurrent program that might form a livelock and (B) its termination-dependency graph.............................................................. 32 Figure 11. Loops in multiple-selection statements ....................................................... 32 Figure 12. Execution-status transitions of a loop without nesting ............................... 35 Figure 13. Modified pretested loop that considers the loop-dependency relationship 40 Figure 14. (A) Example of a concurrent program comprising dynamic and static termination dependencies and (B) its termination-dependency graph .. 40 Figure 15. (A) Example of a concurrent program comprising dynamic and static termination dependencies and (B) its termination-dependency graph .. 41 Figure 16. An example of the reachable state graph of the execution of the concurrent program shown in Figure 1A ................................................................ 44 Figure 17. The proposed framework for dynamic effective testing ............................. 47 Figure 18. Conventional operation of reachability testing to explore different interleavings and some examples.......................................................... 50 Figure 19. Definition of the SYN-sequence transformation function Γ ...................... 52 Figure 20. Reiterated states in a SYN-sequence .......................................................... 58 Figure 21. S and Sy with MCFP M ............................................................................... 60 iii.

(5) Figure 22. An example of the race graph ..................................................................... 63 Figure 23. The transformed concurrent program of Figure 1B .................................... 67 Figure 24. Example of a concurrent program .............................................................. 70 Figure 25. SYN-sequence S and its partial order graph ............................................... 71 Figure 26. Two totally ordered sequences of events from the same partial order graph ............................................................................................................... 72 Figure 27. Partial order graph and race graph for a program with busy-waiting loops 77 Figure 28. The simplified reachable state graph of the execution of the concurrent program shown in Figure 1A ................................................................ 80 Figure 29. The simplified reachable state graph of the execution of the concurrent program shown in Figure 23 ................................................................. 84 Figure 30. An example SYN-sequence that contains multiple receivers ..................... 88 Figure 31. An example of applying a conventional race-table algorithm for the race analysis of the SYN-sequence in Figure 30 .......................................... 88 Figure 32. The partial order graph of the SYN-sequence in Figure 30........................ 91 Figure 33. Three partial order graphs and their race tables derived from Figure 32 ... 91 Figure 34. The generated race variants. ....................................................................... 92 Figure 35. (A) Another example SYN-sequence that contains multiple receivers. (B) The partial order graph of Figure 30. (C) The partial order graph of Figure 35A. (D) The MCFP of the SYN-sequences in Figure 30 and Figure 35A. ........................................................................................... 94 Figure 36. An example is with two SYN-sequences Sx and Sy. There must be two receiving events, which are not-happened-before events (∆), racing for the same message from the sending event in SMCFP. ............................. 97 Figure 37. An example is with contradiction in SYN-sequence Sy'. According to the nature of the FIFO message ordering scheme, the receiving event rβ must not receive the message from sp since rβ has happened-after rα. .. 98 Figure 38. For a sending event sp, if (1) the message from sp is raced by the events in ∆ and (2) a sending event sk sent message to the same port as sp and happened-before sp, then the message from sk would be received by the event in SMCFP. ...................................................................................... 99 Figure 39. Program A3 ............................................................................................... 120 iv.

(6) Figure 40. Program R1 ............................................................................................... 120 Figure 41. Program R2 ............................................................................................... 120 Figure 42. Program R3: Peterson’s algorithm ............................................................ 121 Figure 43. A message-passing concurrent program ................................................... 122. v.

(7) 中文摘要 對於並行程式而言,非決定性行為代表即使給予相同的輸入值,在多次執行 的過程中仍可能產出不同的執行結果。非決定性行為導因於:並行程式給予相同 輸入的多次執行中,內部指令具有多種執行順序。因此如何儘可能地找出所有執 行順序、達到完整測試,一直是測試並行程式時的主要課題。然而當程式中具有 忙碌等待迴圈而衍生重複進入相同狀態時,程式執行將衍生無窮多種執行順序, 造成無法對所有執行順序完整測試。此論文提出一動態測試方法,藉由執行所有 可達的狀態與狀態間轉移,充分測試具有迴圈行為的並行程式。相較於驗證並行 程式時常用的模型檢驗方法,本文提出的測試方法具有:毋須對受測程式預先靜 態分析,並降低與受測程式間的程式語言耦合度等優點。測試過程中僅需分析程 式執行後產生的同步行為序記錄,用以分析執行中的競爭行為並限制執行時的狀 態走訪情形;因此我們提出的測試方法將更易於根據需求移植至其他程式語言。 最後透過實驗數據,我們將實證此種針對具有迴圈行為並行程式的充分測試可被 系統化地完成。 關鍵字:並行程式、非決定性行為、並行測試、可達性測試法、終止決定、訊息 關鍵字 傳遞. vi.

(8) Abstract Concurrent programs exhibit nondeterministic behavior in that multiple executions thereof with the same input might produce different sequences of synchronization events and different results. This is because different executions of a concurrent program with the same input may exhibit different interleavings. Thus, one of the major issues in the testing of concurrent programs is how to explore different interleavings or exhaust all the possible interleavings of the target programs. However, for terminating concurrent programs that have cyclic state spaces due to using iterative statements such as busy-waiting loops, they might have an infinite number of feasible synchronization sequences; that is, there is an infinite number of possible interleavings, which makes it impossible to explore all the possible interleavings for this type of concurrent program. To overcome this problem, we propose a testing scheme called dynamic effective testing that can perform state-cover testing for nondeterministic terminating concurrent programs with an infinite number of synchronization sequences. Dynamic effective testing does not require static analysis of the target concurrent program or the assistance of a model checker, and thus is loosely coupled to the syntax of the target concurrent program. It only needs to analyze sequences of synchronization events produced by the execution of the concurrent programs for race detection and state-traversal control. Therefore, the method is easy to port to different programming languages. The implementation and experimental results obtained with real code demonstrate that source-code-level dynamic testing can be systematically performed on nondeterministic concurrent programs with infinite synchronization sequences. vii.

(9) Keywords: Concurrent program, Nondeterministic behavior, Concurrent testing, Reachability testing, Termination decision, Message passing. viii.

(10) Chapter 1. Introduction 1. Concurrent programming is becoming commonplace in modern computing. A. concurrent program contains two or more processes or threads that execute concurrently and work together to perform some task. Multiple executions of a concurrent program P with the same input might produce different results. This so-called nondeterministic behavior [1,2] means that when testing P with input X (which is a sequence of inputs for processes in P), a single execution is insufficient to determine the correctness of P with X. Even if P has been executed successfully many times with input X, it is possible that a future execution of P with X will produce incorrect results. An execution of a concurrent program exercises a sequence of synchronization events called a synchronization sequence (or SYN-sequence). Examples of process synchronization include “wait” (acquire) and “signal” (release) primitives applied to a shared semaphore, monitor-entry procedures, sending and receiving of messages, and general sharing of memory [3,4]. A concurrent program exhibits nondeterministic behavior due to process scheduling, differences in the speed of the processors, etc. Because of this nondeterministic behavior, different executions of P with the same input X might produce distinct SYN-sequences. In general, for a concurrent program with the same input, the same interleaving always produces the same SYN-sequence. In this thesis, a test of a concurrent program P with input X refers to a single execution of P with input X to obtain a SYN-sequence, with checking of the execution result. A duplicating test means that different tests of P with input X produce the same SYN-sequence. Exhaustive testing of P with input X refers to. 1. Keywords: Concurrent program, Nondeterministic behavior, Concurrent testing, Reachability testing, Termination decision, Message passing. 1.

(11) 2. performing many tests that exercise all feasible SYN-sequences of P with input X. 1.1. Different types of testing for concurrent programs Dynamic testing in software engineering refers to examining the physical response of a system to variables that change with time, and is used to test the dynamic behavior of code [5]. In dynamic testing the software must actually be compiled and run. This involves running the software, providing input values, and checking if the output is as expected. Dynamic testing methodologies include unit testing, integration testing, system testing, and acceptance testing. Source-code-level dynamic testing is considered to be an important step in the software life cycle (software development process) [6]. To deal with nondeterministic behavior, a simple approach to testing a concurrent program P involves executing P with a fixed input many times in the hope that this will expose faults. This type of testing, called nondeterministic testing, is easy to perform, but it is usually very inefficient and has two major problems: (1) some feasible SYN-sequences of P with input X might be executed many times, and (2) some feasible SYN-sequences might never be executed [7,8]. Nondeterministic testing is applied in commercial applications for the stress testing of concurrent software, in which the software is executed under heavy loads with the hope of producing an erroneous interleaving. This form of testing is inadequate, with such stress testing not providing any notion of coverage with respect to concurrency, since even after executing tests lasting for days, the fraction of explored interleavings remains unknown and is likely to be very low. An improved scheme of nondeterministic testing is random testing, which increases the chance of exercising different SYN-sequences by scheduling the processes randomly or inserting delay.

(12) 3. statements into the concurrent program with the amount of delay chosen randomly [7,9,10,11,12]. To deal with the nondeterministic behavior of distributed programs, some researchers have developed static analysis techniques for detecting certain classes of anomalies in concurrent programs [13,14,15]. Instead of executing the target program, an attempt is made to ensure that the program cannot enter certain predefined states that are known to produce errors. Static analysis is used to detect synchronization and data-usage errors in concurrent programs, where synchronization errors include deadlock and “wait forever,” while data-usage errors include the usual sequential data-usage errors, such as reading an uninitialized variable, and parallel data-usage errors. Static analysis does not involve actual program execution although it can involve some form of conceptual execution. An alternative approach is called deterministic testing, which uses SYN-sequences selected from a static model obtained by static analysis techniques [16,17,18] or SYN-sequences selected by the tester [19,20,21]. A concurrent program P can be deterministically tested as follows: (1) select a set of tests, each of the form (X,S), where X and S are an input and a SYN-sequence of P, respectively, and (2) for each selected test (X,S), perform a deterministic execution of P with input X according to S. The forced execution determines whether S is feasible for P with input X. However, accurate static models are often difficult to build for dynamic behavior and the set of SYN-sequences selected by the tester is not guaranteed to detect all the potential faults. Several approaches have been proposed for formal verification. One of the methods is model checking, in which the behavior of a system is verified through exhaustive enumeration of all the states reachable by the system or a model of the system [22,23]. In.

(13) 4. essence, a model checker is a procedure that decides whether a given structure M is a model of a logical formula φ; that is, whether M satisfies φ (abbreviated as M ⊨φ). Intuitively, M is an abstract model of the system (typically a finite automaton-like structure) and φ (typically drawn from temporal logic) specifies a desirable property. In order to use a model checker to verify a program, the program must be modeled using a specific description language. Manually producing software models is labor intensive and error prone, and so a significant amount of research has focused on abstraction techniques for automatically or semiautomatically producing such models. Notable work of this type includes Java PathFinder [24,25], MAGIC [26,27], and CHESS [28,29]. However, these tools are generally language dependent and have the overhead of building a Kripke structure (usually termed a model), which is a type of nondeterministic finite state machine, used in model checking to represent the behavior of the system. 1.2. Model-checking free techniques Several systematic and exhaustive dynamic testing techniques for concurrent programs have been developed to deal with the nondeterministic behavior of concurrent programs [30,31,32,33,34,35,36,37]. These techniques are model-checking free in that they do not need to employ a model checker to explore the feasible interleavings of the execution of the concurrent program. They explore interleavings of a concurrent program by systematically switching threads at synchronization points. However, the common assumption of these techniques is that the execution of the target concurrent program has a finite number of feasible SYN-sequences. Problems are encountered.

(14) 5. when applying dynamic testing to terminating. 2. concurrent programs where. synchronization events are located in iterative statements, such as while loops, which might make the number of feasible SYN-sequences infinite. The testing process cannot stop because the testing tool tries to enumerate all the feasible SYN-sequences.. S0,0 S0,1. S0,0 S0,1. a is a shared variable whose initial value is 0. Process P0 Process P1 ... ... do { t = a; S1,0 a = 1; } while(t==0); (A) a is a shared variable whose initial value is 0. Process P0 Process P1 ... ... do { a = 1; S1,0 do { a = 0; } while(a==0); } while(a==0); (B). Figure 1. Two concurrent programs with infinite SYN-sequences We give two examples to illustrate that terminating concurrent programs with synchronization events in iterative statements may have an infinite number of SYN-sequences. Note that the synchronization operations we consider in the two examples are shared variable read and write operations. In the first example, the number of SYN-sequences of the concurrent program is infinite under an unfair schedule. In Figure 1A, consider only statements S0,0, S0,1, and S1,0. One of the shortest execution sequences of this concurrent program is {S1,0, S0,0, S0,1}, and a longer one is {S0,0, S0,1, S1,0, S0,0, S0,1}. It is obvious that the execution of S1,0 is the condition required to allow the busy-waiting loop of statements S0,0 and S0,1 to exit. It is trivial that the loop within statements S0,0 and S0,1 is not infinite since S1,0 of process P1 will eventually be executed in the normal task scheduling of the operating system. In order for dynamic testing to 2 The execution of a terminating concurrent program is expected to terminate. In this thesis, we will not be concerned with nonterminating programs such as reactive systems which need to interact with their environment frequently and often do not terminate..

(15) 6. exhaustively execute all of the feasible SYN-sequences, the loop for statements S0,0 and S0,1 must be iterated an infinite number of times. That is, it should test execution sequences {(S0,0, S0,1,)i S1,0, S0,0, S0,1}, where 0≤i≤∞ (note that (S0,0, S0,1,)i means that “S0,0, S0,1,” repeats i times). It is obviously impossible to exhaust all the feasible SYN-sequences since there is an infinite number of them. Note that the schedule is unfair if process P1 is unable to make progress for a long time. In the second example, the infinity of SYN-sequences is caused by busy-waiting loops of two processes. For the concurrent program shown in Figure 1B, one of the shortest execution sequences of this program is {S1,0, S0,0, S0,1, S1,1}. However, we can easily see that the feasible execution sequences contain {(S0,0, S1,0, S0,1, S1,1,)i S1,0, S0,0, S0,1, S1,1}, where 0≤i≤∞. This makes the exhaustive exploration of all feasible SYN-sequences impossible. This thesis would propose a systematic and model-checking-free scheme for performing dynamic testing on terminating concurrent programs with an infinite number of SYN-sequences. The proposed scheme can be applied to terminating concurrent programs that have cyclic state spaces due to the use of iterative statements such as busy-waiting loops. Our scheme is called dynamic effective testing, and it can support the testing of concurrent programs where the synchronization events are located in any type of iterative statement. The scheme can also be applied to programs where the repetitive issues of synchronization events are caused by recursive calls. The synchronization operations modeled in this thesis are shared memory read and write operations. In general, modeling all process synchronizations in a concurrent program as operations on shared data (or shared memory) is not restrictive, since many.

(16) 7. synchronization primitives can be reduced to operations on shared data [38]. To demonstrate the portability of our scheme, we also report experiences and experimental results obtained in porting the proposed testing scheme to testing semaphore-based concurrent programs and service-oriented architecture applications. The basic idea of dynamic effective testing is to apply a subset of the feasible tests, since there is usually an infinite number of feasible SYN-sequences. For a concurrent program P with input X, if there is a finite number of feasible execution states, it can be shown that the execution of P with X can be represented by a deterministic finite automaton3 (DFA) F in which the finite sets of states are the feasible execution states and the transitions are labeled with events. The SYN-sequences of P with X are usually infinite if there exists a cycle in F. This makes the exhaustive testing of P with X impossible. Also, it is usually impossible to traverse all the states in F in a single test of P with X. The effective test set of P with X consists of a finite set of tests ET of P with X. The associated SYN-sequences of ET traverse all the states and transitions in F. Dynamic effective testing of P with input X involves dynamic testing of an effective test set of P with input X. Actually, dynamic effective testing is a scheme that controls the execution of the target program to cover some effective test set so that we can conduct a state- and transition-cover testing for the concurrent program with a given input. The method of dynamic effective testing proposed in this thesis only has to analyze the SYN-sequences that are collected during the dynamic testing of the concurrent program – static analysis of syntax and semantics of the target concurrent program is unnecessary. The analysis of SYN-sequences has two goals. First, we have to perform. 3. Note that the DFA is not equivalent to the Kripke structure. We define our DFA in Chapter 5..

(17) 8. race analysis to explore interleavings which are used to guide the tested program to explore different states. Second, we can obtain a sequence of execution states from SYN-sequences so that we can identify reiterated states from them. According to these identified reiterated states, we may either truncate some events from the SYN-sequence or abandon the entire SYN-sequence, so as to achieve state-cover testing from an infinite number of SYN-sequences. Note that we define states traversed in a SYN-sequence S as states which can be reached by all the possible totally ordered sequences4 of S. Also, the operation of dynamic effective testing for a concurrent program P with input X does not need to construct the associated DFA of P with X. That is, we do not need to build up the model or Kripke structure of the target concurrent program before or during performing dynamic effective testing; therefore, it is model-checking free. As long as the SYN-sequences have an identical format, dynamic effective testing can be applied to different programming languages using a single implementation of the SYN-sequence analyzer; thus, it is language independent. To the best of our knowledge, our scheme is the first systematic and model-checking-free dynamic testing scheme that can apply state- and transition-cover dynamic testing on a nondeterministic concurrent program containing an infinite number of SYN-sequences without requiring static analysis of the source code or the assistance of model checking. In addition to performing dynamic testing on concurrent programs, the effective test set collected during dynamic effective testing of a concurrent program can be used to construct the DFA that represents the execution of the program. The DFA can then be used to support debugging of the program.. 4. Refer to Section 5.4.2 for the totally ordered sequences of a SYN-sequence..

(18) 9. This thesis is organized as follows. Chapter 2 surveys previous work on dynamic testing for concurrent programs. In Chapter 3 we present a problem called boundless state reiteration that occurs when a concurrent testing tool attempts to explore interleavings for a concurrent program with an infinite number of SYN-sequences or interleavings. The effective test set and dynamic effective testing are formally defined in Chapter 4. The framework and related algorithms for implementing dynamic effective testing are presented in Chapter 5. Chapter 6 shows how to test a single-port-multi-receiver message-passing program based on the extension of race table algorithm. The implementation details and experimental results are presented in Chapter 7, and conclusions are drawn in Chapter 8..

(19) Chapter 2. Related Works Work on dynamic testing of concurrent programs without the assistance of static analysis and model checking includes several random testing techniques for concurrent programs (these techniques are also called noise makers). Although random testing for concurrent programs was proposed in the 1980s, a considerable amount of related work has been published since 2000. Stoller proposed a scheme that transforms a given Java program by inserting calls to a scheduling function at selected points [10]. The scheduling function either does nothing or causes a context switch. Because it does not control the schedule directly, it can only advise the scheduler to make a thread switch; that is, it cannot force a thread switch. Ben-Asher et al. proposed a similar scheme, in which random context switching is performed at accesses to contended shared variables [11]. Edelstein et al. designed a tool to seed the tested program with some primitives at shared memory accesses and events [12]. At run-time, the tool makes random or coverage-based decisions as to whether the seeded primitive is to be executed, which increases the probability of finding concurrent faults. Sen’s team developed a tool for testing concurrent Java programs, called CalFuzzer [39,40], that can explicitly control the scheduler based on random schedules. The CalFuzzer applies dynamic partial order reduction to avoid the unnecessary interleavings for independent statements. In addition, it attempts to force schedules in which concurrency bugs can be detected. ConTest is a tool from IBM Research for testing multithreaded Java applications [41,42] that generates different interleavings to reveal concurrent faults by seeding the program with conditional sleep statements at shared memory access and synchronization events. Since it cannot guarantee that all the possible interleavings are explored, it is recommended 10.

(20) 11. that the programmer runs the testing tool for a long time (e.g., overnight) in the hope of executing all possible interleavings [43]. Random testing techniques are generally not systematic schemes. The proposed schemes attempt to generate different interleavings as rapidly as possible, but they can neither guarantee state-cover testing nor determine if they have actually achieved state-cover testing during a testing procedure. 2.1. Reachability testing Systematic and exhaustive techniques have been developed for testing nondeterministic concurrent programs. to explore different interleavings. or. SYN-sequences. Hwang et al. proposed the idea of reachability testing for dealing with the problem of nondeterministic behavior of concurrent programs [30,31]. Reachability testing is an approach that can perform dynamic testing on a concurrent program and explore different interleavings or SYN-sequences without static analysis of the target source program. The SYN-sequences generated during testing are analyzed so that the subsequent tests explore different interleavings, thereby producing different SYN-sequences. The major limitation of reachability testing is that it is problematic to apply to testing concurrent programs with an infinite number of SYN-sequences. Hwang et al. showed how to perform reachability testing of concurrent programs that use read and write operations on shared memory and semaphores for synchronization [30,31]. Tai described how to apply reachability testing to concurrent programs that use message passing [33]. Lei and Tai showed how to exercise all the possible SYN-sequences for a message passing program [34]. The sequences/variants must be stored, however, to prevent a sequence from being exercised more than once. In [35,36], a new reachability testing algorithm is presented that exercises all the (partially ordered).

(21) 12. SYN-sequences of a program exactly once, without requiring the sequences to be stored. The true-concurrency model used in [35,36] guarantees that reachability testing never exercises two sequences that differ only in the order of concurrent events. Reference [34] presents a general execution model that allows the algorithm in [35,36] to be applied to programs that use semaphores, locks, monitors, and/or message passing. Hwang et al. described how to apply reachability testing to the exhaustive testing of a client/server database application that exhibits nondeterministic behavior [32]. In [44], it presents the design and implementation of a distributed reachability testing algorithm for a cluster of workstations. Lei et al. presented a testing strategy, called t-way reachability testing, that selectively exercises a subset of SYN-sequences [45]. It has been proven that reachability testing can derive all the feasible SYN-sequences of a concurrent program if the program has a finite number of SYN-sequences with a given input [31,32]. Reachability testing terminates when all the SYN-sequences have been tested. However, the common assumption of these works is that the concurrent program does not contain busy-waiting loops or iterative statements in which shared objects are repeatedly accessed and updated at the same program location. In cases where this assumption does not hold, reachability testing cannot stop because it continues to explore some identical program states. We illustrate why concurrent programs with iterative statements cannot be handled by reachability testing in Chapter 3. Sen and Agha presented a testing scheme that uses simultaneous concrete and symbolic executions to consider both inputs and schedules for message-passing distributed programs [37]. Their programming model requires that the number of possible inputs and schedules that can be exhibited by the target concurrent program be finite. In general,.

(22) 13. all the above schemes are model-checking free. 2.2. CHESS and dynamic partial order reduction CHESS is a concurrent testing tool that repeatedly runs a concurrent test whilst ensuring that every run takes a different interleaving [28,29]. If an interleaving results in an error, CHESS can reproduce the interleaving (for improved debugging). CHESS uses model checking techniques to systematically generate all interleavings of a given scenario. The implementation of CHESS needs a thin wrapper layer between the program under test and the concurrency API. Musuvathi and Qadeer proposed the concept of fair stateless model checking, a technique for systematic testing of nonterminating programs [46]. Related algorithms were implemented in CHESS. They also proposed iterative context-bounding for effectively searching the state space of a multithreaded program [47]. State-space storage and state matching are important in software model checking. During the state search, once states have been visited they are stored. It is impossible to determine which states are visited only once before the search is completed. Storing states avoids redundant explorations of parts of the state space. It would be preferable not to store them in order to decrease the memory requirements. State-space caching creates a restricted cache of selected system states that have already been visited [48]. When the cache fills up, old states are deleted to accommodate new ones. Godefroid et al. proposed a method which utilizes the properties of sleep sets and show that the memory requirement of the cache can be strongly decreased [49]. Dillinger et al. proposed a scheme by employing a compact hash table to offer a faster state matching with good accuracy [50]. In dynamic effective testing, only reiterated states discovered in a single.

(23) 14. SYN-sequence are stored. Since most of the states are reached only once, there is no need to allocate a large memory space to store identified reiterated states. Dynamic partial order reduction uses dynamic information to reduce the search space of states [51]. Dynamic partial order reduction and reachability testing share a similar operation model. They start by executing the concurrent program until completion. Information that was collected during the run is used to identify branching points and the branching points are used to derive alternative executions that need to be explored. In [51], it only supports stateless explorations for acyclic state spaces. In the presence of cycles, the depth of the search has to be bounded somehow. Previous works on reachability testing also only support testing of concurrent programs with acyclic state spaces [31,32,36]. Yang et al. proposed a solution which applies the dynamic partial order reduction to concurrent programs with cyclic state spaces [52]. It consists of a light-weight method for storing abstract states and a stateful dynamic partial order reduction algorithm. As we have mentioned, dynamic effective testing only has to deal with recording and matching of reiterated states..

(24) Chapter 3. Problems in Concurrent Testing In this chapter we show the problems in testing concurrent software. The first problem is that the execution of target concurrent program may not stop due to some processes getting stuck in infinite loops. Thus, we have to detect t infinite loops or livelocks spontaneously during the execution of target concurrent programs. We proposed a scheme called dynamic termination decision to cope with this problem. Even if no executions of all the processes in a concurrent program get stuck in an infinite loop, the existence of the busy-waiting loop makes the number of feasible SYN-sequences infinite. 3.1. The termination problem One of the problems in performing dynamic testing on concurrent programs with busy-waiting loops or iterative statements is that the concrete execution of the target concurrent program might not stop due to some processes getting stuck in infinite loops or some of the processes forming livelocks. The traditional way of handling this problem in dynamic testing – interrupting the execution of the concurrent program whenever a predefined maximum execution time is exceeded – has two drawbacks: (1) the size of memory needed to store the event history (SYN-sequence) of the tested program is always prohibitively large since the busy-waiting loops usually produce events continuously (note that the recorded event history is used to debug the target program when an unexpected execution result is discovered during the testing process [32,38]), and (2) the cost of the execution after some processes get stuck in infinite loops or form livelocks represents pure overhead. Termination analysis is traditionally used to decide if a program can terminate [54,55], 15.

(25) 16. but this is a static program analysis scheme and hence is inappropriate for dynamic testing. Besides, almost all static termination analysis schemes support only sequential programs, and simply encoding all possible interleaves between processes as a single sequential program does not represent a scalable and practical solution. The halting problem is one of the simplest unsolvable problems [61], as proved by Alan Turing in 1936. We say that the halting problem is undecidable over Turing machines. Programs contain loops that can be infinite or finite. An obvious method for testing if a program contains an infinite loop is to simply run the program with the given input. It is self-evident if the program stops, but if it does not stop within a reasonable amount of time, we cannot conclude that it will never stop since it is possible that we did not wait long enough. In this thesis, we propose a scheme for applying the dynamic termination decision to nondeterministic concurrent programs with busy-waiting loops or iterative statements. The scheme is designed with threads or processes that spontaneously detect infinite loops or livelocks during dynamic testing. We have developed protocols to modify the functions for evaluating the Boolean predicates of loops, so that processes that get stuck in infinite loops or form livelocks can stop the execution themselves. This is accomplished by including codes in the functions that evaluate the Boolean predicates of loops that examine the state of program execution in an attempt to determine if the program is stuck in an infinite loop or has formed a livelock. Loops in a concurrent program are classified into two categories based on whether their termination (1) is not affected by other loops or (2) depends on other loops. Different protocols are proposed for the dynamic termination decision of the two types.

(26) 17. of loop. In practice, several loops of the second type sometimes can form a livelock. The termination dependency relationship of loops is identified statically according to the context of the concurrent program. Some codes are added to the program so that we can obtain the dynamic states of loops in the run time. Then, the decision of whether a loop is infinite or several loops form a livelock is made according to the dynamic states and the dependency relationship of loops. This is a heuristic algorithms for coping with the obvious impossibility of developing a general solution for the spontaneous detection of an infinite loop. Although our scheme will raise false alarms in certain cases, we believe that it will identify a large class of infinite loops and livelocks; moreover, the false alarms might stimulate the programmer to improve the program code. Also, we provide theorems to illustrate the capability of the proposed protocols. We implement and apply the proposed scheme to dynamic testing, with its feasibility being demonstrated by experimental results. 4.1. Boundless state reiteration problem Besides the termination problem, we show why aggressive schemes such as reachability testing, which aim at exploring all the SYN-sequences of a concurrent program, encounter problems when testing a concurrent program with an infinite number of SYN-sequences. When a stateless concurrent testing tool tries to enumerate all the feasible SYN-sequences of a concurrent program that has cyclic state spaces due to using iterative statements such as busy-waiting loops, it may repeatedly guide the execution of the concurrent program to reach the same execution state and make the.

(27) 18. testing process endless because the tool does not consider the execution states5. We call this problem boundless state reiteration. We believe that this problem arises for any concurrent testing tool that tries to test all the possible interleavings or SYN-sequences. Assume that S is the SYN-sequence of an execution of concurrent program P with input X. Reachability testing of P with input X involves the following steps6: 1.. Obtain a SYN-sequence S by tracing a nondeterministic execution of P with X.. 2.. Use S to derive a set of prefixes of other feasible SYN-sequences of P with input X. Such prefixes (called race variants of S) are derived by changing the outcome of race conditions in S. An execution that follows a race variant of S will always exercise a SYN-sequence that differs from S.. 3.. For each new race variant R derived in step 1, perform a prefix-based replay of P with input X and R to execute and collect an additional SYN-sequence for P with input X. The prefix-based replay of P with a race variant R comprises two phases: a.. Replay phase: control the execution of the program by following the execution order specified in R.. b.. Monitor phase: execute the program without any control and record subsequent synchronization events after replaying R.. 4.. Repeat steps 2 and 3 for each new SYN-sequence collected in step 3.. Note that R is not a complete SYN-sequence of an execution of P. Thus, after replaying R, we have to record the subsequent synchronization events to obtain a SYN-sequence of an execution of P. This performs a test on the target concurrent program. For performing the prefix-based replay of a race variant, an entry protocol and an exit protocol must be inserted before and after each synchronization event in the original 5 6. We provide the formal definition of the execution state of the concurrent program in Chapter 5. Because of the space limitation, we do not have enough space to give the formal definitions of race variant and prefix-based replay. Refer to [32] for the formal definitions of them..

(28) 19. program, respectively [30,32]. The above algorithm has a problem if the concurrent program contains iterative statements and has an infinite number of feasible SYN-sequences. In this situation the queue used for storing race variants will increase monotonically such that reachability testing will not terminate, and hence it will exhaust the memory space of the system. We now demonstrate that reachability testing cannot be applied to test this type of concurrent program. One of the schemes for deriving race variants from a SYN-sequence S involves constructing the RV-diagram (“race-variant-diagram”) for S, which is a tree. The nodes in the RV-diagram for S are generated by considering all possible interleavings of the synchronization events in S. Note that this is an n-ary tree if the number of concurrent processes is n. The path from the root node of the RV-diagram of S to another node is a totally ordered sequence of the synchronization events in S and represents a feasible prefix or race variant of S. The RV-diagram scheme was first proposed in [30,31], in which the version number of a variable access is used to determine if other race outcomes are present in the nodes of the RV-diagram. Let P be a concurrent program that includes read and write operations. Each shared variable in P is assigned a version number that is initialized to 0 and incremented by 1 after each write operation on it. An execution of P involves two types of synchronization events: read and write. A read event is denoted as R(U,V), which refers to a read operation on a variable named U with a version number of V, and a write event is denoted as W(U,V). Thus, if W(A,1) and R(A,1) are issued by different processes in the concurrent program, W(A,1) occurs before R(A,1) but W(A,1) and R(A,1) have a race condition that allows the read to occur before the write..

(29) 20. We have already mentioned that the possible execution sequences of the program in Figure 1A include {S1,0, S0,0, S0,1} and {S0,0, S0,1, S1,0, S0,0, S0,1}. The corresponding SYN-sequence of the execution sequence {S1,0, S0,0, S0,1} is S17, where S1(P0)8={R(a,1)} and S1(P1)={W(a,1)}; this is because S1,0 was executed before S0,1. Since the read operation in S0,1 reads the value written by the write operation in S1,0, the version number of the read operation in S0,1 is 1. Analogously, the SYN-sequence of {S0,0, S0,1, S1,0, S0,0, S0,1} is S2, where S2(P0)={R(a,0), R(a,1)} and S2(P1)={W(a,1)}. Finally, the SYN-sequence of {(S0,0, S0,1,)i S1,0, S0,0, S0,1} is Si, where Si(P0)={[R(a,0),]i R(a,1)} and Si(P1)={W(a,1)}. Reachability testing will test execution sequences {(S0,0, S0,1,)i S1,0, S0,0, S0,1}, where 0≤i≤∞; that is, it will test SYN-sequences Si(P0)={[R(a,0),]i R(a,1)} and Si(P1)={W(a,1)}, where 0≤i≤∞. It is obvious that there is an infinite number of feasible SYN-sequences. We now prove that reachability testing employing the RV-diagram as its race analyzer will not terminate when it is applied to this type of program. We assume that the first nondeterministic execution of the program in reachability testing yields SYN-sequence S2. Figure 2A shows the RV-diagram generated by analyzing SYN-sequence S2. Note that an edge labeled with P0 (or P1) means that process P0 (or P1) executes the next synchronization event. Also, only write operations increase the version number of a variable by 1. Two race variants are derived since there are two race-variant nodes N1 and N2 in the constructed RV-diagram. The corresponding race variant for node N1 is RV1, where RV1(P0)={R(a,1)} and RV1(P1)={W(a,1)}. The path from the root node to N1 represents an execution in which process P1 first performs W(a,1) and then process 7 8. Note that we only store operations of shared variable a in the SYN-sequence. S1(P0) and S1(P1) represent a sequence of synchronization events by processes P0 and P1, respectively..

(30) 21. P0 performs R(a,1). However, in the original SYN-sequence S2, the version number of the first event of process P0 is 0. If the version number is different from the original execution, then the node is a race-variant node. Thus, N1 is a race-variant node. Similarly, N2 is also a race-variant node. The corresponding race variant for node N2 is RV2, where RV2(P0)={R(a,0), R(a,0)} and RV2(P1)={}. The prefix-based replay of RV2 will produce a SYN-sequence Sj, where Sj(P0)={R(a,0), [R(a,0),]j R(a,1)} and Sj(P1)={W(a,1)}, and j≥1. Figure 2B shows the RV-diagram generated by analyzing SYN-sequence Sj. The corresponding race variant for node N3 is RV3(P0)={R(a,0), [R(a,0),]j+1} and RV3(P1)={}. The prefix-based replay of RV3 will produce a SYN-sequence Sk, where Sk(P0)={R(a,0), [R(a,0),]j+1 [R(a,0),]k R(a,1)} and Sk(P1)={W(a,1)}, and k≥0. This process is repeated, with reachability testing continuing to generate longer and longer SYN-sequences. It is obvious that reachability testing will not stop. In our example, synchronization event R(a,0) is in a busy-waiting loop and does not alter the execution state. Thus, boundless state reiteration occurs. In general, boundless state reiteration in dynamic testing occurs when processes of the target concurrent program involve iterative statements that can issue synchronization events repetitively. When boundless state reiteration occurs, reachability testing will try to generate an infinite number of SYN-sequences even though only a finite number of SYN-sequences can actually be exercised in a fair scheduling when the concurrent program is executed..

(31) 22. Race-variant node. (0,0) (0). Prefix node. P0. (0,0) (0) P0. Number of executed events vector. P1. (1,0) (0). Variables version vector P1. P0. … P1. … (1,0) (0) N2. P0. (0,1) (1) P1. (2,0) (0). N1. (1,1) (1). P0. (1,1) (1). P0. (j,0) (0) P1. N3 P0 (j+1,0) (0). (j,1) (1) P0. (2,1) (1). (j+1,1) (1). S2(P0) = {R(a,0), R(a,1)} S2(P1) = {W(a,1)}. Sj(P0) = {R(a,0), R(a,0)j, R(a,1)} Sj(P1) = {W(a,1)}. (A). (B). Figure 2. RV-diagrams for SYN-sequence S2 and Sj In addition to the RV-diagram scheme, there are other schemes for deriving race variants. According to [32], an algorithm that constructs a race graph is much more efficient than an algorithm that constructs an RV-diagram. However, since race analysis based on the race graph also does not consider the execution state, it cannot avoid boundless state reiteration. The same problem is also unavoidable in the scheme that derives race variants by constructing a race table [35,36]. In reachability testing, boundless state reiteration must occur if the target concurrent program has an infinite number of SYN-sequences. However, if busy-waiting loops are embedded in high-level synchronization operations, there may be no need to collect execution states and reachability testing can be used to perform testing without boundless state reiteration. For example, the wait primitive of shared semaphores can be implemented in busy-waiting loops which access some shared variables. We can apply reachability testing only to explore the interleavings of these wait primitives if we are sure that the implementation of the primitive is correct. Since the interleavings of the accesses to shared variables issued by busy-waiting loops.

(32) 23. are not explored, boundless state reiteration will not occur. But, if the programmer intends to verify the correctness of the behavior of busy-waiting loops and needs to explore the interleavings of busy-waiting loops, then boundless state reiteration may occur. The programmer can employ some schemes to determine whether a program contains busy-waiting loops [53]..

(33) Chapter 4. Dynamic Termination Decision We now describe how to develop protocols for the dynamic termination decision, so that when a process gets stuck in an infinite loop or when several loops of different processes form a livelock, they can stop the execution themselves. In general, a loop in a program can be either pretested or posttested, as shown in Figure 3 [57]. Without loss of generality, we assume that there is no unconditional branching statement (e.g., a GOTO. statement) in the loop body that could cause an unconditional transfer of control. to another statement outside the loop. Whether the loop is infinite depends on the evaluation of Boolean_func(V0,V1,V2,...,Vn-1), which is the guard statement of the loop, where V0, V1, …, Vn–1 are guard variables that are used to decide if the loop should be exited. Note that since we are executing a concurrent program, some of the guard variables might be shared variables. It is obvious that the evaluation of Boolean_func() depends on the values of these variables. If their values cannot be altered and the loop iterates more than once, we can conclude that it is an infinite loop. Thus, the basic idea for determining whether or not the loop will iterate infinitely is checking if any statement is present that could change the values of guard variables. A pretested loop. A posttested loop. while(Boolean_func(V0,V1,V2,...,Vn-1)) { /* loop body */ }. do { /* loop body */ } while(Boolean_func(V0,V1,V2,...,Vn-1));. Figure 3. Pretested and posttested loops 4.1. Termination-dependency relationship We first define the termination-dependency relationships for concurrent programs without nested loop and consider the cases in which the termination of a loop is not affected by other loops (see Definition 1 and Definition 2). If a loop Lx has either 24.

(34) 25. dynamic or static termination dependency with loop Ly, then the termination of Lx generally depends on the termination of Ly. Figure 4 shows a program without any termination-dependency relationship. Actually, the infinity of loop L0 depends on the execution order of S0,0 and S1,0. Definition 1: For a concurrent program without nested loops, a loop Lx is said to have dynamic termination dependency with Ly if (1) Lx and Ly are located in different processes and (2) there exists a shared variable V that is a guard variable of Lx and there is at least one write operation W to V in which W is located in the body of loop Ly.. Definition 2: For a concurrent program without nested loops, a loop Lx is said to have static termination dependency with Ly if (1) Lx and Ly are located in different processes and (2) there exists a shared variable V that is a guard variable of Lx and there is at least one write operation W to V in which W might be executed immediately after the termination of Ly without any loop between Ly and W. a is a shared variable. Process P0 S0,0 a = 1; /* Loop L0 is S0,1 and S0,2 */ S0,1 do { ... } S0,2 while(a==0); .... Process P1. S1,0 a = 0; .... Figure 4. Example of a concurrent program with two processes In Figure 5, L0 has dynamic termination dependency with L1. Since L1 continuously changes the value of variable a, which is a guard variable of L0, the termination of L0 depends on L1. In Figure 6, L0 has static termination dependency with L1. The termination of L0 depends on the value of variable a. Process P1 will change the value.

(35) 26. of a after the termination of L1. However, L1 does not change the value of a. Note that a loop might be located in a multiple-selection statement (e.g., if-then-else), as shown in Figure 7. Since func(&a) might be executed after either loop L1 or L2, a loop whose guard variables includes variable a has static termination dependency with loops L1 and L2. a is a shared variable whose initial value is 0. Process P0 Process P1 /* Loop L0 is S0,0 and S0,1 */ S0,0 do { ... } S0,1 while(a==0); .... /* Loop L1 is S1,0 and S1,1 */ S1,0 do { func(&a)9; } S1,1 while(b==1); .... Figure 5. Example of dynamic termination dependency a is a shared variable. Process P0 S0,0 a = 0; /* Loop L0 is S0,1 and S0,2 */ S0,1 do { ... } S0,2 while(a==0); .... Process P1 S1,0 S1,1 S1,2. /* Loop L1 is S1,0 and S1,1 */ do { ... } while(b==1); func(&a); .... Figure 6. Example of static termination dependency /* Loop L1 is Si,1 and Si,2 */ Si,0 do { ... } Si,1 while(f1()); Si,2 if(Boolean_func()) { /* Loop L2 is Si,3 and Si,4 */ do { ... } Si,3 Si,4 while(f2()); Si,5 } Si,6 func(&a);. Figure 7. Loops in multiple-selection statements 4.2. Without considering the termination-dependency relationship Based on the assumption that a loop does not have a termination-dependency relationship with other loops, a solution for codes like the one shown in Figure 4 is possible. The solution is to maintain a counter for each guard variable that is evaluated in the guard statements of the loops. Each counter is a shared variable. If the value of. 9. We assume that the statement func(&a) modifies the value of variable a..

(36) 27. the counter variable associated with a variable X is N, this means that there are still N statements that could alter the value of X. For each statement S that might alter the value of X, we insert code to decrease the associated counter variable of X by 1 somewhere in the program. This code is executed when statement S cannot be executed anymore. Note that if statement S is inside a loop, we should place the code for decrementing the value of the associated counter variable outside the loop. We assume that the original loop is the pretested loop shown in Figure 3. The modified loop is shown in Figure 8. Here we only show how to modify the pretested loop, since the modification required to the posttested loop is very similar. The original guard statement Boolean_func(V0,V1,V2,...,Vn-1) is changed to the new guard statement New_Boolean_func(V0,V1,V2,...,Vn-1), which first executes the original guard statement. If this returns TRUE, it checks if the value of each guard variable (i.e., V0,. V1,. V2,. …,. Vn–1). can. value_no_change(V0,V1,V2,...,Vn-1). be. altered. further.. Note. that. function. returns TRUE if the following conditions are true: (1). all the write operations in other processes corresponding to the guard variables are finished, and (2) the statements in the loop body cannot change the values of those guard variables. We can apply local or global data-flow analysis for compiler optimization to determine how data values are modified across basic blocks of program. statements. [58].. Boolean_func(V0,V1,V2,...,Vn-1). indicates. that. the. If. this. returns. TRUE,. it. is. obvious. that. the. in the subsequent iterations will always be true, which. execution. Report_must_be_an_infinite_loop(Li).. of. the. loop. is. infinite. by. invoking. Note that sometimes a data-flow analysis cannot. determine if the statements in the loop body are able to modify the values of the guard.

(37) 28. variables. Properties calculated by a data-flow analysis are typically only approximations of the real properties, since such analyses operate on the syntactical structure without simulating the exact control flow of the program. The possible presence of an infinite loop is reported by invoking Report_may_be_an_infinite_loop(Li) if it finds that the number of iterations of the loop reaches the ITERATION_LIMITATION threshold, which is set by the programmer. Theorem 1 and Theorem 2 demonstrate the power of the protocol shown in Figure 8. 1 2 3. /* The modified code for loop Li */ while (New_Boolean_func(V0,V1,V2,...,Vn-1)) { /* loop body */ }. 1 New_Boolean_func(V0,V1,V2,...,Vn-1) { 2 if(Boolean_func(V0,V1,V2,...,Vn-1)==TRUE) { 3 if(value_no_change(V0,V1,V2,...,Vn-1)==TRUE) { Report_must_be_an_infinite_loop(Li); 4 } else { 5 6 Li.number_of_iterations++; if(Li.number_of_iterations is larger than its ITERATION_LIMITATION) 7 Report_may_be_an_infinite_loop(Li); 8 } 9 } else { 10 return FALSE; 11 } 12 13 }. Figure 8. Modified pretested loop Theorem 1: Assume a concurrent program P is modified with New_Boolean_func() shown in Figure 8. If it reports that there exists an infinite loop during the execution of P with input X (i.e., invoking Report_must_be_an_infinite_loop(Li)), then the execution of P must get stuck in an infinite loop. Proof:. If. Report_must_be_an_infinite_loop(Li). value_no_change(V0,V1,V2,...,Vn-1). was. invoked,. then. function. must return TRUE. This means that the values of. the guard variables V0, V1, V2, …, Vn-1 cannot be changed by any statement. Thus, the subsequent evaluations of the function Boolean_func(V0,V1,V2,...,Vn-1) will not.

(38) 29. change. The loop cannot exit. QED. Theorem 2: Assume a concurrent program P is modified with New_Boolean_func() shown in Figure 8. If it reports that there might be an infinite loop during the execution of P with input X (i.e., invoking Report_may_be_an_infinite_loop(Li)), there is a possibility of an infinite loop in P. Proof: Report_may_be_an_infinite_loop(Li) is invoked because the number of iterations of loop Li exceeds ITERATION_LIMITATION. It is not certain that loop Li is an infinite loop – we can only claim that it might be an infinite loop. QED. 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..

(39) 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.”.

(40) 31. a, b, and c are shared variables and initialized to 0.. Process P0 S0,0 S0,1. /* Loop L0 is S0,0 and S0,1 */ do { ... } while(a==0);. S1,0 S1,1 S1,2. /* Loop L1 is S1,0 and S1,1 */ do { ... } while(b==0); update1(&a)10;. Process P1. s L0. L1. L2 s. Process P2 /* Loop L2 is S2,0 and S2,1 */ S2,0 do { ... } S2,1 while(c==0); S2,2 update2(&b);. (A) (B) 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..

(41) 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.. Process P0 /* Loop L0 is S0,1 and S0,2 */ S0,0 do { a = 1; } S0,1 while(a==0);. Process P1. d L0. /* Loop L1 is S1,0 and S1,1 */ S1,0 do { a = 0; } S1,1 while(a==0);. L1 d. (A) (B) Figure 10. (A) Example of a concurrent program that might form a livelock and (B) its termination-dependency graph /* Loop L1 is Si,0 and Si,1 */ Si,0 do { ... } Si,1 while(f1()); Si,2 if(Boolean_func()) { /* Loop L2 is Si,3 and Si,4 */ Si,3 do { ... } Si,4 while(f2()); Si,5 } else { /* Loop L3 is Si,6 and Si,7 */ Si,6 do { ... } Si,7 while(f3()); Si,8 }. 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..

(42) 33. 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..

(43) 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.

(44) 35. 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. =. Active Status transition is set by the execution of other loops. Status transition is set by the execution of itself.. 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′..

(45) 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.

(46) 37. 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.

參考文獻

相關文件

vs Functional grammar (i.e. organising grammar items according to the communicative functions) at the discourse level2. “…a bridge between

To be an effective practitioner, a defined body of formal knowledge and skills is the necessary, but not sufficient, condition to meet workplace requirements. The important

If the best number of degrees of freedom for pure error can be specified, we might use some standard optimality criterion to obtain an optimal design for the given model, and

• When a number can not be represented exactly with the fixed finite number of digits in a computer, a near-by floating-point number is chosen for approximate

If we want to test the strong connectivity of a digraph, our randomized algorithm for testing digraphs with an H-free k-induced subgraph can help us determine which tester should

Microphone and 600 ohm line conduits shall be mechanically and electrically connected to receptacle boxes and electrically grounded to the audio system ground point.. Lines in

* All rights reserved, Tei-Wei Kuo, National Taiwan University, 2005..

• Given a (singly) linked list of unknown length, design an algorithm to find the n-th node from the tail of the linked list. Your algorithm is allowed to traverse the linked