[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