Skip to content

Commit f01377d

Browse files
authored
[tsan] Mark pthread_*_lock functions as blocking (llvm#84162)
Fixes llvm#83561. When a thread is blocked on a mutex and we send an async signal to that mutex, it never arrives because tsan thinks that `pthread_mutex_lock` is not a blocking function. This patch marks `pthread_*_lock` functions as blocking so we can successfully deliver async signals like `SIGPROF` when the thread is blocked on them. See the issue also for more details. I also added a test, which is a simplified version of the compiler explorer example I posted in the issue. Please let me know if you have any other ideas or things to improve! Happy to work on them. Also I filed llvm#83844 which is more tricky because we don't have a libc wrapper for `SYS_futex`. I'm not sure how to intercept this yet. Please let me know if you have ideas on that as well. Thanks!
1 parent 5b544b5 commit f01377d

File tree

2 files changed

+76
-5
lines changed

2 files changed

+76
-5
lines changed

compiler-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp

+5-5
Original file line numberDiff line numberDiff line change
@@ -1340,7 +1340,7 @@ TSAN_INTERCEPTOR(int, pthread_mutex_destroy, void *m) {
13401340
TSAN_INTERCEPTOR(int, pthread_mutex_lock, void *m) {
13411341
SCOPED_TSAN_INTERCEPTOR(pthread_mutex_lock, m);
13421342
MutexPreLock(thr, pc, (uptr)m);
1343-
int res = REAL(pthread_mutex_lock)(m);
1343+
int res = BLOCK_REAL(pthread_mutex_lock)(m);
13441344
if (res == errno_EOWNERDEAD)
13451345
MutexRepair(thr, pc, (uptr)m);
13461346
if (res == 0 || res == errno_EOWNERDEAD)
@@ -1385,7 +1385,7 @@ TSAN_INTERCEPTOR(int, pthread_mutex_clocklock, void *m,
13851385
__sanitizer_clockid_t clock, void *abstime) {
13861386
SCOPED_TSAN_INTERCEPTOR(pthread_mutex_clocklock, m, clock, abstime);
13871387
MutexPreLock(thr, pc, (uptr)m);
1388-
int res = REAL(pthread_mutex_clocklock)(m, clock, abstime);
1388+
int res = BLOCK_REAL(pthread_mutex_clocklock)(m, clock, abstime);
13891389
if (res == errno_EOWNERDEAD)
13901390
MutexRepair(thr, pc, (uptr)m);
13911391
if (res == 0 || res == errno_EOWNERDEAD)
@@ -1403,7 +1403,7 @@ TSAN_INTERCEPTOR(int, pthread_mutex_clocklock, void *m,
14031403
TSAN_INTERCEPTOR(int, __pthread_mutex_lock, void *m) {
14041404
SCOPED_TSAN_INTERCEPTOR(__pthread_mutex_lock, m);
14051405
MutexPreLock(thr, pc, (uptr)m);
1406-
int res = REAL(__pthread_mutex_lock)(m);
1406+
int res = BLOCK_REAL(__pthread_mutex_lock)(m);
14071407
if (res == errno_EOWNERDEAD)
14081408
MutexRepair(thr, pc, (uptr)m);
14091409
if (res == 0 || res == errno_EOWNERDEAD)
@@ -1446,7 +1446,7 @@ TSAN_INTERCEPTOR(int, pthread_spin_destroy, void *m) {
14461446
TSAN_INTERCEPTOR(int, pthread_spin_lock, void *m) {
14471447
SCOPED_TSAN_INTERCEPTOR(pthread_spin_lock, m);
14481448
MutexPreLock(thr, pc, (uptr)m);
1449-
int res = REAL(pthread_spin_lock)(m);
1449+
int res = BLOCK_REAL(pthread_spin_lock)(m);
14501450
if (res == 0) {
14511451
MutexPostLock(thr, pc, (uptr)m);
14521452
}
@@ -1521,7 +1521,7 @@ TSAN_INTERCEPTOR(int, pthread_rwlock_timedrdlock, void *m, void *abstime) {
15211521
TSAN_INTERCEPTOR(int, pthread_rwlock_wrlock, void *m) {
15221522
SCOPED_TSAN_INTERCEPTOR(pthread_rwlock_wrlock, m);
15231523
MutexPreLock(thr, pc, (uptr)m);
1524-
int res = REAL(pthread_rwlock_wrlock)(m);
1524+
int res = BLOCK_REAL(pthread_rwlock_wrlock)(m);
15251525
if (res == 0) {
15261526
MutexPostLock(thr, pc, (uptr)m);
15271527
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// RUN: %clang_tsan %s -lstdc++ -o %t && %run %t 2>&1 | FileCheck %s
2+
3+
#include "test.h"
4+
#include <pthread.h>
5+
#include <signal.h>
6+
#include <stdio.h>
7+
8+
#include <cassert>
9+
#include <condition_variable>
10+
#include <mutex>
11+
12+
std::mutex sampler_mutex; //dummy mutex to lock in the thread we spawn.
13+
std::mutex done_mutex; // guards the cv and done variables.
14+
std::condition_variable cv;
15+
bool done = false;
16+
17+
void *ThreadFunc(void *x) {
18+
while (true) {
19+
// Lock the mutex
20+
std::lock_guard<std::mutex> guard(sampler_mutex);
21+
// Mutex is released at the end
22+
}
23+
24+
return nullptr;
25+
}
26+
27+
static void SigprofHandler(int signal, siginfo_t *info, void *context) {
28+
// Assuming we did some work, change the variable to let the main thread
29+
// know that we are done.
30+
{
31+
std::unique_lock<std::mutex> lck(done_mutex);
32+
done = true;
33+
cv.notify_one();
34+
}
35+
}
36+
37+
int main() {
38+
alarm(60); // Kill the test if it hangs.
39+
40+
// Install the signal handler
41+
struct sigaction sa;
42+
sa.sa_sigaction = SigprofHandler;
43+
sigemptyset(&sa.sa_mask);
44+
sa.sa_flags = SA_RESTART | SA_SIGINFO;
45+
if (sigaction(SIGPROF, &sa, 0) != 0) {
46+
fprintf(stderr, "failed to install signal handler\n");
47+
abort();
48+
}
49+
50+
// Spawn a thread that will just loop and get the mutex lock:
51+
pthread_t thread;
52+
pthread_create(&thread, NULL, ThreadFunc, NULL);
53+
54+
// Lock the mutex before sending the signal
55+
std::lock_guard<std::mutex> guard(sampler_mutex);
56+
// From now on thread 1 will be waiting for the lock
57+
58+
// Send the SIGPROF signal to thread.
59+
int r = pthread_kill(thread, SIGPROF);
60+
assert(r == 0);
61+
62+
// Wait until signal handler sends the data.
63+
std::unique_lock lk(done_mutex);
64+
cv.wait(lk, [] { return done; });
65+
66+
// We got the done variable from the signal handler. Exiting successfully.
67+
fprintf(stderr, "PASS\n");
68+
}
69+
70+
// CHECK-NOT: WARNING: ThreadSanitizer:
71+
// CHECK: PASS

0 commit comments

Comments
 (0)