|
RaceCheckerClass
The RaceChecker class -- helps confirming data races.
The RaceChecker class helps confirming data races, e.g. those detected by Helgrind or ThreadSanitizer. If RaceChecker detected a race it is a 100% proof of a race. If it did not detect a race it proves nothing. How it worksWhen the program enters RaceChecker's constructor, it remembers the address of the racey object (e.g. &foo) and then sleeps. Then, in the destructor, it forgets &foo. If Thread2 entered RaceChecker's constructor while Thread1 is sleeping in RaceChecker's constructor with the same racey object (&foo), there are two concurrent accesses to &foo. If one of them is write -- bingo, we found a race. The code is pretty simple, it might be better than my explanation :) See race_checker.h and all race checker files for details. Exampleint foo = 0; // The racey address.
// Several reads and writes, nested calls to RaceChecker.
void ReaderAndWriter() {
for (int i = 0; i < 1000; i++) {
RaceChecker read_checker(RaceChecker::READ, &foo);
assert(foo >= 0); // Just a read.
if ((i % 10) == 0) {
RaceChecker write_checker(RaceChecker::WRITE, &foo);
foo++;
}
assert(foo >= 0); // Just a read.
}
}
// More functions to make the stack traces more interesting.
static void *Dummy3(void *x) { ReaderAndWriter(); return NULL; }
static void *Dummy2(void *x) { return Dummy3(x); }
static void *Dummy1(void *x) { return Dummy2(x); }
int main() {
pthread_t threads[3];
pthread_create(&threads[0], NULL, &Dummy1, NULL);
pthread_create(&threads[1], NULL, &Dummy2, NULL);
pthread_create(&threads[2], NULL, &Dummy3, NULL);
pthread_join(threads[0], NULL);
pthread_join(threads[1], NULL);
pthread_join(threads[2], NULL);
}% g++ -g race_checker.cc race_checker_unittest.cc -lpthread % RACECHECKER=1 ./a.out 2>&1 | head -19 | ./symbolize.py | c++filt Race found between these points === writer: ./a.out[0x40541a] <ReaderAndWriter()> ./a.out[0x4054a7] <Dummy3(void*)> ./a.out[0x4054c3] <Dummy2(void*)> ./a.out[0x4054db] <Dummy1(void*)> /lib64/tls/libpthread.so.0[0x2aaaaacc7f9f] <_fini> /lib64/tls/libc.so.6(clone+0x72)[0x2aaaab719ea2] === reader: ./a.out[0x4053ae] <ReaderAndWriter()> ./a.out[0x4054a7] <Dummy3(void*)> ./a.out[0x4054c3] <Dummy2(void*)> /lib64/tls/libpthread.so.0[0x2aaaaacc7f9f] <_fini> /lib64/tls/libc.so.6(clone+0x72)[0x2aaaab719ea2] === reader: ./a.out[0x4053ae] <ReaderAndWriter()> ./a.out[0x4054a7] <Dummy3(void*)> /lib64/tls/libpthread.so.0[0x2aaaaacc7f9f] <_fini> /lib64/tls/libc.so.6(clone+0x72)[0x2aaaab719ea2] |
► Sign in to add a comment
Bit confused here. How does the race checker class confirm these data races? My understanding is as follows:
1. Get a list of potential data races using ThreadSanitizer or Helgrind, say you got 10 data races.
2. Re run the program again as shown in above example. 3. Only the data races which are confirmed will be reported i.e. filter out the in feasible data races. So at the end of this step we might get only say 2 data races which are certainly present.
Is this correct? How does the race_checker class works?
If RaceChecker detected a race it is a 100% proof of a race. If it did not detect a race it proves nothing. In my experience, almost all races are confirmed by RaceChecker, but you need play with different values of timeout.
When the program enters RaceChecker?'s constructor, it remembers the address of the racey object (e.g. foo) and then sleeps. Then, in the destructor, it forgets foo.
If Thread2 entered RaceChecker?'s constructor while Thread1 is sleeping in RaceChecker?'s constructor with the same racey object (foo), there are two concurrent accesses to foo. If one of them is write -- bingo, we found a race. The code is pretty simple, it might be better than my explanation :)
Thanks, this is very helpful.
A few relevant references:
K. Sen, "Race Directed Random Testing of Concurrent Programs," in Proc. ACM SIGPLAN Conference on Programming Language Design and Implementation (PLDI'08), 2008, pp. 11-21.
http://spl.cs.berkeley.edu/~ksen/papers/raced.pdf
http://srl.cs.berkeley.edu/~ksen/calfuzzer/