Java Race Finder Checking Java Programs for Sequential

  • Slides: 54
Download presentation
Java Race Finder Checking Java Programs for Sequential Consistency Tuba Yavuz-Kahveci Fall 2013

Java Race Finder Checking Java Programs for Sequential Consistency Tuba Yavuz-Kahveci Fall 2013

Outline The Problem: Getting Multithreaded Java Programs Right Java Memory Model Our Solution: Java

Outline The Problem: Getting Multithreaded Java Programs Right Java Memory Model Our Solution: Java Race Finder What is model checking anyway? Representing Happens-before Heuristic-based Search Code Modification Suggestions

What is Sequential Consistency? Program statements are executed according to program order Each thread’s

What is Sequential Consistency? Program statements are executed according to program order Each thread’s statements are executed according to the program order in that thread’s code Write atomicity Each read operation on a variable sees the most recent write operation on that variable

What is a Memory Model? Constrains the behavior of memory operations What value can

What is a Memory Model? Constrains the behavior of memory operations What value can a read operation see? Example memory models Sequential Consistency Easy to understand Relaxed Consistency Models Relaxation of Program order Write atomicity

Who Should Care? Programmers Understanding how to achieve sequential consistency, if possible Reasoning about

Who Should Care? Programmers Understanding how to achieve sequential consistency, if possible Reasoning about correctness Compiler writers Optimizing code within the restrictions of the memory model

Problem: Getting Multi-threaded Java Programs Right Important Questions Any Java Programmer Should Ask Is

Problem: Getting Multi-threaded Java Programs Right Important Questions Any Java Programmer Should Ask Is my multithreaded program correctly synchronized? Beware!!! Sequential consistency is not guaranteed for incorrectly synchronized Java programs! If my multithreaded program is not correctly synchronized, how can I fix it? If my multithreaded program is not correctly synchronized for a good reason, should I still be worried? Automated tool support is needed to answer these nontrivial questions

An Example: Peterson’s Mutual Exclusion Algorithm - Version 1 Initialization: flag[0] = flag[1] =

An Example: Peterson’s Mutual Exclusion Algorithm - Version 1 Initialization: flag[0] = flag[1] = turn = shared = 0 /* all non-volatile */ Thread 1 Thread 2 s 1: flag[0] = 1; s 6: flag[1] = 1; s 2: turn = 1; s 7: turn = 0; s 3: while (flag[1] == 1 && turn == 1) { /*spin*/} s 8: while (flag[0] == 1 && turn == 0) { /*spin*/} s 4: shared++; /* critical section */ s 9: shared++; /* critical section */ s 5: flag[0] = 0; s 10: flag[0] = 0;

Outline The Problem: Getting Multithreaded Java Programs Right Java Memory Model Our Solution: Java

Outline The Problem: Getting Multithreaded Java Programs Right Java Memory Model Our Solution: Java Race Finder What is model checking anyway? Representing Happens-before Heuristic-based Search Code Modification Suggestions

What is Java Memory Model (JMM)? A relaxed memory model Sequential consistency is guaranteed

What is Java Memory Model (JMM)? A relaxed memory model Sequential consistency is guaranteed only for correctly synchronized programs For programs without data races Incorrectly synchronized programs can show extra behavior that is not sequentially consistent Still subject to some safety rules

Synchronization Rules in Java Some synchronization actions and their relationship in Java: Unlocking a

Synchronization Rules in Java Some synchronization actions and their relationship in Java: Unlocking a monitor lock synchronizes with locking that monitor lock. Writing a volatile variable synchronizes with reading of that variable. Starting a thread synchronizes with the first action of that thread. Final action in a thread synchronizes with any action of a thread that detects termination of that thread. Initialization of a field synchronizes with the first access to the field in every thread. In general a release action synchronizes with a matching acquire action.

Happens-Before Relation An action a 1 happens-before action a 2, a 1 ≤hb a

Happens-Before Relation An action a 1 happens-before action a 2, a 1 ≤hb a 2, due to one of the following: a 1 comes before a 2 according to program order: a 1 ≤po a 2. a 1 synchronizes with a 2: a 1 ≤sw a 2. a 1 happens-before a’ that happens-before a 2: Exists a’. a 1 ≤hb a’ and a’ ≤hb a 2 (transitivity). Happens-before, ≤hb = ( ≤po U ≤sw )+ , is a partial-order on all actions in an execution.

Happens-before Consistency A read operation r can see results of a write operation w

Happens-before Consistency A read operation r can see results of a write operation w provided that: r does not happen-before w: not (r ≤hb w). There is no intervening write operation: not (exists w’. w r ≤hb w’ ≤hb r).

Anatomy of a Data Race Definition: If two actions a 1 and a 2

Anatomy of a Data Race Definition: If two actions a 1 and a 2 from different threads access the same memory location loc, the actions are not ordered by happens-before and if one of the actions is a write, then there is a data race on loc. Example: Initialization: boolean done = false; /* non-volatile */ ≤hb Thread 1 Thread 2 result = compute(); ≤hb done = true; Race on done!!! if (done) ≤hb // use result

A Simple Fix A write to a volatile variable synchronizes with a read of

A Simple Fix A write to a volatile variable synchronizes with a read of that variable. Example: Initialization: volatile boolean done = false; ≤hb Thread 1 result = compute(); ≤hb ≤ done =hbtrue; Thread 2 ≤hb if (done)≤hb Not in a race // use result

Outline The Problem: Getting Multithreaded Java Programs Right Java Memory Model Our Solution: Java

Outline The Problem: Getting Multithreaded Java Programs Right Java Memory Model Our Solution: Java Race Finder What is model checking anyway? Representing Happens-before Heuristic-based Search Code Modification Suggestions

Our Solutions/Contributions Is my multi-threaded program correctly synchronized? Kim K. , Yavuz-Kahveci T. ,

Our Solutions/Contributions Is my multi-threaded program correctly synchronized? Kim K. , Yavuz-Kahveci T. , Sanders B. Precise Data Race detection in Relaxed Memory Model using Heuristic based Model Checking [ASE Conf. 2009] If my multi-threaded program is not correctly synchronized, how can I fix it? Kim K. , Yavuz-Kahveci T. , Sanders B. JRF-E: Using Model Checking to give Advice on Eliminating Memory Model-related Bugs [ASE Conf. 2010, ASE Journal 2012] If my program is not correctly synchronized for a good reason, should I still be worried? Jin H. , Yavuz-Kahveci T. , Sanders B. Java Path Relaxer: Extending JPF for JMM-aware model checking [JPF Workshop] Jin H. , Yavuz-Kahveci T. , Sanders B. Java Memory Model-Aware Model Checking [TACAS 2012]

Outline The Problem: Getting Multithreaded Java Programs Right Java Memory Model Our Solution: Java

Outline The Problem: Getting Multithreaded Java Programs Right Java Memory Model Our Solution: Java Race Finder What is model checking anyway? Representing Happens-before Heuristic-based Search Code Modification Suggestions

State/Snapshot of a Running Java Program JAVA VIRTUAL MACHINE Values of Static Fields Heap

State/Snapshot of a Running Java Program JAVA VIRTUAL MACHINE Values of Static Fields Heap (objects) Thread states Bytecode for the Java program

Model Checking Java Programs Values of Static Fields Thread states Heap (objects) Main Thread

Model Checking Java Programs Values of Static Fields Thread states Heap (objects) Main Thread 1 Thread 2 Thread 3 … Main Thread 2 Thread 3 Thread 1 … …

Model Checking for Sequential Consistency Multi-threaded Java application yes Java Race Finder (JRF) •

Model Checking for Sequential Consistency Multi-threaded Java application yes Java Race Finder (JRF) • extends JPF’s state representation to detect data races Data Race? no Java Path Finder (JPF) • a model-checker for Java programs • checks for general correctness properties • assumes sequential consistency • explores all possible thread interleaving

Our Approach for Detecting Data Races Algorithm: for each execution path EPj=<a 1, a

Our Approach for Detecting Data Races Algorithm: for each execution path EPj=<a 1, a 2, …, an> of program P do initialize happens-before relation for each action ai , i= 1 to n, do let loc be the memory location ai accesses if (it is safe (without a data race) for ai to access loc) generate DATA RACE error execute ai update happens-before relation

Representing Happens-Before We define an h-function that captures the happens-before relation in an implicit

Representing Happens-Before We define an h-function that captures the happens-before relation in an implicit way. h: Sync. Addr U Thread -> 2 Addr. Sync. Addr: Volatile variables and locks Addr: Non-volatile variables Is it safe for aj of thread ti to access loc? does h(ti ) contain loc? Which variables can be safely accessed if acquire on s (with a matching release on s) is executed? h(s).

The h-function Initialization: At the beginning there is only the main thread: h 0

The h-function Initialization: At the beginning there is only the main thread: h 0 = λz. if z = main then static(P) else φ Update: Executing an action updates the h-function: action(t, x) h = h’ h: h-function before executing action t: the thread the action belongs to x: synchronization variable (volatile or a lock) h’: the updated h function

Updating the h-function an by thread t hn+1 write a volatile field v release(t,

Updating the h-function an by thread t hn+1 write a volatile field v release(t, v) hn read a volatile field v acquire(t, v) hn lock the lock variable lck acquire(t, lck) hn unlock the lock variable lck release(t, lck) hn start thread t′ release(t, t′) hn join thread t′ acquire(t, t′) hn t′. is. Alive() returns false acquire(t, t′) hn write a non-volatile field x invalidate(t, x) hn read a non-volatile field x hn instantiate an object containing non-volatile fields and volatile fields volatiles new (t, fields , volatiles ) hn

Action Semantics Variables that can be safely accessed from thread t copied to the

Action Semantics Variables that can be safely accessed from thread t copied to the set for synchronization variable x release(t, x)h = h[x →h(t)∪h(x)] Variables in the set of synchronization variable x will now be safely accessed by thread t acquire(t, x)h = h[t →h(t)∪h(x)] Only thread t which changed x can safely access it. invalidate(t, x) h = λz. if (t = z) then h(z) else h(z){x} The non-volatile fields of the newly created object can be safely accessed by the thread who created it. The volatile fields are initialized to refer to empty sets. new(t, fields, volatiles)h = λz. if (t = z) then h(t) ∪ fields else if (z ∈ volatiles) then{} else h(z)

Implementation of the h-function

Implementation of the h-function

How JRF extends JPF

How JRF extends JPF

Test Programs Sources # of examples found to have data races Textbook by Herhily

Test Programs Sources # of examples found to have data races Textbook by Herhily and Shavitz. 65 19 Amino Concurrent Building Blocks Library 10 9 Google Concurrent Data Structures Workshop. 12 10 Java Grande Forum Benchmark Suite 10 6 Webserver Simulator – Student Projects 28 7

Time Overhead of JRF Time (secs) 1000 100 JPF JRF 10 1 0 2

Time Overhead of JRF Time (secs) 1000 100 JPF JRF 10 1 0 2 4 6 Test Programs 8 10 12

Space Overhead of JRF Memory (MB) 1000 100 JPF JRF 10 1 0 2

Space Overhead of JRF Memory (MB) 1000 100 JPF JRF 10 1 0 2 4 6 Test Programs 8 10 12

Outline The Problem: Getting Multithreaded Java Programs Right Java Memory Model Our Solution: Java

Outline The Problem: Getting Multithreaded Java Programs Right Java Memory Model Our Solution: Java Race Finder What is model checking anyway? Representing Happens-before Heuristic-based Search Code Modification Suggestions

Finding the data race quickly initial state State space of a program race Each

Finding the data race quickly initial state State space of a program race Each path from initial state to a leaf state represents a separate execution.

Finding the data race using DFS initial state State space of a program DFS

Finding the data race using DFS initial state State space of a program DFS counter-example race Each path from initial state to a leaf state represents a separate execution.

Finding the data race using BFS initial state State space of a program BFS

Finding the data race using BFS initial state State space of a program BFS counter-example race Each path from initial state to a leaf state represents a separate execution.

Heuristic-Based Data Race Search Our goal is to reach a state that has a

Heuristic-Based Data Race Search Our goal is to reach a state that has a data race as quick as possible. Assign a traversal priority to each program state based on how close it may be to a racy state. Writes-First (WF): Prefer write statements to read statements Watch-Written (WW): Prefer access to memory locations recently written by another thread Avoid Release/Acquire (ARA): Avoid scheduling threads that perform proper synchronization. Acquire-First (AF): Prefer acquire operations that do not have a matching release operation.

An Example: Peterson’s Mutual Exclusion Algorithm - Version 1 Initialization: flag[0] = flag[1] =

An Example: Peterson’s Mutual Exclusion Algorithm - Version 1 Initialization: flag[0] = flag[1] = turn = shared = 0 /* all non-volatile */ Thread 1 Thread 2 s 1: flag[0] = 1; s 6: flag[1] = 1; s 2: turn = 1; s 7: turn = 0; s 3: while (flag[1] == 1 && turn == 1) { /*spin*/} s 8: while (flag[0] == 1 && turn == 0) { /*spin*/} s 4: shared++; /* critical section */ s 9: shared++; /* critical section */ s 5: flag[0] = 0; s 10: flag[0] = 0;

DFS vs Heuristic Search DFS Search Path Thread 1 s 1: flag[0] = 1;

DFS vs Heuristic Search DFS Search Path Thread 1 s 1: flag[0] = 1; Heuristic Search Path s 2: turn = 1; Thread 2 s 4: shared++; /* critical section */ s 6: flag[1] = 1; s 5: flag[0] = 0; s 7: turn = 0; Thread 2 s 7: turn = 0; s 1: flag[0] = 1; s 2: turn = 1; s 3: while (flag[1] == 1 && turn == 1) { /*spin*/} s 6: flag[1] = 1; Thread 1 Race! turn not in h(thread 2)!

Experimental Results: Heuristic Search Code (lines of code) Search Dis. Barrier (232) DFS Heuristic

Experimental Results: Heuristic Search Code (lines of code) Search Dis. Barrier (232) DFS Heuristic BFS 109 79 2589 109 39 36 4 3 256 53 46 644 Moldyn (1252) DFS Heuristic BFS 2821 1896 5127 2821 950 >574* 231 257 1014 579 518 988 DEQueue (334) DFS Heuristic BFS 33 19 30 28 12 9 1 1 2 27 26 31 Binary. Static. Tree DFS Barrier (1910) Heuristic BFS 61 137 2275 61 52 >18* 7 9 2221 66 86 986 *: JPF ran out of memory State Length Time (sec) Memory (MB)

Outline The Problem: Getting Multithreaded Java Programs Right Java Memory Model Our Solution: Java

Outline The Problem: Getting Multithreaded Java Programs Right Java Memory Model Our Solution: Java Race Finder What is model checking anyway? Representing Happens-before Heuristic-based Search Code Modification Suggestions

What went wrong? Thread 1 s 1: flag[0] = 1; s 2: turn =

What went wrong? Thread 1 s 1: flag[0] = 1; s 2: turn = 1; Thread 2 source statement removes turn from h(thread 2) s 6: flag[1] = 1; s 7: turn = 0; manifest statement accesses turn when turn is not in h(thread 2)

How to fix it? Data races are due to absence of happens-before relationship Suggest

How to fix it? Data races are due to absence of happens-before relationship Suggest code modifications that will create happens-before relationship between the source and manifest statements Change the variable to volatile Change the array to an atomic array Move the source statement to make use of existing happens- before relationships due to transitivity Perform the same synchronization Change another variable to volatile to create happens-before relationships due to transitivity

Change to atomic array Thread 1 Peterson’s ME Alg. turn and flag are volatile

Change to atomic array Thread 1 Peterson’s ME Alg. turn and flag are volatile Change flag to atomic array s 1: flag[0] = 1; s 2: turn = 1; Thread 2 s 6: flag[1] = 1; removes flag[1] from h(thread 1) source statement Thread 1 s 3: while (flag[1] == 1 && turn == 1) { /*spin*/} manifest statement Accesses flag[1] when flag[1] is not in h(thread 1)

An Example for move source Initialization: go. Flag = false; volatile Data publish; Thread

An Example for move source Initialization: go. Flag = false; volatile Data publish; Thread 2 Thread 1 s 1: r = new Data(); t 1: if (publish != null) { s 2: publish = r; t 2: while (!go. Flag); s 3: r. set. Desc(e); t 3: String s = publish. get. Desc(); s 4: go. Flag = true; t 4: assert(s. equals(“e”); • Updates published object after making the reference visible • Compiler may reorder s 3 and s 4 } • May use the published object when it is in an inconsistent state

Move source statement Thread 1 publish is volatile go. Flag is not volatile s

Move source statement Thread 1 publish is volatile go. Flag is not volatile s 1: r = new Data(); s 4: go. Flag = true; s 2: publish = r; s 3: r. set. Desc(e); Move s 4 before s 2 s 4: go. Flag = true; Thread 2 t 1: if (publish != null) { t 2: while (!go. Flag); removes go. Flag from h(thread 2) source statement Accesses go. Flag when go. Flag is not in h(thread 2) manifest statement

An Example for perform the same synchronization operation Initialization: int data; final Object lock

An Example for perform the same synchronization operation Initialization: int data; final Object lock = new Object(); Thread 2 Thread 1 s 1: print (data); t 1: synchronized (lock) { /*lock*/ t 2: data = 1; t 3: } /*unlock*/ • For every non-volatile variable v, acquire. History(v) stores the set of safe accesses by thread t via a synchronization operation on s. • Thread 2’s safe access on data is noted as an example behavior.

Perform that synchronized block data is not volatile Thread 2 t 1: synchronized (lock)

Perform that synchronized block data is not volatile Thread 2 t 1: synchronized (lock) { /*lock*/ t 2: Perform synchronized (lock) to access data = 1; t 3: } /*unlock*/ removes data from h(thread 1) source statement Thread 1 s 0: synchronized (lock) { /*lock*/ s 1: print (data); s 2: } /*unlock*/ Accesses data when data is not in h(thread 1) manifest statement

An Example for change another to volatile Initialization: int x; boolean done = false;

An Example for change another to volatile Initialization: int x; boolean done = false; /* both non-volatile*/ Thread 1 Thread 2 s 1: x = 1; t 1: while (!done); s 2: done = true; t 2: assert(x == 1); • Potential data races both on x and done. • Should we really change both to x and done to volatile? • Can we get away by changing only one?

Change other to volatile x and done are not volatile Thread 1 s 1:

Change other to volatile x and done are not volatile Thread 1 s 1: x = 1; s 2: done = true; Change done to volatile removes x from h(thread 2) source statement Thread 2 t 1: while (!done); t 2: assert(x == 1); manifest statement accesses x when x is not in h(thread 2)

JRF-E: Eliminating Data Races JRF is configured to produce threshold # of counter-example paths

JRF-E: Eliminating Data Races JRF is configured to produce threshold # of counter-example paths and write to a file JRF-E works on the output of JRF and analyzes the counter- example paths to generate code modification suggestions For each race reports intersection of suggestions on all the relevant counter- example paths For each specific code modification suggestion reports the frequency

How did it JRF-E RESULT =========================== data race #1 happen? jrf. hbset. util. HBData.

How did it JRF-E RESULT =========================== data race #1 happen? jrf. hbset. util. HBData. Race. Exception. . . How to ___________________________ analyze counter example fix it? race source statement : "putstatic" at simple/Simple. Race. java: 64 : "x = 1; " data race manifest statement : "getstatic" at simple/Simple. Race. java: 74: "assert (x==1); " feedback on a single race Change the field "simple. Simple. Race. x from INITIALIZER" to volatile. Change the field "simple. Simple. Race. done from INITIALIZER" to volatile. ___________________________ advice from acquiring history NONE =========================== data race #2 jrf. hbset. util. HBData. Race. Exception. . . ___________________________ analyze counter example data race source statement : "putstatic" at simple/Simple. Race. java: 65 : "done = true; " data race manifest statement : "getstatic" at simple/Simple. Race. java: 73: "while(!done) { /*spin*/ }" How many times a Change the field "simple. Simple. Race. done INITIALIZER" suggestionfrom has been to volatile. made considering all advice from acquiring history ___________________________ NONE the races? ___________________________ frequency of advice [1 times] Change the field "simple. Simple. Race. x from INITIALIZER" to volatile. [2 times] Change the field "simple. Simple. Race. done from INITIALIZER" to volatile. ___________________________ statistic JRF takes 0: 0: 1 to find 2 equivalent races with 9 counterexample traces. JRF-E takes 0: 0: 0 in 9 races analysis. feedback on another race feedback on all races

JRF-E - Analyzing threshold # of races 100 10 Threshold=10 1 im W eb

JRF-E - Analyzing threshold # of races 100 10 Threshold=10 1 im W eb se rw er S so r ct fa Lu 0. 1 Threshold=1 Di Lo s. B ck ar Fr rie ee Ha r sh O Se pt im t ist ic. L ist Li M ne CS ar Lo Se ck ns * e. B Ite ar ra rie to r r_ EB. . . De qu e Time (secs) 1000 In all except MCSLock, the right suggestion made when Threshold <= 10.

Suggestions that worked Length Threshold # of Racy Fields Change (other) to volatile Change

Suggestions that worked Length Threshold # of Racy Fields Change (other) to volatile Change to atomic array Dis. Barrier 40 1 2 1 Lock. Free. Hash. S et 50 1 4 1 Optimistic. List 42 1 3 1 MCSLock 65 100 3 2 Linear. Sense. Bar rier. 61 10 2 1 Iterator_EBDeq ue 11 1 Lufact 19 1 1 1 Sor 44 1 2 1 Webserver Sim. 68 1 2 Use synchronized block 1

Conclusion Even experts can benefit from tool support for detecting data races. JRF can

Conclusion Even experts can benefit from tool support for detecting data races. JRF can also analyze synchronization idioms that do not use locking. Has become an official extension of Java Path Finder http: //babelfish. arc. nasa. gov/trac/jpf JRF-E makes working suggestions for most of the data races in our experiments. JRF-E can teach programmers the intricacies of Java Memory Model.

Thank You Questions?

Thank You Questions?