[Sanitizer] Add generic ThreadRegistry class for sanitizer runtimes. This class holds basic thread bookkeeping logic and allows specific sanitizer runtimes to create thread contexts and mark threads as created/running/joined etc. The class is based on the way we currently store thread contexts in TSan.

llvm-svn: 177074
This commit is contained in:
Alexey Samsonov 2013-03-14 13:54:30 +00:00
parent 40aacf4872
commit 1cb684381a
5 changed files with 617 additions and 0 deletions

View File

@ -18,6 +18,7 @@ set(SANITIZER_SOURCES
sanitizer_symbolizer_linux.cc
sanitizer_symbolizer_mac.cc
sanitizer_symbolizer_win.cc
sanitizer_thread_registry.cc
sanitizer_win.cc
)
@ -47,6 +48,7 @@ set(SANITIZER_HEADERS
sanitizer_stackdepot.h
sanitizer_stacktrace.h
sanitizer_symbolizer.h
sanitizer_thread_registry.h
)
set(SANITIZER_CFLAGS

View File

@ -0,0 +1,248 @@
//===-- sanitizer_thread_registry.cc --------------------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file is shared between sanitizer tools.
//
// General thread bookkeeping functionality.
//===----------------------------------------------------------------------===//
#include "sanitizer_thread_registry.h"
namespace __sanitizer {
ThreadContextBase::ThreadContextBase(u32 tid)
: tid(tid), unique_id(0), os_id(0), user_id(0), status(ThreadStatusInvalid),
detached(false), reuse_count(0), parent_tid(0), next(0) {
name[0] = '\0';
}
void ThreadContextBase::SetName(const char *new_name) {
name[0] = '\0';
if (new_name) {
internal_strncpy(name, new_name, sizeof(name));
name[sizeof(name) - 1] = '\0';
}
}
void ThreadContextBase::SetDead() {
CHECK(status == ThreadStatusRunning ||
status == ThreadStatusFinished);
status = ThreadStatusDead;
user_id = 0;
OnDead();
}
void ThreadContextBase::SetJoined(void *arg) {
// FIXME(dvyukov): print message and continue (it's user error).
CHECK_EQ(false, detached);
CHECK_EQ(ThreadStatusFinished, status);
status = ThreadStatusDead;
user_id = 0;
OnJoined(arg);
}
void ThreadContextBase::SetFinished() {
if (!detached)
status = ThreadStatusFinished;
OnFinished();
}
void ThreadContextBase::SetStarted(uptr _os_id, void *arg) {
status = ThreadStatusRunning;
os_id = _os_id;
OnStarted(arg);
}
void ThreadContextBase::SetCreated(uptr _user_id, u64 _unique_id,
bool _detached, u32 _parent_tid, void *arg) {
status = ThreadStatusCreated;
user_id = _user_id;
unique_id = _unique_id;
detached = _detached;
// Parent tid makes no sense for the main thread.
if (tid != 0)
parent_tid = _parent_tid;
OnCreated(arg);
}
void ThreadContextBase::Reset(void *arg) {
status = ThreadStatusInvalid;
reuse_count++;
SetName(0);
OnReset(arg);
}
// ThreadRegistry implementation.
ThreadRegistry::ThreadRegistry(ThreadContextFactory factory, u32 max_threads,
u32 thread_quarantine_size)
: context_factory_(factory),
max_threads_(max_threads),
thread_quarantine_size_(thread_quarantine_size),
mtx_(),
n_contexts_(0),
total_threads_(0),
alive_threads_(0),
max_alive_threads_(0),
running_threads_(0) {
threads_ = (ThreadContextBase **)MmapOrDie(max_threads_ * sizeof(threads_[0]),
"ThreadRegistry");
dead_threads_.clear();
}
void ThreadRegistry::GetNumberOfThreads(uptr *total, uptr *running,
uptr *alive) {
BlockingMutexLock l(&mtx_);
if (total) *total = n_contexts_;
if (running) *running = running_threads_;
if (alive) *alive = alive_threads_;
}
uptr ThreadRegistry::GetMaxAliveThreads() {
BlockingMutexLock l(&mtx_);
return max_alive_threads_;
}
u32 ThreadRegistry::CreateThread(uptr user_id, bool detached, u32 parent_tid,
void *arg) {
BlockingMutexLock l(&mtx_);
u32 tid = kUnknownTid;
ThreadContextBase *tctx = 0;
if (dead_threads_.size() > thread_quarantine_size_ ||
n_contexts_ >= max_threads_) {
// Reusing old thread descriptor and tid.
if (dead_threads_.size() == 0) {
Report("%s: Thread limit (%u threads) exceeded. Dying.\n",
SanitizerToolName, max_threads_);
Die();
}
tctx = dead_threads_.front();
dead_threads_.pop_front();
CHECK_EQ(ThreadStatusDead, tctx->status);
tctx->Reset(arg);
tid = tctx->tid;
} else {
// Allocate new thread context and tid.
tid = n_contexts_++;
tctx = context_factory_(tid);
threads_[tid] = tctx;
}
CHECK_NE(tctx, 0);
CHECK_NE(tid, kUnknownTid);
CHECK_LT(tid, max_threads_);
CHECK_EQ(tctx->status, ThreadStatusInvalid);
alive_threads_++;
if (max_alive_threads_ < alive_threads_) {
max_alive_threads_++;
CHECK_EQ(alive_threads_, max_alive_threads_);
}
tctx->SetCreated(user_id, total_threads_++, detached,
parent_tid, arg);
return tid;
}
void ThreadRegistry::RunCallbackForEachThreadLocked(ThreadCallback cb,
void *arg) {
CheckLocked();
for (u32 tid = 0; tid < n_contexts_; tid++) {
ThreadContextBase *tctx = threads_[tid];
if (tctx == 0)
continue;
cb(tctx, arg);
}
}
u32 ThreadRegistry::FindThread(FindThreadCallback cb, void *arg) {
BlockingMutexLock l(&mtx_);
for (u32 tid = 0; tid < n_contexts_; tid++) {
ThreadContextBase *tctx = threads_[tid];
if (tctx != 0 && cb(tctx, arg))
return tctx->tid;
}
return kUnknownTid;
}
ThreadContextBase *
ThreadRegistry::FindThreadContextLocked(FindThreadCallback cb, void *arg) {
CheckLocked();
for (u32 tid = 0; tid < n_contexts_; tid++) {
ThreadContextBase *tctx = threads_[tid];
if (tctx != 0 && cb(tctx, arg))
return tctx;
}
return 0;
}
void ThreadRegistry::SetThreadName(u32 tid, const char *name) {
BlockingMutexLock l(&mtx_);
CHECK_LT(tid, n_contexts_);
ThreadContextBase *tctx = threads_[tid];
CHECK_NE(tctx, 0);
CHECK_EQ(ThreadStatusRunning, tctx->status);
tctx->SetName(name);
}
void ThreadRegistry::DetachThread(u32 tid) {
BlockingMutexLock l(&mtx_);
CHECK_LT(tid, n_contexts_);
ThreadContextBase *tctx = threads_[tid];
CHECK_NE(tctx, 0);
if (tctx->status == ThreadStatusInvalid) {
Report("%s: Detach of non-existent thread\n", SanitizerToolName);
return;
}
if (tctx->status == ThreadStatusFinished) {
tctx->SetDead();
dead_threads_.push_back(tctx);
} else {
tctx->detached = true;
}
}
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);
dead_threads_.push_back(tctx);
}
void ThreadRegistry::FinishThread(u32 tid) {
BlockingMutexLock l(&mtx_);
CHECK_GT(alive_threads_, 0);
alive_threads_--;
CHECK_GT(running_threads_, 0);
running_threads_--;
CHECK_LT(tid, n_contexts_);
ThreadContextBase *tctx = threads_[tid];
CHECK_NE(tctx, 0);
CHECK_EQ(ThreadStatusRunning, tctx->status);
tctx->SetFinished();
if (tctx->detached) {
tctx->SetDead();
dead_threads_.push_back(tctx);
}
}
void ThreadRegistry::StartThread(u32 tid, uptr os_id, void *arg) {
BlockingMutexLock l(&mtx_);
running_threads_++;
CHECK_LT(tid, n_contexts_);
ThreadContextBase *tctx = threads_[tid];
CHECK_NE(tctx, 0);
CHECK_EQ(ThreadStatusCreated, tctx->status);
tctx->SetStarted(os_id, arg);
}
} // namespace __sanitizer

View File

@ -0,0 +1,138 @@
//===-- sanitizer_thread_registry.h -----------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file is shared between sanitizer tools.
//
// General thread bookkeeping functionality.
//===----------------------------------------------------------------------===//
#ifndef SANITIZER_THREAD_REGISTRY_H
#define SANITIZER_THREAD_REGISTRY_H
#include "sanitizer_common.h"
#include "sanitizer_list.h"
#include "sanitizer_mutex.h"
namespace __sanitizer {
enum ThreadStatus {
ThreadStatusInvalid, // Non-existent thread, data is invalid.
ThreadStatusCreated, // Created but not yet running.
ThreadStatusRunning, // The thread is currently running.
ThreadStatusFinished, // Joinable thread is finished but not yet joined.
ThreadStatusDead // Joined, but some info is still available.
};
// Generic thread context. Specific sanitizer tools may inherit from it.
// If thread is dead, context may optionally be reused for a new thread.
class ThreadContextBase {
public:
explicit ThreadContextBase(u32 tid);
const u32 tid; // Thread ID. Main thread should have tid = 0.
u64 unique_id; // Unique thread ID.
uptr os_id; // PID (used for reporting).
uptr user_id; // Some opaque user thread id (e.g. pthread_t).
char name[64]; // As annotated by user.
ThreadStatus status;
bool detached;
int reuse_count;
u32 parent_tid;
ThreadContextBase *next; // For storing thread contexts in a list.
void SetName(const char *new_name);
void SetDead();
void SetJoined(void *arg);
void SetFinished();
void SetStarted(uptr _os_id, void *arg);
void SetCreated(uptr _user_id, u64 _unique_id, bool _detached,
u32 _parent_tid, void *arg);
void Reset(void *arg);
// The following methods may be overriden by subclasses.
// Some of them take opaque arg that may be optionally be used
// by subclasses.
virtual void OnDead() {}
virtual void OnJoined(void *arg) {}
virtual void OnFinished() {}
virtual void OnStarted(void *arg) {}
virtual void OnCreated(void *arg) {}
virtual void OnReset(void *arg) {}
};
typedef ThreadContextBase* (*ThreadContextFactory)(u32 tid);
class ThreadRegistry {
private:
const ThreadContextFactory context_factory_;
const u32 max_threads_;
const u32 thread_quarantine_size_;
static const u32 kUnknownTid = -1U;
BlockingMutex mtx_;
u32 n_contexts_; // Number of created thread contexts,
// at most max_threads_.
u64 total_threads_; // Total number of created threads. May be greater than
// max_threads_ if contexts were reused.
uptr alive_threads_; // Created or running.
uptr max_alive_threads_;
uptr running_threads_;
ThreadContextBase **threads_; // Array of thread contexts is leaked.
IntrusiveList<ThreadContextBase> dead_threads_;
public:
ThreadRegistry(ThreadContextFactory factory, u32 max_threads,
u32 thread_quarantine_size);
void GetNumberOfThreads(uptr *total = 0, uptr *running = 0, uptr *alive = 0);
uptr GetMaxAliveThreads();
void Lock() { mtx_.Lock(); }
void CheckLocked() { mtx_.CheckLocked(); }
void Unlock() { mtx_.Unlock(); }
// Should be guarded by ThreadRegistryLock.
ThreadContextBase *GetThreadLocked(u32 tid) {
DCHECK_LT(tid, n_contexts_);
return threads_[tid];
}
u32 CreateThread(uptr user_id, bool detached, u32 parent_tid, void *arg);
typedef void (*ThreadCallback)(ThreadContextBase *tctx, void *arg);
// Invokes callback with a specified arg for each thread context.
// Should be guarded by ThreadRegistryLock.
void RunCallbackForEachThreadLocked(ThreadCallback cb, void *arg);
typedef bool (*FindThreadCallback)(ThreadContextBase *tctx, void *arg);
// Finds a thread using the provided callback. Returns kUnknownTid if no
// thread is found.
u32 FindThread(FindThreadCallback cb, void *arg);
// Should be guarded by ThreadRegistryLock. Returns 0 if no thread
// is found.
ThreadContextBase *FindThreadContextLocked(FindThreadCallback cb,
void *arg);
void SetThreadName(u32 tid, const char *name);
void DetachThread(u32 tid);
void JoinThread(u32 tid, void *arg);
void FinishThread(u32 tid);
void StartThread(u32 tid, uptr os_id, void *arg);
};
typedef GenericScopedLock<ThreadRegistry> ThreadRegistryLock;
} // namespace __sanitizer
#endif // SANITIZER_THREAD_REGISTRY_H

View File

@ -13,6 +13,7 @@ set(SANITIZER_UNITTESTS
sanitizer_stackdepot_test.cc
sanitizer_stacktrace_test.cc
sanitizer_test_main.cc
sanitizer_thread_registry_test.cc
)
set(SANITIZER_TEST_HEADERS)

View File

@ -0,0 +1,228 @@
//===-- sanitizer_thread_registry_test.cc ---------------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file is a part of shared sanitizer runtime.
//
//===----------------------------------------------------------------------===//
#include "sanitizer_common/sanitizer_thread_registry.h"
#include "gtest/gtest.h"
#include <vector>
namespace __sanitizer {
static BlockingMutex tctx_allocator_lock(LINKER_INITIALIZED);
static LowLevelAllocator tctx_allocator;
template<typename TCTX>
static ThreadContextBase *GetThreadContext(u32 tid) {
BlockingMutexLock l(&tctx_allocator_lock);
void *mem = tctx_allocator.Allocate(sizeof(TCTX));
return new(mem) TCTX(tid);
}
static const u32 kMaxRegistryThreads = 1000;
static const u32 kRegistryQuarantine = 2;
static void CheckThreadQuantity(ThreadRegistry *registry, uptr exp_total,
uptr exp_running, uptr exp_alive) {
uptr total, running, alive;
registry->GetNumberOfThreads(&total, &running, &alive);
EXPECT_EQ(exp_total, total);
EXPECT_EQ(exp_running, running);
EXPECT_EQ(exp_alive, alive);
}
static bool is_detached(int tid) {
return (tid % 2 == 0);
}
static uptr get_uid(int tid) {
return tid * 2;
}
static bool HasName(ThreadContextBase *tctx, void *arg) {
char *name = (char*)arg;
return (tctx->name && 0 == internal_strcmp(tctx->name, name));
}
static bool HasUid(ThreadContextBase *tctx, void *arg) {
uptr uid = (uptr)arg;
return (tctx->user_id == uid);
}
static void MarkUidAsPresent(ThreadContextBase *tctx, void *arg) {
bool *arr = (bool*)arg;
arr[tctx->tid] = true;
}
static void TestRegistry(ThreadRegistry *registry, bool has_quarantine) {
// Create and start a main thread.
EXPECT_EQ(0, registry->CreateThread(get_uid(0), true, -1, 0));
registry->StartThread(0, 0, 0);
// Create a bunch of threads.
for (int i = 1; i <= 10; i++) {
EXPECT_EQ(i, registry->CreateThread(get_uid(i), is_detached(i), 0, 0));
}
CheckThreadQuantity(registry, 11, 1, 11);
// Start some of them.
for (int i = 1; i <= 5; i++) {
registry->StartThread(i, 0, 0);
}
CheckThreadQuantity(registry, 11, 6, 11);
// Finish, create and start more threads.
for (int i = 1; i <= 5; i++) {
registry->FinishThread(i);
if (!is_detached(i))
registry->JoinThread(i, 0);
}
for (int i = 6; i <= 10; i++) {
registry->StartThread(i, 0, 0);
}
std::vector<int> new_tids;
for (int i = 11; i <= 15; i++) {
new_tids.push_back(
registry->CreateThread(get_uid(i), is_detached(i), 0, 0));
}
ASSERT_LE(kRegistryQuarantine, 5);
int exp_total = 16 - (has_quarantine ? 5 - kRegistryQuarantine : 0);
CheckThreadQuantity(registry, exp_total, 6, 11);
// Test SetThreadName and FindThread.
registry->SetThreadName(6, "six");
registry->SetThreadName(7, "seven");
EXPECT_EQ(7, registry->FindThread(HasName, (void*)"seven"));
EXPECT_EQ(-1, registry->FindThread(HasName, (void*)"none"));
EXPECT_EQ(0, registry->FindThread(HasUid, (void*)get_uid(0)));
EXPECT_EQ(10, registry->FindThread(HasUid, (void*)get_uid(10)));
EXPECT_EQ(-1, registry->FindThread(HasUid, (void*)0x1234));
// Detach and finish and join remaining threads.
for (int i = 6; i <= 10; i++) {
registry->DetachThread(i);
registry->FinishThread(i);
}
for (int i = 0; i < new_tids.size(); i++) {
int tid = new_tids[i];
registry->StartThread(tid, 0, 0);
registry->DetachThread(tid);
registry->FinishThread(tid);
}
CheckThreadQuantity(registry, exp_total, 1, 1);
// Test methods that require the caller to hold a ThreadRegistryLock.
bool has_tid[16];
internal_memset(&has_tid[0], 0, sizeof(has_tid));
{
ThreadRegistryLock l(registry);
registry->RunCallbackForEachThreadLocked(MarkUidAsPresent, &has_tid[0]);
}
for (int i = 0; i < exp_total; i++) {
EXPECT_TRUE(has_tid[i]);
}
{
ThreadRegistryLock l(registry);
registry->CheckLocked();
ThreadContextBase *main_thread = registry->GetThreadLocked(0);
EXPECT_EQ(main_thread, registry->FindThreadContextLocked(
HasUid, (void*)get_uid(0)));
}
EXPECT_EQ(11, registry->GetMaxAliveThreads());
}
TEST(SanitizerCommon, ThreadRegistryTest) {
ThreadRegistry quarantine_registry(GetThreadContext<ThreadContextBase>,
kMaxRegistryThreads,
kRegistryQuarantine);
TestRegistry(&quarantine_registry, true);
ThreadRegistry no_quarantine_registry(GetThreadContext<ThreadContextBase>,
kMaxRegistryThreads,
kMaxRegistryThreads);
TestRegistry(&no_quarantine_registry, false);
}
static const int kThreadsPerShard = 20;
static const int kNumShards = 25;
static int num_created[kNumShards + 1];
static int num_started[kNumShards + 1];
static int num_joined[kNumShards + 1];
namespace {
struct RunThreadArgs {
ThreadRegistry *registry;
uptr shard; // started from 1.
};
class TestThreadContext : public ThreadContextBase {
public:
explicit TestThreadContext(int tid) : ThreadContextBase(tid) {}
void OnJoined(void *arg) {
uptr shard = (uptr)arg;
num_joined[shard]++;
}
void OnStarted(void *arg) {
uptr shard = (uptr)arg;
num_started[shard]++;
}
void OnCreated(void *arg) {
uptr shard = (uptr)arg;
num_created[shard]++;
}
};
} // namespace
void *RunThread(void *arg) {
RunThreadArgs *args = static_cast<RunThreadArgs*>(arg);
std::vector<int> tids;
for (int i = 0; i < kThreadsPerShard; i++)
tids.push_back(
args->registry->CreateThread(0, false, 0, (void*)args->shard));
for (int i = 0; i < kThreadsPerShard; i++)
args->registry->StartThread(tids[i], 0, (void*)args->shard);
for (int i = 0; i < kThreadsPerShard; i++)
args->registry->FinishThread(tids[i]);
for (int i = 0; i < kThreadsPerShard; i++)
args->registry->JoinThread(tids[i], (void*)args->shard);
return 0;
}
static void ThreadedTestRegistry(ThreadRegistry *registry) {
// Create and start a main thread.
EXPECT_EQ(0, registry->CreateThread(0, true, -1, 0));
registry->StartThread(0, 0, 0);
pthread_t threads[kNumShards];
RunThreadArgs args[kNumShards];
for (int i = 0; i < kNumShards; i++) {
args[i].registry = registry;
args[i].shard = i + 1;
pthread_create(&threads[i], 0, RunThread, &args[i]);
}
for (int i = 0; i < kNumShards; i++) {
pthread_join(threads[i], 0);
}
// Check that each thread created/started/joined correct amount
// of "threads" in thread_registry.
EXPECT_EQ(1, num_created[0]);
EXPECT_EQ(1, num_started[0]);
EXPECT_EQ(0, num_joined[0]);
for (int i = 1; i <= kNumShards; i++) {
EXPECT_EQ(kThreadsPerShard, num_created[i]);
EXPECT_EQ(kThreadsPerShard, num_started[i]);
EXPECT_EQ(kThreadsPerShard, num_joined[i]);
}
}
TEST(SanitizerCommon, ThreadRegistryThreadedTest) {
ThreadRegistry registry(GetThreadContext<TestThreadContext>,
kThreadsPerShard * kNumShards + 1, 10);
ThreadedTestRegistry(&registry);
}
} // namespace __sanitizer