Detecting Data Races in Multi-Threaded Programs

errorhandleΛογισμικό & κατασκευή λογ/κού

18 Νοε 2013 (πριν από 3 χρόνια και 8 μήνες)

61 εμφανίσεις

Eraser

A Dynamic Data-Race Detector
for Multi-Threaded Programs
MultiRace
An Efficient On-the-Fly Data-Race Detection Tool
for Multi-Threaded C++ Programs
John C. Linford
Detecting Data Races in
Multi-Threaded Programs
Slide 1 / 31
Key Points
1.
Data races are easy to cause and
hard to debug.
2.
We can't detect all data races.
3.
Detection of
feasible
races relies on
detection of
apparent
data races.
4.
D
ata race detection tools are either
static
or
dynamic
(on-the-fly
and
postmortem).
Slide 2 / 31
Key Points Cont.
5.
Data races can be prevented by following a
locking discipline
.
6.
C
ommonly used detection algorithms are
Lockset
and
DJIT
(Happens-Before).
7.
Lockset
maintains a set of
candidate locks

for each shared memory location. If a
shared location is accessed when this set is
empty, there has been a violation of the
locking discipline
.
Slide 3 / 31
Key Points Cont.
8.
Lockset is vulnerable to false alarms.
9.
DJIT uses a
logging mechanism
. Every
shared memory access is logged to see
that it “happens before” prior accesses to
the same location.
10.
DJIT is dependent on the scheduler and
thread interleaving.

11.
Combining happens-before with Lockset
can improve detection accuracy.
Slide 4 / 31
Data Race Review

At least one access is a write,

Simultaneous access is not prevented.

Example (variable X is global and shared)
Thread 1
Thread 2
X = 2.7
X = 3.1
Z = 2
T = X
Two threads access a shared variable
Execution
Slide 5 / 31
Data Race Demonstration

Data races often lead to
unexpected and even
nondeterministic behavior

The outcome may be
dependent on specific
execution order
(threads' interleaving)

Click image to start
Slide 6 / 31
Data Race Demonstration Cont.
t1 = new Thread() {

public void run() {

while(t1 != null) {

...

shared[0] = shared[0] + 1;

...

}

...
t2 = new Thread() {

public void run() {

while(t2 != null) {

...

shared[0] = shared[0] + 1;

...

}

...
int[] shared = new int[1];
Thread t1, t2;
public DataRace() {

// Initialize and start threads (shown below)
}
Slide 7 / 31
We Can't Detect All Data Races

For
t
threads of
n
instructions each, the
number of possible orders is about
t
n
*
t
.

All possible inputs would have to be tested.

Adding detection code or debugging
information can change the execution
schedule.
[
Pozniansky & Schuster, 2003]
Slide 8 / 31
Feasible Data Races

Races based on possible behavior of the
program.

Actual data races which could manifest in
any execution.

Locating feasible races requires a full
analysis of the program's semantics.

Exactly locating feasible races is NP-hard
[
Pozniansky & Schuster, 2003].
Slide 9 / 31
Apparent Data Races

Approximations of feasible data races based
on synchronization behavior in an execution.

Easier to detect, but less accurate.

Apparent races exist if and only if at least
one feasible race exists.

Locating
all
apparent races is NP-hard
[
Pozniansky & Schuster, 2003].
Slide 10 / 31
Eraser
[Savage, Burrows, et al., 1997]

On-the-fly tool.

Lockset algorithm.

Code annotations to flag
special cases.

Can be extended to
handle other locking
mechanisms (IRQs).

Used in industry.

Slows applications by a
factor of 10 – 30.
Slide 11 / 31
The Lockset Algorithm
(Simple Form)
Let
locks_held(t)
be the set of
locks held by thread t
For each shared memory location
v
,
initialize
C(v)
to the set of all locks
On each access to
v
by thread
t
,
Set
C(v)
:=
C(v)



locks_held(t)
If
C(v)
:= {}, then
issue a warning

Detects races not manifested in one execution.

Generates false alarms.
Lockset
Refinement
Slide 12 / 31
Lockset Refinement Example
Program
locks_held
C(v)
int v;
v := 1024;
lock(mu1);
v := v + 1;
unlock(mu1);
lock(mu2);
v := v + 1;
unlock(mu2);
{}
{mu1}
{}
{mu2}
{}
{mu1, mu2}
{mu1}
{}
Warning!
Slide 13 / 31
Simple Lockset is too Strict

Variables initialized without locks held.

Read-shared data read without locks held.

Read-write locking mechanisms
(producer / consumer).
Lockset will produce false-positives for:
Slide 14 / 31
Lockset State Diagram
Virgin
Exclusive
Shared
Shared-Modified
wr
rd,
2
nd
thread
wr
wr, 2
nd
thread
rd / wr,
1
st
thread
rd
Warnings are issued only in the Shared-Modified state
Slide 15 / 31
Lockset State Example
Program
locks_held
C(v)
State(v)
int v;
v := 1024;
lock(mu1);
v := v + 1;
unlock(mu1);
lock(mu2);
v := v + 1;
unlock(mu2);
{}
{mu1}
{}
{mu2}
{}
{mu1, mu2}
{mu1}
{}
Virgin
Exclusive
Shared
Shared-Modified
T1
T2
T1
Race detected
correctly
Slide 16 / 31
The Lockset Algorithm
(Extended)
Let
locks_held(t)
be the set of locks
held in any mode by thread
t
Let
write_locks_held(t)
be the set of
locks held in write mode by thread
t
For each shared memory location
v
,
initialize
C(v)
to the set of all locks
On each read of
v
by thread
t
,
Set
C(v)
:=
C(v)



locks_held(t)
If
C(v)
= {}, then
issue a warning
On each write of
v
by thread
t
,
Set
C(v)
:=
C(v)



write_locks_held(t)
If
C(v)
= {}, then
issue a warning
Slide 17 / 31
Unhandled Cases in Eraser

Memory reuse

Unrecognized thread API

Initialization in different thread

Benign races
if(fptr == NULL) {
lock(fptr_mu);
if(fptr == NULL) {
fptr = open(filename);
}
unlock(fptr_mu);
}
Slide 18 / 31
Unhandled Cases in Eraser
Cont.

Race on and will be missed if executes first
int[] shared = new int[1];
Thread t = new Thread() {
public void run() {
shared = shared + 1;

...
};
...
shared = 512;
t.start();
shared = shared + 256;
...
[Seragiotto, 2005]
Slide 19 / 31
Unhandled Cases in Eraser
Cont.
Program
State(shared)
Data race is not detected!
int[] shared = new int[1];
shared = 512;
t.start();
shared = shared + 256;
Thread t = new Thread() {
public void run() {
shared = shared + 1;

...
};
...
Virgin
Exclusive
Shared
Shared-Modified
locks_held
C(v)
{}
{mu1}
{}
Slide 20 / 31
Unhandled Cases in Eraser
Cont.
Data race is detected!
Program
State(shared)
int[] shared = new int[1];
shared = 512;
t.start();
Thread t = new Thread() {
public void run() {

shared = shared + 1;
...
};
shared = shared + 256;
Virgin
Exclusive
Shared
Shared-Modified
locks_held
C(v)
{}
{mu1}
{}
Slide 21 / 31
Improved Lockset
State Diagram [Seragiotto, 2005]
Initialized
(Exclusive)
Initialized
and Read
Shared
Shared-Modified
Initialized
and Written
Virgin
rd,
2
nd
thread
rd,
not 2
nd
thread
wr, any
rd / wr,
not 2
nd
thread
wr,
2
nd
thread
wr, 2
nd
thread
wr,
not 2
nd
thread
first
access
rd / wr,
1
st
thread
rd,
2
nd
thread
rd,
any
rd / wr,
2
nd
thread
Slide 22 / 31
Implementations: Eraser

Maintains hash table of sets of locks.

Represents each set of locks with an index.

Every shared memory location has shadow
memory containing lockset index and state.

Shadow memory is located by adding offset
to shared memory location address.
Slide 23 / 31
Implementations: Eraser
v
Program
Memory
Shadow
Memory
&v +
Shadow
Offset
Lockset
Index
Table
mu1
mu2
Lock
Vector
Shared memory location
v

is associated with locks
mu1
and
mu2
[Savage, Burrows, et al., 2005]
Slide 24 / 31
Implementations: Ladybug
[Seragiotto, 2005]

GC Eraser:

Maintains lock list for threads
and
variables.

Uses weak references (less memory usage).

Fast Eraser:

Maintains lock list for threads
and
variables.

Uses strong references (faster).

Vanilla Eraser:

Same as eraser, but keeps hash table of lock
sets already created.
Slide 25 / 31
Ladybug Demonstration

Rewrite class file

java -cp Ladybug.jar
br.ime.usp.ladybug.LadybugClassRewriter
DataRace.class

Run modified class

java -cp Ladybug.jar:. DataRace

Races reported as exceptions
br.ime.usp.ladybug.RCException: [line 9]
Race condition detected: t2 of DataRace (hash code = 1b67f74) with Thread-0
at br.ime.usp.ladybug.StaticLadybug.warn(StaticLadybug.java:1014)
at br.ime.usp.ladybug.eraser.EraserGC.writeField(EraserGC.java:47)
...
at DataRace.access$202(DataRace.java:9)
at DataRace$1.run(
DataRace.java:37
)

Can also use GUI
Slide 26 / 31
MultiRace
[Pozniansky & Schuster, 2003]

On-the-fly tool.

Improved Lockset
and DJIT+.

Significantly fewer
false alarms than
Eraser.

Minimal impact on
program speed.
Slide 27 / 31
DJIT

Based on Lamport's
Happens-Before

relationship.

Detects the first apparent data race when it
actually occurs.

Can be extended to detect races after the
first (DJIT+).

Dependent on scheduling order.
Slide 28 / 31
Benefits of Combining
Lockset and DJIT

Races are in the intersection of warnings.

Lockset's insensitivity compensates for
DJIT's sensitivity to thread interleaving.

Lockset reduces DJIT execution overhead.

Lockset warnings are “ranked” by DJIT.

Implementation overhead is minimized.
Slide 29 / 31
Conclusion
1.
Data races are easy to cause and
hard to debug.
2.
Data race detection tools are either
static
or
dynamic
(on-the-fly
and
postmortem).
3.
C
ommonly used detection algorithms are
Lockset
and
DJIT
(Happens-Before).
4.
Lockset is vulnerable to false alarms.
5.
DJIT is dependent on the scheduler and
thread interleaving.
6.
Combining happens-before with Lockset
can improve detection accuracy.
Slide 30 / 31
References

S. Savage, M. Burrows, G. Nelson, P. Sobalvarro, and T.E.
Anderson. Eraser: A Dynamic Data Race Detector for
Multithreaded Programs. In
ACM Transactions on Computer
Systems
, 15(4): pp. 391-411, 1997.

E. Pozniansky and A. Schuster. Dynamic Data-Race
Detection in Lock-Based Multi-Threaded Programs. In
Principles and Practice of Parallel Programming
, pp. 170-
190, 2003.

E. Pozniansky and A. Schuster.
MultiRace: Efficient Data
Race Detection Tool for Multithreaded C++ Programs
. 2005.
http://dsl.cs.technion.ac.il/projects/multirace/MultiRace.htm
.

C. Seragiotto.
Ladybug: Race Condition Detection in Java.
2005.

http://www.par.univie.ac.at/~clovis/ladybug/
Slide 31 / 31