summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/tsan/rtl/tsan_interceptors.cc1
-rw-r--r--lib/tsan/rtl/tsan_mman.cc51
-rw-r--r--lib/tsan/rtl/tsan_rtl.h10
-rw-r--r--lib/tsan/rtl/tsan_sync.cc13
-rw-r--r--test/tsan/lots_of_threads.c30
5 files changed, 82 insertions, 23 deletions
diff --git a/lib/tsan/rtl/tsan_interceptors.cc b/lib/tsan/rtl/tsan_interceptors.cc
index f662c4477..7064194ab 100644
--- a/lib/tsan/rtl/tsan_interceptors.cc
+++ b/lib/tsan/rtl/tsan_interceptors.cc
@@ -740,6 +740,7 @@ TSAN_INTERCEPTOR(int, munmap, void *addr, long_t sz) {
if (sz != 0) {
// If sz == 0, munmap will return EINVAL and don't unmap any memory.
DontNeedShadowFor((uptr)addr, sz);
+ ScopedGlobalProcessor sgp;
ctx->metamap.ResetRange(thr->proc(), (uptr)addr, (uptr)sz);
}
int res = REAL(munmap)(addr, sz);
diff --git a/lib/tsan/rtl/tsan_mman.cc b/lib/tsan/rtl/tsan_mman.cc
index d3af9a08c..aa7198200 100644
--- a/lib/tsan/rtl/tsan_mman.cc
+++ b/lib/tsan/rtl/tsan_mman.cc
@@ -78,6 +78,38 @@ GlobalProc *global_proc() {
return reinterpret_cast<GlobalProc*>(&global_proc_placeholder);
}
+ScopedGlobalProcessor::ScopedGlobalProcessor() {
+ GlobalProc *gp = global_proc();
+ ThreadState *thr = cur_thread();
+ if (thr->proc())
+ return;
+ // If we don't have a proc, use the global one.
+ // There are currently only two known case where this path is triggered:
+ // __interceptor_free
+ // __nptl_deallocate_tsd
+ // start_thread
+ // clone
+ // and:
+ // ResetRange
+ // __interceptor_munmap
+ // __deallocate_stack
+ // start_thread
+ // clone
+ // Ideally, we destroy thread state (and unwire proc) when a thread actually
+ // exits (i.e. when we join/wait it). Then we would not need the global proc
+ gp->mtx.Lock();
+ ProcWire(gp->proc, thr);
+}
+
+ScopedGlobalProcessor::~ScopedGlobalProcessor() {
+ GlobalProc *gp = global_proc();
+ ThreadState *thr = cur_thread();
+ if (thr->proc() != gp->proc)
+ return;
+ ProcUnwire(gp->proc, thr);
+ gp->mtx.Unlock();
+}
+
void InitializeAllocator() {
allocator()->Init(common_flags()->allocator_may_return_null);
}
@@ -137,29 +169,12 @@ void *user_calloc(ThreadState *thr, uptr pc, uptr size, uptr n) {
}
void user_free(ThreadState *thr, uptr pc, void *p, bool signal) {
- GlobalProc *gp = nullptr;
- if (thr->proc() == nullptr) {
- // If we don't have a proc, use the global one.
- // There is currently only one known case where this path is triggered:
- // __interceptor_free
- // __nptl_deallocate_tsd
- // start_thread
- // clone
- // Ideally, we destroy thread state (and unwire proc) when a thread actually
- // exits (i.e. when we join/wait it). Then we would not need the global proc
- gp = global_proc();
- gp->mtx.Lock();
- ProcWire(gp->proc, thr);
- }
+ ScopedGlobalProcessor sgp;
if (ctx && ctx->initialized)
OnUserFree(thr, pc, (uptr)p, true);
allocator()->Deallocate(&thr->proc()->alloc_cache, p);
if (signal)
SignalUnsafeCall(thr, pc);
- if (gp) {
- ProcUnwire(gp->proc, thr);
- gp->mtx.Unlock();
- }
}
void OnUserAlloc(ThreadState *thr, uptr pc, uptr p, uptr sz, bool write) {
diff --git a/lib/tsan/rtl/tsan_rtl.h b/lib/tsan/rtl/tsan_rtl.h
index aa8181586..ff6901566 100644
--- a/lib/tsan/rtl/tsan_rtl.h
+++ b/lib/tsan/rtl/tsan_rtl.h
@@ -345,6 +345,16 @@ struct Processor {
DDPhysicalThread *dd_pt;
};
+#ifndef SANITIZER_GO
+// ScopedGlobalProcessor temporary setups a global processor for the current
+// thread, if it does not have one. Intended for interceptors that can run
+// at the very thread end, when we already destroyed the thread processor.
+struct ScopedGlobalProcessor {
+ ScopedGlobalProcessor();
+ ~ScopedGlobalProcessor();
+};
+#endif
+
// This struct is stored in TLS.
struct ThreadState {
FastState fast_state;
diff --git a/lib/tsan/rtl/tsan_sync.cc b/lib/tsan/rtl/tsan_sync.cc
index 15759d4e0..50fe151a0 100644
--- a/lib/tsan/rtl/tsan_sync.cc
+++ b/lib/tsan/rtl/tsan_sync.cc
@@ -152,18 +152,21 @@ void MetaMap::ResetRange(Processor *proc, uptr p, uptr sz) {
const uptr p0 = p;
const uptr sz0 = sz;
// Probe start of the range.
- while (sz > 0) {
+ for (uptr checked = 0; sz > 0; checked += kPageSize) {
bool has_something = FreeRange(proc, p, kPageSize);
p += kPageSize;
sz -= kPageSize;
- if (!has_something)
+ if (!has_something && checked > (128 << 10))
break;
}
// Probe end of the range.
- while (sz > 0) {
- bool has_something = FreeRange(proc, p - kPageSize, kPageSize);
+ for (uptr checked = 0; sz > 0; checked += kPageSize) {
+ bool has_something = FreeRange(proc, p + sz - kPageSize, kPageSize);
sz -= kPageSize;
- if (!has_something)
+ // Stacks grow down, so sync object are most likely at the end of the region
+ // (if it is a stack). The very end of the stack is TLS and tsan increases
+ // TLS by at least 256K, so check at least 512K.
+ if (!has_something && checked > (512 << 10))
break;
}
// Finally, page out the whole range (including the parts that we've just
diff --git a/test/tsan/lots_of_threads.c b/test/tsan/lots_of_threads.c
new file mode 100644
index 000000000..eef9b1cb0
--- /dev/null
+++ b/test/tsan/lots_of_threads.c
@@ -0,0 +1,30 @@
+// RUN: %clang_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s
+#include "test.h"
+
+void *thr(void *arg) {
+ // Create a sync object on stack, so there is something to free on thread end.
+ volatile int x;
+ __atomic_fetch_add(&x, 1, __ATOMIC_SEQ_CST);
+ barrier_wait(&barrier);
+ return 0;
+}
+
+int main() {
+ const int kThreads = 10;
+ barrier_init(&barrier, kThreads + 1);
+ pthread_t t[kThreads];
+ pthread_attr_t attr;
+ pthread_attr_init(&attr);
+ pthread_attr_setstacksize(&attr, 16 << 20);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+ for (int i = 0; i < kThreads; i++)
+ pthread_create(&t[i], &attr, thr, 0);
+ pthread_attr_destroy(&attr);
+ barrier_wait(&barrier);
+ sleep(1);
+ fprintf(stderr, "DONE\n");
+ return 0;
+}
+
+// CHECK: DONE
+