Index: compiler-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp =================================================================== --- compiler-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp +++ compiler-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp @@ -56,23 +56,6 @@ #define mallopt(a, b) #endif -#ifdef __mips__ -const int kSigCount = 129; -#else -const int kSigCount = 65; -#endif - -#ifdef __mips__ -struct ucontext_t { - u64 opaque[768 / sizeof(u64) + 1]; -}; -#else -struct ucontext_t { - // The size is determined by looking at sizeof of real ucontext_t on linux. - u64 opaque[936 / sizeof(u64) + 1]; -}; -#endif - #if defined(__x86_64__) || defined(__mips__) || SANITIZER_PPC64V1 #define PTHREAD_ABI_BASE "GLIBC_2.3.2" #elif defined(__aarch64__) || SANITIZER_PPC64V2 @@ -157,23 +140,6 @@ (cur_thread_init(), !cur_thread()->is_inited) namespace __tsan { -struct SignalDesc { - bool armed; - bool sigaction; - __sanitizer_siginfo siginfo; - ucontext_t ctx; -}; - -struct ThreadSignalContext { - int int_signal_send; - atomic_uintptr_t in_blocking_func; - atomic_uintptr_t have_pending_signals; - SignalDesc pending_signals[kSigCount]; - // emptyset and oldset are too big for stack. - __sanitizer_sigset_t emptyset; - __sanitizer_sigset_t oldset; -}; - // The sole reason tsan wraps atexit callbacks is to establish synchronization // between callback setup and callback execution. struct AtExitCtx { @@ -236,16 +202,6 @@ } // namespace __tsan -static ThreadSignalContext *SigCtx(ThreadState *thr) { - ThreadSignalContext *ctx = (ThreadSignalContext*)thr->signal_ctx; - if (ctx == 0 && !thr->is_dead) { - ctx = (ThreadSignalContext*)MmapOrDie(sizeof(*ctx), "ThreadSignalContext"); - MemoryResetRange(thr, (uptr)&SigCtx, (uptr)ctx, sizeof(*ctx)); - thr->signal_ctx = ctx; - } - return ctx; -} - ScopedInterceptor::ScopedInterceptor(ThreadState *thr, const char *fname, uptr pc) : thr_(thr), pc_(pc), in_ignored_lib_(false), ignoring_(false) { @@ -891,11 +847,7 @@ ThreadFinish(thr); ProcUnwire(proc, thr); ProcDestroy(proc); - ThreadSignalContext *sctx = thr->signal_ctx; - if (sctx) { - thr->signal_ctx = 0; - UnmapOrDie(sctx, sizeof(*sctx)); - } + DTLS_Destroy(); cur_thread_finalize(); } Index: compiler-rt/lib/tsan/rtl/tsan_rtl.h =================================================================== --- compiler-rt/lib/tsan/rtl/tsan_rtl.h +++ compiler-rt/lib/tsan/rtl/tsan_rtl.h @@ -321,7 +321,37 @@ } }; -struct ThreadSignalContext; +#ifdef __mips__ +struct ucontext_buffer_t { + u64 opaque[768 / sizeof(u64) + 1]; +}; +const int kSigCount = 129; +#else +struct ucontext_buffer_t { + // The size is determined by looking at sizeof of real ucontext_t on linux. + u64 opaque[936 / sizeof(u64) + 1]; +}; +const int kSigCount = 65; +#endif + +struct SignalDesc { + bool armed; + bool sigaction; + __sanitizer_siginfo siginfo; + ucontext_buffer_t ctx; +}; + +struct ThreadSignalContext { + int int_signal_send; + atomic_uintptr_t in_blocking_func; + atomic_uintptr_t have_pending_signals; + SignalDesc pending_signals[kSigCount]; + // emptyset and oldset are too big for stack. + __sanitizer_sigset_t emptyset; + __sanitizer_sigset_t oldset; +}; +// Lazy initializing getter +ThreadSignalContext *SigCtx(ThreadState *thr); struct JmpBuf { uptr sp; Index: compiler-rt/lib/tsan/rtl/tsan_rtl_thread.cpp =================================================================== --- compiler-rt/lib/tsan/rtl/tsan_rtl_thread.cpp +++ compiler-rt/lib/tsan/rtl/tsan_rtl_thread.cpp @@ -125,12 +125,6 @@ } void ThreadContext::OnFinished() { -#if SANITIZER_GO - internal_free(thr->shadow_stack); - thr->shadow_stack = nullptr; - thr->shadow_stack_pos = nullptr; - thr->shadow_stack_end = nullptr; -#endif if (!detached) { thr->fast_state.IncrementEpoch(); // Can't increment epoch w/o writing to the trace as well. @@ -138,18 +132,7 @@ ReleaseImpl(thr, 0, &sync); } epoch1 = thr->fast_state.epoch(); - - if (common_flags()->detect_deadlocks) - ctx->dd->DestroyLogicalThread(thr->dd_lt); - thr->clock.ResetCached(&thr->proc()->clock_cache); -#if !SANITIZER_GO - thr->last_sleep_clock.ResetCached(&thr->proc()->clock_cache); -#endif - thr->~ThreadState(); -#if TSAN_COLLECT_STATS - StatAggregate(ctx->stat, thr->stat); -#endif - thr = 0; + thr = nullptr; } #if !SANITIZER_GO @@ -282,7 +265,43 @@ if (thr->tls_addr && thr->tls_size) DontNeedShadowFor(thr->tls_addr, thr->tls_size); thr->is_dead = true; + + // Finish attached ThreadContext + thr->tctx = nullptr; ctx->thread_registry->FinishThread(thr->tid); + + // Clean up ThreadState +#if SANITIZER_GO + internal_free(thr->shadow_stack); + thr->shadow_stack = nullptr; + thr->shadow_stack_pos = nullptr; + thr->shadow_stack_end = nullptr; +#endif + if (common_flags()->detect_deadlocks) + ctx->dd->DestroyLogicalThread(thr->dd_lt); + thr->clock.ResetCached(&thr->proc()->clock_cache); +#if !SANITIZER_GO + thr->last_sleep_clock.ResetCached(&thr->proc()->clock_cache); +#endif + ThreadSignalContext *signal_ctx = thr->signal_ctx; + if (signal_ctx) { + thr->signal_ctx = nullptr; + UnmapOrDie(signal_ctx, sizeof(*signal_ctx)); + } +#if TSAN_COLLECT_STATS + StatAggregate(ctx->stat, thr->stat); +#endif + thr->~ThreadState(); +} + +ThreadSignalContext *SigCtx(ThreadState *thr) { + ThreadSignalContext *ctx = (ThreadSignalContext *)thr->signal_ctx; + if (ctx == 0 && !thr->is_dead) { + ctx = (ThreadSignalContext *)MmapOrDie(sizeof(*ctx), "ThreadSignalContext"); + MemoryResetRange(thr, (uptr)&SigCtx, (uptr)ctx, sizeof(*ctx)); + thr->signal_ctx = ctx; + } + return ctx; } struct ConsumeThreadContext { Index: compiler-rt/test/tsan/fiber_cleanup.cpp =================================================================== --- /dev/null +++ compiler-rt/test/tsan/fiber_cleanup.cpp @@ -0,0 +1,71 @@ +// RUN: %clang_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s +// REQUIRES: linux +#include "test.h" + +#include +#include +#include + +long count_memory_mappings() { + pid_t my_pid = getpid(); + char proc_file_name[128]; + snprintf(proc_file_name, sizeof(proc_file_name), "/proc/%ld/maps", my_pid); + + FILE *proc_file = fopen(proc_file_name, "r"); + long line_count = 0; + char c; + do { + c = fgetc(proc_file); + if (c == '\n') { + line_count++; + } + } while (c != EOF); + fclose(proc_file); + + return line_count; +} + +void fiber_iteration() { + void *orig_fiber = __tsan_get_current_fiber(); + void *fiber = __tsan_create_fiber(0); + + pthread_mutex_t mutex; + pthread_mutex_init(&mutex, NULL); + + // Running some code on the fiber that triggers handling of pending signals. + __tsan_switch_to_fiber(fiber, 0); + pthread_mutex_lock(&mutex); + pthread_mutex_unlock(&mutex); + __tsan_switch_to_fiber(orig_fiber, 0); + + // We expect the fiber to clean up all resources (here the sigcontext) when destroyed. + __tsan_destroy_fiber(fiber); +} + +// Magic-Number for some warmup iterations, +// as tsan maps some memory for the first runs. +const size_t num_warmup = 100; + +int main() { + for (size_t i = 0; i < num_warmup; i++) { + fiber_iteration(); + } + + long memory_mappings_before = count_memory_mappings(); + fiber_iteration(); + fiber_iteration(); + long memory_mappings_after = count_memory_mappings(); + + // Is there a better way to detect a resource leak in the + // ThreadState object? (i.e. a mmap not being freed) + if (memory_mappings_before == memory_mappings_after) { + fprintf(stderr, "PASS\n"); + } else { + fprintf(stderr, "FAILED\n"); + } + + return 0; +} + +// CHECK-NOT: WARNING: ThreadSanitizer: +// CHECK: PASS \ No newline at end of file