VyrdMC: Driving Runtime Refinement Checking Using Model Checkers

Slides:



Advertisements
Similar presentations
Intermediate Code Generation
Advertisements

1 Chao Wang, Yu Yang*, Aarti Gupta, and Ganesh Gopalakrishnan* NEC Laboratories America, Princeton, NJ * University of Utah, Salt Lake City, UT Dynamic.
Architecture-aware Analysis of Concurrent Software Rajeev Alur University of Pennsylvania Amir Pnueli Memorial Symposium New York University, May 2010.
Reduction, abstraction, and atomicity: How much can we prove about concurrent programs using them? Serdar Tasiran Koç University Istanbul, Turkey Tayfun.
Concurrency Control Part 2 R&G - Chapter 17 The sequel was far better than the original! -- Nobody.
A Randomized Dynamic Program Analysis for Detecting Real Deadlocks Koushik Sen CS 265.
Background for “KISS: Keep It Simple and Sequential” cs264 Ras Bodik spring 2005.
CS 267: Automated Verification Lecture 10: Nested Depth First Search, Counter- Example Generation Revisited, Bit-State Hashing, On-The-Fly Model Checking.
/ PSWLAB Concurrent Bug Patterns and How to Test Them by Eitan Farchi, Yarden Nir, Shmuel Ur published in the proceedings of IPDPS’03 (PADTAD2003)
Atomicity in Multi-Threaded Programs Prachi Tiwari University of California, Santa Cruz CMPS 203 Programming Languages, Fall 2004.
1 Introduction to Computability Theory Lecture12: Reductions Prof. Amos Israeli.
Nested Transactional Memory: Model and Preliminary Architecture Sketches J. Eliot B. Moss Antony L. Hosking.
B + -Trees (Part 1). Motivation AVL tree with N nodes is an excellent data structure for searching, indexing, etc. –The Big-Oh analysis shows most operations.
Chapter 12 File Management Systems
Transactions and Reliability. File system components Disk management Naming Reliability  What are the reliability issues in file systems? Security.
15-740/ Oct. 17, 2012 Stefan Muller.  Problem: Software is buggy!  More specific problem: Want to make sure software doesn’t have bad property.
Runtime Refinement Checking of Concurrent Data Structures (the VYRD project) Serdar Tasiran Koç University, Istanbul, Turkey Shaz Qadeer Microsoft Research,
Optimistic Design 1. Guarded Methods Do something based on the fact that one or more objects have particular states  Make a set of purchases assuming.
Views Lesson 7.
/ PSWLAB Thread Modular Model Checking by Cormac Flanagan and Shaz Qadeer (published in Spin’03) Hong,Shin Thread Modular Model.
Simplifying Linearizability Proofs Using Reduction and Abstraction Serdar Tasiran Koc University, Istanbul, Turkey Tayfun Elmas, Ali Sezgin, Omer Subasi.
File System Consistency
Eighth Lecture Exception Handling in Java
Topic 2: binary Trees COMP2003J: Data Structures and Algorithms 2
Module 11: File Structure
Transactions and Reliability
CHP - 9 File Structures.
CS522 Advanced database Systems
Paging COMP 755.
Compilers Principles, Techniques, & Tools Taught by Jing Zhang
Indexing ? Why ? Need to locate the actual records on disk without having to read the entire table into memory.
Data Structure and Algorithms
© Craig Zilles (adapted from slides by Howard Huang)
Faster Data Structures in Transactional Memory using Three Paths
Lecture 25 More Synchronized Data and Producer/Consumer Relationship
Extra: B+ Trees CS1: Java Programming Colorado State University
B+-Trees.
B+-Trees.
B-Trees Disk Storage What is a multiway tree? What is a B-tree?
Specifying Multithreaded Java semantics for Program Verification
Stack Data Structure, Reverse Polish Notation, Homework 7
CMSC 341 Lecture 10 B-Trees Based on slides from Dr. Katherine Gibson.
Database Applications (15-415) DBMS Internals- Part III Lecture 15, March 11, 2018 Mohammad Hammoud.
Design by Contract Fall 2016 Version.
Optimizing Malloc and Free
Design and Programming
Chapter 6 Intermediate-Code Generation
Serdar Tasiran, Tayfun Elmas Koç University, Istanbul, Turkey
B- Trees D. Frey with apologies to Tom Anastasio
Over-Approximating Boolean Programs with Unbounded Thread Creation
Indexing and Hashing Basic Concepts Ordered Indices
B- Trees D. Frey with apologies to Tom Anastasio
Virtual Memory Hardware
A Robust Data Structure
VyrdMC: Driving Runtime Refinement Checking Using Model Checkers
B-Trees Disk Storage What is a multiway tree? What is a B-tree?
Dr. Mustafa Cem Kasapbaşı
B-Trees Disk Storage What is a multiway tree? What is a B-tree?
Runtime Checking of Refinement for Concurrent Software Components
Serdar Tasiran, Tayfun Elmas Koç University, Istanbul, Turkey
Serdar Tasiran, Tayfun Elmas, Guven Bolukbasi, M
B- Trees D. Frey with apologies to Tom Anastasio
Files Management – The interfacing
Data Structures & Algorithms
Tayfun Elmas, Serdar Tasiran Koç University, Istanbul, Turkey
Programming with Shared Memory Specifying parallelism
Tayfun Elmas, Serdar Tasiran Koç University, Istanbul, Turkey
Advance Database System
Internal Representation of Files
Dynamic Binary Translators and Instrumenters
Presentation transcript:

VyrdMC: Driving Runtime Refinement Checking Using Model Checkers Serdar Tasiran, Tayfun Elmas Koç University, Istanbul, Turkey Hi all. I’m Tayfun Elmas from Koc University. In this talk I’ll present you a technique for detecting concurrency errors. In this technique we watch for refinement violations at runtime. This is joint work with my advisor Serdar Tasiran and Shaz Qadeer, from Microsoft Research. 14/01/19 PLDI 2005, June 12-15, Chicago, U.S.

Motivation Verifying Concurrent Data Structures Widely-used software systems are built on concurrent data structures File systems, databases, internet services Standard Java and C# class libraries Intricate synchronization mechanisms to improve performance Prone to concurrency errors Concurrency errors Data loss/corruption Difficult to detect, reproduce through testing Well, Many widely-used software applications are built on concurrent data structures. Examples are file systems, databases, internet services and some standard Java and C# class libraries. These systems frequently use intricate synchronization mechanisms to get better performance in a concurrent environment. This makes them prone to concurrency errors. Concurrency errors can have serious consequences, such as data loss or corruption. Unfortunately, these errors are typically hard to detect and reproduce through pure testing-based techniques. 14/01/19 PLDI 2005, June 12-15, Chicago, U.S.

Runtime Refinement Checking For each execution of Impl there exists an “equivalent”, atomic execution of Spec Spec: “Atomized” version of Impl Client methods run one at a time Obtained from Impl itself Use refinement as correctness criterion More thorough than assertions More observability than pure testing Runtime verification: Check refinement using execution traces Can handle industrial-scale programs Intermediate between testing & exhaustive verification Keywords: Linerizability and atomicy are more restrictive. The flexibility in spec gives us a more powerful method to prove correctness of some tricky implmentations. In our approach to verifying concurrent data structures we use refinement as the correctness criterion. The benefits of this choice are that refinement is a more thorough condition than method local assertions and that it provides more observability than pure testing. Correctness conditions like Linearizability and atomicity require that for each execution of impl in a concurrent environment there exists an equivalent atomic execution of the same Impl. However Refinement uses a separate specification and for each execution of the impl refinement requires existence of an equivalent atomic execution of this spec. The specification we use is more permissive than the impl. For example the spec allows methods to terminate exceptionally to model failure due to resource contention in a concurrent environment. However the impl would not allow some of the method executions to fail. We check refinement at runtime using execution traces of the implementation. We do this in order to be able to handle industrial-scale programs. Our approach can be regarded as intermediate between testing and exhaustive verification with respect to the coverage of the whole execution space explored. 14/01/19 PLDI 2005, June 12-15, Chicago, U.S.

Runtime Refinement Checking This paper: Improve coverage of runtime refinement checking by a restricted application of a model checker Get good checking from small test Controllability, coverage: Execution-based model checker Observability, : View refinement Keywords: Linerizability and atomicy are more restrictive. The flexibility in spec gives us a more powerful method to prove correctness of some tricky implmentations. In our approach to verifying concurrent data structures we use refinement as the correctness criterion. The benefits of this choice are that refinement is a more thorough condition than method local assertions and that it provides more observability than pure testing. Correctness conditions like Linearizability and atomicity require that for each execution of impl in a concurrent environment there exists an equivalent atomic execution of the same Impl. However Refinement uses a separate specification and for each execution of the impl refinement requires existence of an equivalent atomic execution of this spec. The specification we use is more permissive than the impl. For example the spec allows methods to terminate exceptionally to model failure due to resource contention in a concurrent environment. However the impl would not allow some of the method executions to fail. We check refinement at runtime using execution traces of the implementation. We do this in order to be able to handle industrial-scale programs. Our approach can be regarded as intermediate between testing and exhaustive verification with respect to the coverage of the whole execution space explored. 14/01/19 PLDI 2005, June 12-15, Chicago, U.S.

The VYRD Tool ... Impl Implreplay Spec Replay Mechanism Refinement Multi-threaded test Impl Write to log ... Call LookUp(3) Call Insert(3) A[0].elt=3 Unlock A[0] Call Delete(3) Call Insert(4) read A[0] A[1].elt=4 Return “true” Unlock A[1] Return“success” Return“success” A[0].elt=null Unlock A[0] Return“success” Read from log Execute logged actions Replay Mechanism Run methods atomically Implreplay Abstraction function (for checking view-refinement) An extra method of the data structure For the current data structure state, computes the current state of the view variable Vyrd analyzes execution traces of the impl generated by test programs. Vyrd uses two separate threads for the process. The testing thread runs a test harness that generates test programs. A test program makes concurrent method calls to the impl. During the run of the test program, the corresponding execution trace is recorded in a shared sequential log. The verification thread reads the execution trace from the log. Since the verification thread follows the testing thread from behind, it can not access the instantaneous state of the impl. Thus the replaying module re-executes actions from the log on a separate instance of the impl called impl-replay and executes atomic methods on the spec at commit points. During replaying, the replaying mechanism also computes the view variables when it reaches a commit point and annotates the commit actions along the traces with view variables. The refinement checker module checks the resulting lambda traces of impl and the spec for IO and view refinement. These threads can run in online or offline setting. In online checking both threads simultaneously while in offline checking the verification thread runs after the whole test program finishes its work. Spec Refinement Checker traceImpl traceSpec At certain points for each method, take “state snapshots” Check consistency of data structure contents 14/01/19 PLDI 2005, June 12-15, Chicago, U.S.

Multiset Implementation: LookUp LookUp (x) for i = 1 to n Multiset data structure M = { 2, 3, 3, 3, 9, 8, 8, 5 } Why this example? Mimics patterns we encountered in real systems LookUp (x) for i = 1 to n acquire(A[i]) if (A[i].content==x && A[i].valid) release(A[i]) return true else release(A[i]) return false A 9  8 6  5 3  2 content valid Our motivating data structure is a multiset. Here is an example of a multiset. Notice that several copies of the same integer can be in the multiset like 3 and 8 in this example. The implementation represents the multiset by an array A with two fields. The content field stores the integer element and the Boolean valid field tells us whether the element is to be included in the multiset or not. For example one representation of the multiset above could be like as the bottom one. On the right you see the implementation for the lookup method. Lookup queries whether a given integer x is in the multiset. It traverses the array A linearly by locking elements one by one and checking if the content is x and the valid field is set. 14/01/19 PLDI 2005, June 12-15, Chicago, U.S.

Multiset Implementation: FindSlot FindSlot (x) for i = 1 to n FindSlot: Helper routine for InsertPair For space allocation Does not set valid field x not in multiset yet FindSlot (x) for i = 1 to n acquire(A[i]) if (A[i].content==null) A[i].content = x release(A[i]) return i else return 0 FindSlot is a helper method for an insertion method I will tell you about in the next slide. Given an integer x, it looks for an empty slot to put x in. If it finds one, it allocates the slot for x by setting its content field to x and returns the index, otherwise it returns 0. Notice that it doesn’t set the valid field, so x is not in the multiset yet. Thus it will not be treated as in the set by a Lookup metod that will check this slot. 14/01/19 PLDI 2005, June 12-15, Chicago, U.S.

Multiset Implementation: InsertPair InsertPair (x, y) i = FindSlot (x) if (i == 0) return failure j = FindSlot (y) if (j == 0) A[i].content = null acquire(A[i]) acquire(A[j]) A[i].valid = true A[j].valid = true release(A[i]) release(A[j]) return success Refinement violation if only one of x, y inserted Insertpair has an interesting except. term that is not possible in seq case. Using a sep spec we do not rule out this excep execution. Multiset has an InsertPair method to insert a pair of integers x, y into the contents. The implementation of InsertPair is given on the right. InsertPair makes the multiset example interesting because InsertPair demonstrates the methods in real concurrent systems that first hold up several resources and then completes its operation on all the resources atomically. It is considered an error if one of x or y is inserted and but not the other. To prevent this error, it makes two calls to FindSlot to first allocate slots for x and y. If both FindSlot’s succeed, in a protected block, it includes x and y into the multiset atomically by setting their corresponding valid bits to true. Then it returns success. InsertPair returns failure if either of the FindSlot calls fail. This can happen because of resource contention with other concurrent InsertPair routines. For example, imagine we have an empty multiset of size n. n concurrent InsertPair’s running on this multiset can all find free slots for their x’s but then they may be unable to find slots for their y’s if there is no more empty slots. This causes all the InsertPairs to return failure even though at the beginning there is space for some of them to succeed. 14/01/19 PLDI 2005, June 12-15, Chicago, U.S.

Multiset Testing Don’t know which happened first Call Insert(3) Call LookUp(3) Return“success” Call Insert(4) Return “true” Call Delete(3) Unlock A[0] A[0].elt=3 Unlock A[1] A[1].elt=4 read A[0] A[0].elt=null Don’t know which happened first Insert(3) or Delete(3) ? Should 3 be in the multiset at the end? Must accept both possibilities as correct Common practice: Run long multi-threaded test Perform sanity checks on final state In this slide we’ll explain how we check IO refinement. Again we use the insert operation instead of insertpair to simplify the picture. On the right you see 14/01/19 PLDI 2005, June 12-15, Chicago, U.S.

Multiset I/O Refinement Witness ordering Spec trace M=Ø Call Insert(3) Call LookUp(3) Return“success” Call Insert(4) Return “true” Call Delete(3) Unlock A[0] A[0].elt=3 Unlock A[1] A[1].elt=4 read A[0] A[0].elt=null M=Ø {3} {3, 4} {4} Spec trace Call Insert(3) Return “success” Call LookUp(3) Call Insert(4) Call Delete(3) M = M U {3} Check 3  M Return “true” M = M U {4} M = M \ {3} Commit Insert(3) Commit LookUp(3) Commit Insert(4) Commit Delete(3) Witness ordering Unlock A[0] A[0].elt=3 Unlock A[1] A[1].elt=4 read A[0] A[0].elt=null M = M U {3} Check 3  M M = M U {4} M = M \ {3} In this slide we’ll explain how we check IO refinement. Again we use the insert operation instead of insertpair to simplify the picture. On the right you see 14/01/19 PLDI 2005, June 12-15, Chicago, U.S.

LookUp(5)=true, LookUp(7)=true LookUp(6)=true, LookUp(8)=true Need for more observability View-refinement T1: InsertPair(5,7) T2: InsertPair(6,8) Read A[0].elt = null FINDSLOT (x) // Buggy for i  1 to n if (A[i].content == null) acquire(A[i]) A[i].content = x release(A[i]) return i return 0 Read A[0].elt = null Read A[1].elt = null 1 2 3 elt     valid F F F F elt 5 7   valid F F F F elt 5 7   Overwrites 5! valid T T F F LookUp(5)=true, LookUp(7)=true 1 2 3 elt 6 7   valid T T F F It would be caught is lookup5 would get interleaved here. IO refinement is still not sufficient to catch some errors that does not appear in method calls and return values. Consider the buggy impl of FindSlot on the left. It does not lock the elements before reading from their content fields. It acquires the lock just before starting the modification. As a result as you see on the right, two concurrently executed insertpairs can get interleaved in a way so that they read the first slot in the array as empty and think that it is available for an insertion but only one of them succeeds in inserting its x into this slot. In this case, the integer 5 inserted by the first thread is overwritten by the second thread. Each thread checks the insertpair it runs by calling lookup methods with the same arguments as the insertpair had after the insertpair finishes. If the lookups are scheduled in this way, they all return true although the last state is inconsistent with the termination status of the methods. The error is there and is observable from the state but IO refinement is unable to detect it in this senario due to the lookup not being scheduled in the right places. Read A[2].elt = null elt 6 7 8  valid T F elt 6 7 8  valid T F LookUp(5)=false LookUp(6)=true, LookUp(8)=true 14/01/19 PLDI 2005, June 12-15, Chicago, U.S.

LookUp(5)=true, LookUp(7)=true LookUp(6)=true, LookUp(8)=true I/O-refinement may miss errors View-refinement T1: InsertPair(5,7) T2: InsertPair(6,8) Read A[0].elt = null If observer methods don’t get interleaved in the right place, I/O refinement may miss errors Source of bug too far in the past when I/O refinement violation happens Read A[0].elt = null Read A[1].elt = null 1 2 3 elt     valid F F F F elt 5 7   valid F F F F elt 5 7   Overwrites 5! valid T T F F LookUp(5)=true, LookUp(7)=true 1 2 3 elt 6 7   valid T T F F Do not say about the first bullet. IO refinement is still not sufficient to catch some errors that does not appear in method calls and return values. Consider the buggy impl of FindSlot on the left. It does not lock the elements before reading from their content fields. It acquires the lock just before starting the modification. As a result as you see on the right, two insertpairs can get interleaved in a way so that they read the first slot in the array as empty but only one of them succeeds in inserting its x into this slot. In this case, the integer 5 inserted by the first thread is overwritten by the second thread. Each thread checks the insertpair it runs by calling lookup methods with the same arguments as the insertpair had. If the lookups are scheduled in this way, they all return true although the last state is inconsistent with the termination status of the methods. The error is there and is observable from the state but IO refinement is unable to detect it in this senario due to the lookup not being scheduled in the right places. Read A[2].elt = null elt 6 7 8  valid T F elt 6 7 8  valid T F LookUp(6)=true, LookUp(8)=true 14/01/19 PLDI 2005, June 12-15, Chicago, U.S.

View-refinement More Observability I/O-refinement may miss errors I/O-refinement + “correspondence” between states of Impl and Spec at commit points Catches state discrepancy at the next commit point Early warnings for possible I/O refinement violations As we saw in the previous example with 2 insertpairs IO refinement is not that good at finding refinement errors.The problem is IO refinement relies on observer methods and if the observer methods do not get interleaved in the right place along the trace, IO refinement may miss errors. In the extreme case, if there are no observer methods, IO refinement trivially passes any executions. In another case, when a refinement violation is detected, the source of the bug may be too far in the past so there may need to be an analysis of the trace to the far back. Our solution is view-refinement. View refinement augments IO refinement with a new condition that seeks correspondence between states of the impl and the spec along at commit points. To accomplish this we add commit actions to the set lambda and label them with state information when they are executed. View-refinement catches state discrepancies right when it happens. In fact these state discrepancies are early warnings for future IO refinement violations. 14/01/19 PLDI 2005, June 12-15, Chicago, U.S.

View-refinement   View Variables State correspondence Hypothetical “view” variables must match at commit points “view” variable: Value of variable is abstract data structure state Updated atomically once by each method viewImpl : state information for Impl For A[1..n] Extract content if valid=true viewImpl={3, 3, 5, 5, 8, 8, 9} The state correspondence is obtained by matching view variables from the impl and the spec at commit points. A view variable is a hypothetical variable that extracts an abstract state of the data structure. This abstract state is updated or observed atomically by each method. The view variables that carry state information of the impl and the spec are denoted by viewimpl and viewspec respectively. The view for multiset data structure is the set of integers stored in the multiset. The view variable for the multiset impl extracts the elements in content fields whose corresponding valid fields are true. Thus the view variable for the multiset in the figure does not contain the first 5 and 6 in the view variable. The view var for the spec gets elements from the set M. 3  5   content valid A 9 8 6 14/01/19 PLDI 2005, June 12-15, Chicago, U.S.

View-refinement Checking Refinement Witness ordering Spec trace M=Ø Call Insert(3) Unlock A[0] A[0].elt=3 Call LookUp(3) Return“success” Unlock A[1] A[1].elt=4 Call Insert(4) read A[0] Return “true” Call Delete(3) A[0].elt=null M=Ø {3} {3, 4} {4} Spec trace Call Insert(3) Return “success” Call LookUp(3) Return “true” Call Insert(4) Call Delete(3) M = M U {3} Check 3  M M = M U {4} M = M \ {3} Commit Insert(3) Commit LookUp(3) Commit Insert(4) Commit Delete(3) Witness ordering viewImpl = {3} viewImpl = {3,4} viewImpl = {4} A[0].elt=3 A[1].elt=4 A[0].elt=null viewSpec = {3} viewSpec = {3,4} viewSpec = {4} Say the view are computed by running abst func at this point. The checking procedure is similar to checking IO refinement. 14/01/19 PLDI 2005, June 12-15, Chicago, U.S.

InsertP(5,7) Returns “success” InsertP(6,8) Returns “success” Catching FindSlot Bug View-refinement Specification {5, 7} {5, 6, 7, 8} M = Ø Call InsertPair(5,7) Return “success” Call InsertPair(6,8) viewSpec = Ø {5, 7} {5, 6, 7, 8} viewImpl = Ø {5, 7} {6, 7, 8} Suppose we are checking the execution trace with buggy FindSlot implementation. First we drive the spec according to the witness ordering of the commit points. Then we track the valuations of the view variables for the impl and the spec at commit points and compare them for equivalence. Here at the commit point of the second insertpair, 5 disappears from the view variable of the impl but it is there in the view var of the spec. Then a refinement error is signalled that says an element is overwritten betweeen last two commit actions. InsertP(5,7) Returns “success” Call InsertP(5,7) Read A[0].elt InsertP(6,8) Returns “success” Call InsertP(6,8) Read A[0].elt Read A[0].elt A[0].content=5 A[1].content=7 A[0].valid=true A[0].valid=true A[1].valid=true A[0].content=6 A[0].valid=true Read A[2].elt A[2].content=8 A[2].valid=true Implementation 14/01/19 PLDI 2005, June 12-15, Chicago, U.S.

Experience Experimental Results Scalable method: Caught bugs in industrial-scale designs Boxwood (30K LOC) Scan Filesystem (Windows NT) Java Libraries with known bugs Moderate annotation effort Several lines for each method I/O-refinement Low logging and verification overhead: BLinkTree: Logging 17% over testing, refinement check 27% View-refinement BLinkTree: Logging 20% over testing, refinement check 137% More effective in catching errors Boxwood Cache: # of random method calls until error caught View-refinement: 26 I/O-refinement: 539 Remove tables. Explain why cache bug is tricky. Here are the experimental results from application of Vyrd on the Blinktree and cache modules. The overall results show that Vyrd can handle industrial scale designs with modest logging and verification costs. IO refinement requires only method call commit and return actions to be logged so the logging overhead is much less than view refinement requires. Note that the logging overhead includes the logging for IO refinement. But view refinement is more effective in catching bugs the first table shows the big difference in time passes in terms of the number of method calls made before detecting the error. The overhead of logging the actions for view-refinement may take much time as the granularity of actions gets finer. 14/01/19 PLDI 2005, June 12-15, Chicago, U.S.

Observation, VyrdMC Weakness of runtime verification: Poor coverage Need many threads, method calls to trigger concurrency error View refinement: More observability, but no better if bug not triggered Small test case (e.g. two threads, one method call each) sufficient, but must start from non-trivial initial state must pick the right interleaving (not known a priori) Idea: Lead design to interesting initial state first Run very small, multi-threaded test Explore all distinct thread interleavings of fixed test with execution-based model checker Computationally feasible Get good checking from small test Controllability, coverage: Execution-based model checker Observability, : View refinement 14/01/19 PLDI 2005, June 12-15, Chicago, U.S.

LookUp(5)=true, LookUp(7)=true LookUp(6)=true, LookUp(8)=true I/O-refinement may miss errors View-refinement T1: InsertPair(5,7) T2: InsertPair(6,8) Read A[0].elt = null If observer methods don’t get interleaved in the right place, I/O refinement may miss errors Source of bug too far in the past when I/O refinement violation happens Read A[0].elt = null Read A[1].elt = null 1 2 3 elt     valid F F F F elt 5 7   valid F F F F elt 5 7   Overwrites 5! valid T T F F LookUp(5)=true, LookUp(7)=true 1 2 3 elt 6 7   valid T T F F Do not say about the first bullet. IO refinement is still not sufficient to catch some errors that does not appear in method calls and return values. Consider the buggy impl of FindSlot on the left. It does not lock the elements before reading from their content fields. It acquires the lock just before starting the modification. As a result as you see on the right, two insertpairs can get interleaved in a way so that they read the first slot in the array as empty but only one of them succeeds in inserting its x into this slot. In this case, the integer 5 inserted by the first thread is overwritten by the second thread. Each thread checks the insertpair it runs by calling lookup methods with the same arguments as the insertpair had. If the lookups are scheduled in this way, they all return true although the last state is inconsistent with the termination status of the methods. The error is there and is observable from the state but IO refinement is unable to detect it in this senario due to the lookup not being scheduled in the right places. Read A[2].elt = null elt 6 7 8  valid T F elt 6 7 8  valid T F LookUp(6)=true, LookUp(8)=true 14/01/19 PLDI 2005, June 12-15, Chicago, U.S.

Observation, VyrdMC Weakness of runtime verification: Poor coverage Need many threads, method calls to trigger concurrency error View refinement: More observability, but no better if bug not triggered Small test case (e.g. two threads, one method call each) sufficient, but must start from non-trivial initial state must pick the right interleaving (not known a priori) Idea: Lead design to interesting initial state first Run very small, multi-threaded test Explore all distinct thread interleavings of fixed test with execution-based model checker Computationally feasible Get good checking from small test Controllability, coverage: Execution-based model checker Observability, : View refinement 14/01/19 PLDI 2005, June 12-15, Chicago, U.S.

VyrdMC: The Idea Thread 1 Thread 2 Thread 3 LookUp(3) Insert(3) Delete(3) For fixed test program, model checker explores all distinct thread interleavings Rationale: Explore “concurrency component” of state space Make fixed program interesting Lead program to non-trivial “anchor” state Issue a long sequence of random method calls from initial state Start multi-threaded test from anchor state: Each thread issues a small number of method calls Pick method arguments so threads contend for access to same portion of program state The goal of the test harness is to generate concurrent execution traces of impl. To accomplish this, it forks a number of threads. Each thread issues a number of method calls consecutively. The arguments to these methods are picked so that the thread contend over the same region of the data structure to reveal concurrency errors. The runtime information regarding to each action that is run atomically is recorded to shared log so that at the end we have a linear sequence of actions in the log. 14/01/19 PLDI 2005, June 12-15, Chicago, U.S.

Guiding Vyrd with a Model Checker Call InsertP(5,7) Call InsertP(5,7) Call InsertP(6,8) Call InsertP(6,8) Read A[0].elt Read A[0].elt Read A[0].elt A[0].content=5 A[0].content=5 Read A[1].elt A[1].content=7 A[1].content=7 ATOMIC ATOMIC A[0].valid=true A[0].valid=true A[1].valid=true A[1].valid=true A[0].content=6 InsertP(5,7) Returns “success” A[2].content=8 Read A[2].elt ATOMIC Read A[2].elt A[2].content=6 A[0].valid=true A[2].valid=true Read A[3].elt A[3].content=8 ATOMIC InsertP(5,7) Returns “success” A[2].valid=true A[3].valid=true InsertP(6,8) Returns “success” InsertP(6,8) Returns “success”

Vyrd + ExplicitMC (like Java PathFinder) Important side benefit: Automates, streamlines logging and replaying of implementation Makes it program independent Logged actions: Each bytecode instruction Model checker’s VM has hooks for calling methods of refinement checker, logger after each bytecode instruction Semantics of replay for VM’s actions defined, already implemented in model checker’s VM Refinement checker simply uses it Automates most labor-intensive part of runtime refinement checking Programmer only provides commit point annotations, writes abstraction function (for view refinement only) 14/01/19 PLDI 2005, June 12-15, Chicago, U.S.

Handling Backtracking Special log entries indicate parent-child relationships in tree, jumps to ancestors Vyrd resets to initial state, rewinds to beginning of log and replays actions from root to back-tracked ancestor Handling Backtracking Call InsertP(5,7) Call InsertP(6,8) Read A[0].elt Read A[0].elt Read A[1].elt A[0].content=5 A[1].content=7 ATOMIC A[1].content=7 A[0].valid=true ATOMIC A[1].valid=true A[0].valid=true A[1].valid=true InsertP(5,7) Returns “success” A[0].content=6 Read A[2].elt A[2].content=8 A[2].content=6 ATOMIC Read A[2].elt Read A[3].elt A[0].valid=true A[3].content=8 ATOMIC A[2].valid=true A[2].valid=true InsertP(5,7) Returns “success” A[3].valid=true InsertP(6,8) Returns “success” InsertP(6,8) Returns “success”

Vyrd + Stateless MC (like Verisoft) No state caching, just store path from root to node Terminates because state-transition diagram is a DAG/tree. Benefits: Possibly more efficient Vyrd is re-started anyway At re-starts, Vyrd resets state, No need to rewind to beginning of log Drawbacks: Must manually implement logging, replay Must instrument implementation source or byte code 14/01/19 PLDI 2005, June 12-15, Chicago, U.S.

Partial order reductions Pure blocks, annotations [Flanagan, Qadeer ’04] If pure block terminates normally, no net modification of program state Example: Lock node, Check if data is there, Release lock if it isn’t Many industrial-scale programs make extensive use of pure blocks Enumerating all distinct interleavings of even two concurrent methods infeasible if purity is not exploited Solution: Use non-deterministic abstraction of pure block Makes pure execution of pure block independent of any other action 14/01/19 PLDI 2005, June 12-15, Chicago, U.S.

Layered Architecture Example: Boxwood BLINKTREE MODULE Root Pointer Node Internal Pointer Node Level n+1 ................ Level n .................. Root Level Leaf Pointer Node ... Level 0 ... ...... ..... ..... ..... ..... ............... .... .... ......... ........ ...... ...... ..... ..... ..... ..... ............... .... .... ......... ........ ...... Data Node ... Data Nodes ... GlobalDiskAllocator CHUNK MANAGER MODULE Replicated Disk Manager Write Read Read Write CACHE MODULE Dirty Cache Entries ... Clean Cache Entries Cache We verified the storage modules of Boxwood that consists of Cache and Chunk manager. Both modules are accessed in a highly concurrent manner. They provide public methods for manipulating handle,byte-array pairs where handle is an abtract address for the data encoded into the byte array. We decided the view variable for these modules as the set of handle, byte-array pairs managed by the modules. Both cache and chunk manager manages the same set of handles but the byte-array stored by cache and chunk manager may change as cache may store the last version of the bytearray but not chunk manager. Thus for each handle managed by them, we first look at cache to see if there is any entry with the same handle. If there is a dirty cache entry for the handle we get the bytearray from cache. If there is a clean entry in the cache we again read the bytearray and to make sure that the state being abstracted is valid, we require that the byte arrays in cache and the chunk manager are the same. If cache has no entry for the handle, we fetch the bytearray from chunk manager. 14/01/19 PLDI 2005, June 12-15, Chicago, U.S.

Assume-Guarantee Reasoning for Multi-layered Architectures Each layer uses layer immediately below Layers do not call methods from components above One-pass verification: Run C1 || C2 || ... || Cn all together at once When replaying Ci run it in conjunction with a separate instance of Si+1 || Si+2 || ... || Sn Models the assumption that the environment of Ci respects its spec for this execution Assumption verified when Ci+1 Ci+2, ..., Cn checked for refinement violations on the same execution C1 S1 C2 S2 Components Specifications (Atomized Versions) C3 S3 ... ... Cn Sn 14/01/19 PLDI 2005, June 12-15, Chicago, U.S.

Summary, Future Work VyrdMC Execution-based model checker improves controllability of runtime refinement checking Get good checking from small test Controllability, coverage: Execution-based model checker Observability, : View refinement Future work Implementing VyrdMC around Java PathFinder Including automatic program abstraction to exploit “pure” annotations Stateless exploration in Java PathFinder? In this part of the talk I’ll tell you about our experience using the Vyrd tool. 14/01/19 PLDI 2005, June 12-15, Chicago, U.S.