Skip to content

Commit

Permalink
Prevent Thread Exited/Joined events race
Browse files Browse the repository at this point in the history
Summary:
Add atomic verification to ensure that Thread is Joined after marking it
Finished.

It is required for NetBSD in order to prevent Thread Exited/Joined race,
that may occur when native system libpthread(3) cannot be reliably traced
in a way to guarantee that the mentioned events happen one after another.

This change fixes at least TSan and LSan on NetBSD.

Sponsored by <The NetBSD Foundation>

Reviewers: joerg, dvyukov, vitalybuka

Reviewed By: dvyukov

Subscribers: llvm-commits, kubamracek, #sanitizers

Tags: #sanitizers

Differential Revision: https://reviews.llvm.org/D40294

llvm-svn: 319004
  • Loading branch information
krytarowski committed Nov 26, 2017
1 parent 61145d5 commit 7160c2f
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 10 deletions.
40 changes: 30 additions & 10 deletions compiler-rt/lib/sanitizer_common/sanitizer_thread_registry.cc
Expand Up @@ -21,6 +21,7 @@ ThreadContextBase::ThreadContextBase(u32 tid)
status(ThreadStatusInvalid),
detached(false), workerthread(false), parent_tid(0), next(0) {
name[0] = '\0';
atomic_store(&thread_destroyed, 0, memory_order_release);
}

ThreadContextBase::~ThreadContextBase() {
Expand All @@ -44,6 +45,14 @@ void ThreadContextBase::SetDead() {
OnDead();
}

void ThreadContextBase::SetDestroyed() {
atomic_store(&thread_destroyed, 1, memory_order_release);
}

bool ThreadContextBase::GetDestroyed() {
return !!atomic_load(&thread_destroyed, memory_order_acquire);
}

void ThreadContextBase::SetJoined(void *arg) {
// FIXME(dvyukov): print message and continue (it's user error).
CHECK_EQ(false, detached);
Expand Down Expand Up @@ -85,6 +94,7 @@ void ThreadContextBase::SetCreated(uptr _user_id, u64 _unique_id,
void ThreadContextBase::Reset() {
status = ThreadStatusInvalid;
SetName(0);
atomic_store(&thread_destroyed, 0, memory_order_release);
OnReset();
}

Expand Down Expand Up @@ -243,16 +253,25 @@ void ThreadRegistry::DetachThread(u32 tid, void *arg) {
}

void ThreadRegistry::JoinThread(u32 tid, void *arg) {
BlockingMutexLock l(&mtx_);
CHECK_LT(tid, n_contexts_);
ThreadContextBase *tctx = threads_[tid];
CHECK_NE(tctx, 0);
if (tctx->status == ThreadStatusInvalid) {
Report("%s: Join of non-existent thread\n", SanitizerToolName);
return;
}
tctx->SetJoined(arg);
QuarantinePush(tctx);
bool destroyed = false;
do {
{
BlockingMutexLock l(&mtx_);
CHECK_LT(tid, n_contexts_);
ThreadContextBase *tctx = threads_[tid];
CHECK_NE(tctx, 0);
if (tctx->status == ThreadStatusInvalid) {
Report("%s: Join of non-existent thread\n", SanitizerToolName);
return;
}
if ((destroyed = tctx->GetDestroyed())) {
tctx->SetJoined(arg);
QuarantinePush(tctx);
}
}
if (!destroyed)
internal_sched_yield();
} while (!destroyed);
}

// Normally this is called when the thread is about to exit. If
Expand Down Expand Up @@ -281,6 +300,7 @@ void ThreadRegistry::FinishThread(u32 tid) {
tctx->SetDead();
QuarantinePush(tctx);
}
tctx->SetDestroyed();
}

void ThreadRegistry::StartThread(u32 tid, tid_t os_id, bool workerthread,
Expand Down
5 changes: 5 additions & 0 deletions compiler-rt/lib/sanitizer_common/sanitizer_thread_registry.h
Expand Up @@ -50,6 +50,8 @@ class ThreadContextBase {
u32 parent_tid;
ThreadContextBase *next; // For storing thread contexts in a list.

atomic_uint32_t thread_destroyed; // To address race of Joined vs Finished

void SetName(const char *new_name);

void SetDead();
Expand All @@ -60,6 +62,9 @@ class ThreadContextBase {
u32 _parent_tid, void *arg);
void Reset();

void SetDestroyed();
bool GetDestroyed();

// The following methods may be overriden by subclasses.
// Some of them take opaque arg that may be optionally be used
// by subclasses.
Expand Down

0 comments on commit 7160c2f

Please sign in to comment.