| //===-- sanitizer_thread_registry.cpp -------------------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // This file is shared between sanitizer tools. |
| // |
| // General thread bookkeeping functionality. |
| //===----------------------------------------------------------------------===// |
| |
| #include "sanitizer_thread_registry.h" |
| |
| #include "sanitizer_placement_new.h" |
| |
| namespace __sanitizer { |
| |
| ThreadContextBase::ThreadContextBase(u32 tid) |
| : tid(tid), unique_id(0), reuse_count(), os_id(0), user_id(0), |
| status(ThreadStatusInvalid), detached(false), |
| thread_type(ThreadType::Regular), parent_tid(0), next(0) { |
| name[0] = '\0'; |
| atomic_store(&thread_destroyed, 0, memory_order_release); |
| } |
| |
| ThreadContextBase::~ThreadContextBase() { |
| // ThreadContextBase should never be deleted. |
| CHECK(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::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); |
| CHECK_EQ(ThreadStatusFinished, status); |
| status = ThreadStatusDead; |
| user_id = 0; |
| OnJoined(arg); |
| } |
| |
| void ThreadContextBase::SetFinished() { |
| // ThreadRegistry::FinishThread calls here in ThreadStatusCreated state |
| // for a thread that never actually started. In that case the thread |
| // should go to ThreadStatusFinished regardless of whether it was created |
| // as detached. |
| if (!detached || status == ThreadStatusCreated) status = ThreadStatusFinished; |
| OnFinished(); |
| } |
| |
| void ThreadContextBase::SetStarted(tid_t _os_id, ThreadType _thread_type, |
| void *arg) { |
| status = ThreadStatusRunning; |
| os_id = _os_id; |
| thread_type = _thread_type; |
| 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 != kMainTid) |
| parent_tid = _parent_tid; |
| OnCreated(arg); |
| } |
| |
| void ThreadContextBase::Reset() { |
| status = ThreadStatusInvalid; |
| SetName(0); |
| atomic_store(&thread_destroyed, 0, memory_order_release); |
| OnReset(); |
| } |
| |
| // ThreadRegistry implementation. |
| |
| ThreadRegistry::ThreadRegistry(ThreadContextFactory factory) |
| : ThreadRegistry(factory, UINT32_MAX, UINT32_MAX, 0) {} |
| |
| ThreadRegistry::ThreadRegistry(ThreadContextFactory factory, u32 max_threads, |
| u32 thread_quarantine_size, u32 max_reuse) |
| : context_factory_(factory), |
| max_threads_(max_threads), |
| thread_quarantine_size_(thread_quarantine_size), |
| max_reuse_(max_reuse), |
| mtx_(), |
| total_threads_(0), |
| alive_threads_(0), |
| max_alive_threads_(0), |
| running_threads_(0) { |
| dead_threads_.clear(); |
| invalid_threads_.clear(); |
| } |
| |
| void ThreadRegistry::GetNumberOfThreads(uptr *total, uptr *running, |
| uptr *alive) { |
| ThreadRegistryLock l(this); |
| if (total) |
| *total = threads_.size(); |
| if (running) *running = running_threads_; |
| if (alive) *alive = alive_threads_; |
| } |
| |
| uptr ThreadRegistry::GetMaxAliveThreads() { |
| ThreadRegistryLock l(this); |
| return max_alive_threads_; |
| } |
| |
| u32 ThreadRegistry::CreateThread(uptr user_id, bool detached, u32 parent_tid, |
| void *arg) { |
| ThreadRegistryLock l(this); |
| u32 tid = kInvalidTid; |
| ThreadContextBase *tctx = QuarantinePop(); |
| if (tctx) { |
| tid = tctx->tid; |
| } else if (threads_.size() < max_threads_) { |
| // Allocate new thread context and tid. |
| tid = threads_.size(); |
| tctx = context_factory_(tid); |
| threads_.push_back(tctx); |
| } else { |
| #if !SANITIZER_GO |
| Report("%s: Thread limit (%u threads) exceeded. Dying.\n", |
| SanitizerToolName, max_threads_); |
| #else |
| Printf("race: limit on %u simultaneously alive goroutines is exceeded," |
| " dying\n", max_threads_); |
| #endif |
| Die(); |
| } |
| CHECK_NE(tctx, 0); |
| CHECK_NE(tid, kInvalidTid); |
| 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_); |
| } |
| if (user_id) { |
| // Ensure that user_id is unique. If it's not the case we are screwed. |
| // Ignoring this situation may lead to very hard to debug false |
| // positives later (e.g. if we join a wrong thread). |
| CHECK(live_.try_emplace(user_id, tid).second); |
| } |
| 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 < threads_.size(); tid++) { |
| ThreadContextBase *tctx = threads_[tid]; |
| if (tctx == 0) |
| continue; |
| cb(tctx, arg); |
| } |
| } |
| |
| u32 ThreadRegistry::FindThread(FindThreadCallback cb, void *arg) { |
| ThreadRegistryLock l(this); |
| for (u32 tid = 0; tid < threads_.size(); tid++) { |
| ThreadContextBase *tctx = threads_[tid]; |
| if (tctx != 0 && cb(tctx, arg)) |
| return tctx->tid; |
| } |
| return kInvalidTid; |
| } |
| |
| ThreadContextBase * |
| ThreadRegistry::FindThreadContextLocked(FindThreadCallback cb, void *arg) { |
| CheckLocked(); |
| for (u32 tid = 0; tid < threads_.size(); tid++) { |
| ThreadContextBase *tctx = threads_[tid]; |
| if (tctx != 0 && cb(tctx, arg)) |
| return tctx; |
| } |
| return 0; |
| } |
| |
| static bool FindThreadContextByOsIdCallback(ThreadContextBase *tctx, |
| void *arg) { |
| return (tctx->os_id == (uptr)arg && tctx->status != ThreadStatusInvalid && |
| tctx->status != ThreadStatusDead); |
| } |
| |
| ThreadContextBase *ThreadRegistry::FindThreadContextByOsIDLocked(tid_t os_id) { |
| return FindThreadContextLocked(FindThreadContextByOsIdCallback, |
| (void *)os_id); |
| } |
| |
| void ThreadRegistry::SetThreadName(u32 tid, const char *name) { |
| ThreadRegistryLock l(this); |
| ThreadContextBase *tctx = threads_[tid]; |
| CHECK_NE(tctx, 0); |
| CHECK_EQ(SANITIZER_FUCHSIA ? ThreadStatusCreated : ThreadStatusRunning, |
| tctx->status); |
| tctx->SetName(name); |
| } |
| |
| void ThreadRegistry::SetThreadNameByUserId(uptr user_id, const char *name) { |
| ThreadRegistryLock l(this); |
| if (const auto *tid = live_.find(user_id)) |
| threads_[tid->second]->SetName(name); |
| } |
| |
| void ThreadRegistry::DetachThread(u32 tid, void *arg) { |
| ThreadRegistryLock l(this); |
| ThreadContextBase *tctx = threads_[tid]; |
| CHECK_NE(tctx, 0); |
| if (tctx->status == ThreadStatusInvalid) { |
| Report("%s: Detach of non-existent thread\n", SanitizerToolName); |
| return; |
| } |
| tctx->OnDetached(arg); |
| if (tctx->status == ThreadStatusFinished) { |
| if (tctx->user_id) |
| live_.erase(tctx->user_id); |
| tctx->SetDead(); |
| QuarantinePush(tctx); |
| } else { |
| tctx->detached = true; |
| } |
| } |
| |
| void ThreadRegistry::JoinThread(u32 tid, void *arg) { |
| bool destroyed = false; |
| do { |
| { |
| ThreadRegistryLock l(this); |
| 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())) { |
| if (tctx->user_id) |
| live_.erase(tctx->user_id); |
| tctx->SetJoined(arg); |
| QuarantinePush(tctx); |
| } |
| } |
| if (!destroyed) |
| internal_sched_yield(); |
| } while (!destroyed); |
| } |
| |
| // Normally this is called when the thread is about to exit. If |
| // called in ThreadStatusCreated state, then this thread was never |
| // really started. We just did CreateThread for a prospective new |
| // thread before trying to create it, and then failed to actually |
| // create it, and so never called StartThread. |
| ThreadStatus ThreadRegistry::FinishThread(u32 tid) { |
| ThreadRegistryLock l(this); |
| CHECK_GT(alive_threads_, 0); |
| alive_threads_--; |
| ThreadContextBase *tctx = threads_[tid]; |
| CHECK_NE(tctx, 0); |
| bool dead = tctx->detached; |
| ThreadStatus prev_status = tctx->status; |
| if (tctx->status == ThreadStatusRunning) { |
| CHECK_GT(running_threads_, 0); |
| running_threads_--; |
| } else { |
| // The thread never really existed. |
| CHECK_EQ(tctx->status, ThreadStatusCreated); |
| dead = true; |
| } |
| tctx->SetFinished(); |
| if (dead) { |
| if (tctx->user_id) |
| live_.erase(tctx->user_id); |
| tctx->SetDead(); |
| QuarantinePush(tctx); |
| } |
| tctx->SetDestroyed(); |
| return prev_status; |
| } |
| |
| void ThreadRegistry::StartThread(u32 tid, tid_t os_id, ThreadType thread_type, |
| void *arg) { |
| ThreadRegistryLock l(this); |
| running_threads_++; |
| ThreadContextBase *tctx = threads_[tid]; |
| CHECK_NE(tctx, 0); |
| CHECK_EQ(ThreadStatusCreated, tctx->status); |
| tctx->SetStarted(os_id, thread_type, arg); |
| } |
| |
| void ThreadRegistry::QuarantinePush(ThreadContextBase *tctx) { |
| if (tctx->tid == 0) |
| return; // Don't reuse the main thread. It's a special snowflake. |
| dead_threads_.push_back(tctx); |
| if (dead_threads_.size() <= thread_quarantine_size_) |
| return; |
| tctx = dead_threads_.front(); |
| dead_threads_.pop_front(); |
| CHECK_EQ(tctx->status, ThreadStatusDead); |
| tctx->Reset(); |
| tctx->reuse_count++; |
| if (max_reuse_ > 0 && tctx->reuse_count >= max_reuse_) |
| return; |
| invalid_threads_.push_back(tctx); |
| } |
| |
| ThreadContextBase *ThreadRegistry::QuarantinePop() { |
| if (invalid_threads_.size() == 0) |
| return 0; |
| ThreadContextBase *tctx = invalid_threads_.front(); |
| invalid_threads_.pop_front(); |
| return tctx; |
| } |
| |
| u32 ThreadRegistry::ConsumeThreadUserId(uptr user_id) { |
| ThreadRegistryLock l(this); |
| u32 tid; |
| auto *t = live_.find(user_id); |
| CHECK(t); |
| tid = t->second; |
| live_.erase(t); |
| auto *tctx = threads_[tid]; |
| CHECK_EQ(tctx->user_id, user_id); |
| tctx->user_id = 0; |
| return tid; |
| } |
| |
| void ThreadRegistry::SetThreadUserId(u32 tid, uptr user_id) { |
| ThreadRegistryLock l(this); |
| ThreadContextBase *tctx = threads_[tid]; |
| CHECK_NE(tctx, 0); |
| CHECK_NE(tctx->status, ThreadStatusInvalid); |
| CHECK_NE(tctx->status, ThreadStatusDead); |
| CHECK_EQ(tctx->user_id, 0); |
| tctx->user_id = user_id; |
| CHECK(live_.try_emplace(user_id, tctx->tid).second); |
| } |
| |
| } // namespace __sanitizer |