diff --git a/compiler-rt/lib/tsan/rtl/tsan_interceptors.cc b/compiler-rt/lib/tsan/rtl/tsan_interceptors.cc index 8dff0cbd40cf..df4d5fd20938 100644 --- a/compiler-rt/lib/tsan/rtl/tsan_interceptors.cc +++ b/compiler-rt/lib/tsan/rtl/tsan_interceptors.cc @@ -1056,13 +1056,25 @@ static void *init_cond(void *c, bool force = false) { } struct CondMutexUnlockCtx { + ScopedInterceptor *si; ThreadState *thr; uptr pc; void *m; }; static void cond_mutex_unlock(CondMutexUnlockCtx *arg) { + // pthread_cond_wait interceptor has enabled async signal delivery + // (see BlockingCall below). Disable async signals since we are running + // tsan code. Also ScopedInterceptor and BlockingCall destructors won't run + // since the thread is cancelled, so we have to manually execute them + // (the thread still can run some user code due to pthread_cleanup_push). + ThreadSignalContext *ctx = SigCtx(arg->thr); + CHECK_EQ(atomic_load(&ctx->in_blocking_func, memory_order_relaxed), 1); + atomic_store(&ctx->in_blocking_func, 0, memory_order_relaxed); MutexLock(arg->thr, arg->pc, (uptr)arg->m); + // Undo BlockingCall ctor effects. + arg->thr->ignore_interceptors--; + arg->si->~ScopedInterceptor(); } INTERCEPTOR(int, pthread_cond_init, void *c, void *a) { @@ -1077,12 +1089,17 @@ INTERCEPTOR(int, pthread_cond_wait, void *c, void *m) { SCOPED_TSAN_INTERCEPTOR(pthread_cond_wait, cond, m); MutexUnlock(thr, pc, (uptr)m); MemoryAccessRange(thr, pc, (uptr)c, sizeof(uptr), false); - CondMutexUnlockCtx arg = {thr, pc, m}; + CondMutexUnlockCtx arg = {&si, thr, pc, m}; + int res = 0; // This ensures that we handle mutex lock even in case of pthread_cancel. // See test/tsan/cond_cancel.cc. - int res = call_pthread_cancel_with_cleanup( - (int(*)(void *c, void *m, void *abstime))REAL(pthread_cond_wait), - cond, m, 0, (void(*)(void *arg))cond_mutex_unlock, &arg); + { + // Enable signal delivery while the thread is blocked. + BlockingCall bc(thr); + res = call_pthread_cancel_with_cleanup( + (int(*)(void *c, void *m, void *abstime))REAL(pthread_cond_wait), + cond, m, 0, (void(*)(void *arg))cond_mutex_unlock, &arg); + } if (res == errno_EOWNERDEAD) MutexRepair(thr, pc, (uptr)m); MutexLock(thr, pc, (uptr)m); @@ -1094,12 +1111,16 @@ INTERCEPTOR(int, pthread_cond_timedwait, void *c, void *m, void *abstime) { SCOPED_TSAN_INTERCEPTOR(pthread_cond_timedwait, cond, m, abstime); MutexUnlock(thr, pc, (uptr)m); MemoryAccessRange(thr, pc, (uptr)c, sizeof(uptr), false); - CondMutexUnlockCtx arg = {thr, pc, m}; + CondMutexUnlockCtx arg = {&si, thr, pc, m}; + int res = 0; // This ensures that we handle mutex lock even in case of pthread_cancel. // See test/tsan/cond_cancel.cc. - int res = call_pthread_cancel_with_cleanup( - REAL(pthread_cond_timedwait), cond, m, abstime, - (void(*)(void *arg))cond_mutex_unlock, &arg); + { + BlockingCall bc(thr); + res = call_pthread_cancel_with_cleanup( + REAL(pthread_cond_timedwait), cond, m, abstime, + (void(*)(void *arg))cond_mutex_unlock, &arg); + } if (res == errno_EOWNERDEAD) MutexRepair(thr, pc, (uptr)m); MutexLock(thr, pc, (uptr)m); diff --git a/compiler-rt/lib/tsan/rtl/tsan_platform_linux.cc b/compiler-rt/lib/tsan/rtl/tsan_platform_linux.cc index 659e8d8a8345..8b53eff4327d 100644 --- a/compiler-rt/lib/tsan/rtl/tsan_platform_linux.cc +++ b/compiler-rt/lib/tsan/rtl/tsan_platform_linux.cc @@ -394,6 +394,8 @@ int ExtractRecvmsgFDs(void *msgp, int *fds, int nfd) { return res; } +// Note: this function runs with async signals enabled, +// so it must not touch any tsan state. int call_pthread_cancel_with_cleanup(int(*fn)(void *c, void *m, void *abstime), void *c, void *m, void *abstime, void(*cleanup)(void *arg), void *arg) { diff --git a/compiler-rt/lib/tsan/rtl/tsan_platform_mac.cc b/compiler-rt/lib/tsan/rtl/tsan_platform_mac.cc index 63f1748e13ce..b72d9b07ef35 100644 --- a/compiler-rt/lib/tsan/rtl/tsan_platform_mac.cc +++ b/compiler-rt/lib/tsan/rtl/tsan_platform_mac.cc @@ -76,6 +76,8 @@ void InitializePlatform() { } #ifndef SANITIZER_GO +// Note: this function runs with async signals enabled, +// so it must not touch any tsan state. int call_pthread_cancel_with_cleanup(int(*fn)(void *c, void *m, void *abstime), void *c, void *m, void *abstime, void(*cleanup)(void *arg), void *arg) { diff --git a/compiler-rt/test/tsan/cond_cancel.c b/compiler-rt/test/tsan/cond_cancel.c index e744570b12fc..ddfb745174f6 100644 --- a/compiler-rt/test/tsan/cond_cancel.c +++ b/compiler-rt/test/tsan/cond_cancel.c @@ -8,9 +8,14 @@ pthread_mutex_t m; pthread_cond_t c; int x; +static void my_cleanup(void *arg) { + printf("my_cleanup\n"); + pthread_mutex_unlock((pthread_mutex_t*)arg); +} + void *thr1(void *p) { pthread_mutex_lock(&m); - pthread_cleanup_push((void(*)(void *arg))pthread_mutex_unlock, &m); + pthread_cleanup_push(my_cleanup, &m); barrier_wait(&barrier); while (x == 0) pthread_cond_wait(&c, &m); diff --git a/compiler-rt/test/tsan/signal_cond.cc b/compiler-rt/test/tsan/signal_cond.cc new file mode 100644 index 000000000000..f5eae745d407 --- /dev/null +++ b/compiler-rt/test/tsan/signal_cond.cc @@ -0,0 +1,51 @@ +// RUN: %clang_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s +#include "test.h" +#include +#include +#include +#include + +// Test that signals can be delivered to blocked pthread_cond_wait. +// https://code.google.com/p/thread-sanitizer/issues/detail?id=91 + +int g_thread_run = 1; +pthread_mutex_t mutex; +pthread_cond_t cond; +sem_t sem; + +void sig_handler(int sig) { + (void)sig; + write(1, "SIGNAL\n", sizeof("SIGNAL\n") - 1); + sem_post(&sem); +} + +void* my_thread(void* arg) { + pthread_mutex_lock(&mutex); + while (g_thread_run) + pthread_cond_wait(&cond, &mutex); + pthread_mutex_unlock(&mutex); + return 0; +} + +int main() { + sem_init(&sem, 0, 0); + signal(SIGUSR1, &sig_handler); + pthread_t thr; + pthread_create(&thr, 0, &my_thread, 0); + // wait for thread to get inside pthread_cond_wait + // (can't use barrier_wait for that) + sleep(1); + pthread_kill(thr, SIGUSR1); + while (sem_wait(&sem) == -1 && errno == EINTR) { + } + pthread_mutex_lock(&mutex); + g_thread_run = 0; + pthread_cond_signal(&cond); + pthread_mutex_unlock(&mutex); + pthread_join(thr, 0); + fprintf(stderr, "DONE\n"); + return 0; +} + +// CHECK: SIGNAL +// CHECK: DONE