[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:
parent
40aacf4872
commit
1cb684381a
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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(®istry);
|
||||
}
|
||||
|
||||
} // namespace __sanitizer
|
||||
Loading…
Reference in New Issue