13cab2bb3Spatrick //===-- sanitizer_thread_registry.cpp -------------------------------------===//
23cab2bb3Spatrick //
33cab2bb3Spatrick // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
43cab2bb3Spatrick // See https://llvm.org/LICENSE.txt for license information.
53cab2bb3Spatrick // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
63cab2bb3Spatrick //
73cab2bb3Spatrick //===----------------------------------------------------------------------===//
83cab2bb3Spatrick //
93cab2bb3Spatrick // This file is shared between sanitizer tools.
103cab2bb3Spatrick //
113cab2bb3Spatrick // General thread bookkeeping functionality.
123cab2bb3Spatrick //===----------------------------------------------------------------------===//
133cab2bb3Spatrick
143cab2bb3Spatrick #include "sanitizer_thread_registry.h"
153cab2bb3Spatrick
16*810390e3Srobert #include "sanitizer_placement_new.h"
17*810390e3Srobert
183cab2bb3Spatrick namespace __sanitizer {
193cab2bb3Spatrick
ThreadContextBase(u32 tid)203cab2bb3Spatrick ThreadContextBase::ThreadContextBase(u32 tid)
213cab2bb3Spatrick : tid(tid), unique_id(0), reuse_count(), os_id(0), user_id(0),
223cab2bb3Spatrick status(ThreadStatusInvalid), detached(false),
233cab2bb3Spatrick thread_type(ThreadType::Regular), parent_tid(0), next(0) {
243cab2bb3Spatrick name[0] = '\0';
253cab2bb3Spatrick atomic_store(&thread_destroyed, 0, memory_order_release);
263cab2bb3Spatrick }
273cab2bb3Spatrick
~ThreadContextBase()283cab2bb3Spatrick ThreadContextBase::~ThreadContextBase() {
293cab2bb3Spatrick // ThreadContextBase should never be deleted.
303cab2bb3Spatrick CHECK(0);
313cab2bb3Spatrick }
323cab2bb3Spatrick
SetName(const char * new_name)333cab2bb3Spatrick void ThreadContextBase::SetName(const char *new_name) {
343cab2bb3Spatrick name[0] = '\0';
353cab2bb3Spatrick if (new_name) {
363cab2bb3Spatrick internal_strncpy(name, new_name, sizeof(name));
373cab2bb3Spatrick name[sizeof(name) - 1] = '\0';
383cab2bb3Spatrick }
393cab2bb3Spatrick }
403cab2bb3Spatrick
SetDead()413cab2bb3Spatrick void ThreadContextBase::SetDead() {
423cab2bb3Spatrick CHECK(status == ThreadStatusRunning ||
433cab2bb3Spatrick status == ThreadStatusFinished);
443cab2bb3Spatrick status = ThreadStatusDead;
453cab2bb3Spatrick user_id = 0;
463cab2bb3Spatrick OnDead();
473cab2bb3Spatrick }
483cab2bb3Spatrick
SetDestroyed()493cab2bb3Spatrick void ThreadContextBase::SetDestroyed() {
503cab2bb3Spatrick atomic_store(&thread_destroyed, 1, memory_order_release);
513cab2bb3Spatrick }
523cab2bb3Spatrick
GetDestroyed()533cab2bb3Spatrick bool ThreadContextBase::GetDestroyed() {
543cab2bb3Spatrick return !!atomic_load(&thread_destroyed, memory_order_acquire);
553cab2bb3Spatrick }
563cab2bb3Spatrick
SetJoined(void * arg)573cab2bb3Spatrick void ThreadContextBase::SetJoined(void *arg) {
583cab2bb3Spatrick // FIXME(dvyukov): print message and continue (it's user error).
593cab2bb3Spatrick CHECK_EQ(false, detached);
603cab2bb3Spatrick CHECK_EQ(ThreadStatusFinished, status);
613cab2bb3Spatrick status = ThreadStatusDead;
623cab2bb3Spatrick user_id = 0;
633cab2bb3Spatrick OnJoined(arg);
643cab2bb3Spatrick }
653cab2bb3Spatrick
SetFinished()663cab2bb3Spatrick void ThreadContextBase::SetFinished() {
673cab2bb3Spatrick // ThreadRegistry::FinishThread calls here in ThreadStatusCreated state
683cab2bb3Spatrick // for a thread that never actually started. In that case the thread
693cab2bb3Spatrick // should go to ThreadStatusFinished regardless of whether it was created
703cab2bb3Spatrick // as detached.
713cab2bb3Spatrick if (!detached || status == ThreadStatusCreated) status = ThreadStatusFinished;
723cab2bb3Spatrick OnFinished();
733cab2bb3Spatrick }
743cab2bb3Spatrick
SetStarted(tid_t _os_id,ThreadType _thread_type,void * arg)753cab2bb3Spatrick void ThreadContextBase::SetStarted(tid_t _os_id, ThreadType _thread_type,
763cab2bb3Spatrick void *arg) {
773cab2bb3Spatrick status = ThreadStatusRunning;
783cab2bb3Spatrick os_id = _os_id;
793cab2bb3Spatrick thread_type = _thread_type;
803cab2bb3Spatrick OnStarted(arg);
813cab2bb3Spatrick }
823cab2bb3Spatrick
SetCreated(uptr _user_id,u64 _unique_id,bool _detached,u32 _parent_tid,void * arg)833cab2bb3Spatrick void ThreadContextBase::SetCreated(uptr _user_id, u64 _unique_id,
843cab2bb3Spatrick bool _detached, u32 _parent_tid, void *arg) {
853cab2bb3Spatrick status = ThreadStatusCreated;
863cab2bb3Spatrick user_id = _user_id;
873cab2bb3Spatrick unique_id = _unique_id;
883cab2bb3Spatrick detached = _detached;
893cab2bb3Spatrick // Parent tid makes no sense for the main thread.
90d89ec533Spatrick if (tid != kMainTid)
913cab2bb3Spatrick parent_tid = _parent_tid;
923cab2bb3Spatrick OnCreated(arg);
933cab2bb3Spatrick }
943cab2bb3Spatrick
Reset()953cab2bb3Spatrick void ThreadContextBase::Reset() {
963cab2bb3Spatrick status = ThreadStatusInvalid;
973cab2bb3Spatrick SetName(0);
983cab2bb3Spatrick atomic_store(&thread_destroyed, 0, memory_order_release);
993cab2bb3Spatrick OnReset();
1003cab2bb3Spatrick }
1013cab2bb3Spatrick
1023cab2bb3Spatrick // ThreadRegistry implementation.
1033cab2bb3Spatrick
ThreadRegistry(ThreadContextFactory factory)104d89ec533Spatrick ThreadRegistry::ThreadRegistry(ThreadContextFactory factory)
105d89ec533Spatrick : ThreadRegistry(factory, UINT32_MAX, UINT32_MAX, 0) {}
1063cab2bb3Spatrick
ThreadRegistry(ThreadContextFactory factory,u32 max_threads,u32 thread_quarantine_size,u32 max_reuse)1073cab2bb3Spatrick ThreadRegistry::ThreadRegistry(ThreadContextFactory factory, u32 max_threads,
1083cab2bb3Spatrick u32 thread_quarantine_size, u32 max_reuse)
1093cab2bb3Spatrick : context_factory_(factory),
1103cab2bb3Spatrick max_threads_(max_threads),
1113cab2bb3Spatrick thread_quarantine_size_(thread_quarantine_size),
1123cab2bb3Spatrick max_reuse_(max_reuse),
113*810390e3Srobert mtx_(MutexThreadRegistry),
1143cab2bb3Spatrick total_threads_(0),
1153cab2bb3Spatrick alive_threads_(0),
1163cab2bb3Spatrick max_alive_threads_(0),
1173cab2bb3Spatrick running_threads_(0) {
1183cab2bb3Spatrick dead_threads_.clear();
1193cab2bb3Spatrick invalid_threads_.clear();
1203cab2bb3Spatrick }
1213cab2bb3Spatrick
GetNumberOfThreads(uptr * total,uptr * running,uptr * alive)1223cab2bb3Spatrick void ThreadRegistry::GetNumberOfThreads(uptr *total, uptr *running,
1233cab2bb3Spatrick uptr *alive) {
124*810390e3Srobert ThreadRegistryLock l(this);
125d89ec533Spatrick if (total)
126d89ec533Spatrick *total = threads_.size();
1273cab2bb3Spatrick if (running) *running = running_threads_;
1283cab2bb3Spatrick if (alive) *alive = alive_threads_;
1293cab2bb3Spatrick }
1303cab2bb3Spatrick
GetMaxAliveThreads()1313cab2bb3Spatrick uptr ThreadRegistry::GetMaxAliveThreads() {
132*810390e3Srobert ThreadRegistryLock l(this);
1333cab2bb3Spatrick return max_alive_threads_;
1343cab2bb3Spatrick }
1353cab2bb3Spatrick
CreateThread(uptr user_id,bool detached,u32 parent_tid,void * arg)1363cab2bb3Spatrick u32 ThreadRegistry::CreateThread(uptr user_id, bool detached, u32 parent_tid,
1373cab2bb3Spatrick void *arg) {
138*810390e3Srobert ThreadRegistryLock l(this);
139d89ec533Spatrick u32 tid = kInvalidTid;
1403cab2bb3Spatrick ThreadContextBase *tctx = QuarantinePop();
1413cab2bb3Spatrick if (tctx) {
1423cab2bb3Spatrick tid = tctx->tid;
143d89ec533Spatrick } else if (threads_.size() < max_threads_) {
1443cab2bb3Spatrick // Allocate new thread context and tid.
145d89ec533Spatrick tid = threads_.size();
1463cab2bb3Spatrick tctx = context_factory_(tid);
147d89ec533Spatrick threads_.push_back(tctx);
1483cab2bb3Spatrick } else {
1493cab2bb3Spatrick #if !SANITIZER_GO
1503cab2bb3Spatrick Report("%s: Thread limit (%u threads) exceeded. Dying.\n",
1513cab2bb3Spatrick SanitizerToolName, max_threads_);
1523cab2bb3Spatrick #else
1533cab2bb3Spatrick Printf("race: limit on %u simultaneously alive goroutines is exceeded,"
1543cab2bb3Spatrick " dying\n", max_threads_);
1553cab2bb3Spatrick #endif
1563cab2bb3Spatrick Die();
1573cab2bb3Spatrick }
1583cab2bb3Spatrick CHECK_NE(tctx, 0);
159d89ec533Spatrick CHECK_NE(tid, kInvalidTid);
1603cab2bb3Spatrick CHECK_LT(tid, max_threads_);
1613cab2bb3Spatrick CHECK_EQ(tctx->status, ThreadStatusInvalid);
1623cab2bb3Spatrick alive_threads_++;
1633cab2bb3Spatrick if (max_alive_threads_ < alive_threads_) {
1643cab2bb3Spatrick max_alive_threads_++;
1653cab2bb3Spatrick CHECK_EQ(alive_threads_, max_alive_threads_);
1663cab2bb3Spatrick }
167*810390e3Srobert if (user_id) {
168*810390e3Srobert // Ensure that user_id is unique. If it's not the case we are screwed.
169*810390e3Srobert // Ignoring this situation may lead to very hard to debug false
170*810390e3Srobert // positives later (e.g. if we join a wrong thread).
171*810390e3Srobert CHECK(live_.try_emplace(user_id, tid).second);
172*810390e3Srobert }
1733cab2bb3Spatrick tctx->SetCreated(user_id, total_threads_++, detached,
1743cab2bb3Spatrick parent_tid, arg);
1753cab2bb3Spatrick return tid;
1763cab2bb3Spatrick }
1773cab2bb3Spatrick
RunCallbackForEachThreadLocked(ThreadCallback cb,void * arg)1783cab2bb3Spatrick void ThreadRegistry::RunCallbackForEachThreadLocked(ThreadCallback cb,
1793cab2bb3Spatrick void *arg) {
1803cab2bb3Spatrick CheckLocked();
181d89ec533Spatrick for (u32 tid = 0; tid < threads_.size(); tid++) {
1823cab2bb3Spatrick ThreadContextBase *tctx = threads_[tid];
1833cab2bb3Spatrick if (tctx == 0)
1843cab2bb3Spatrick continue;
1853cab2bb3Spatrick cb(tctx, arg);
1863cab2bb3Spatrick }
1873cab2bb3Spatrick }
1883cab2bb3Spatrick
FindThread(FindThreadCallback cb,void * arg)1893cab2bb3Spatrick u32 ThreadRegistry::FindThread(FindThreadCallback cb, void *arg) {
190*810390e3Srobert ThreadRegistryLock l(this);
191d89ec533Spatrick for (u32 tid = 0; tid < threads_.size(); tid++) {
1923cab2bb3Spatrick ThreadContextBase *tctx = threads_[tid];
1933cab2bb3Spatrick if (tctx != 0 && cb(tctx, arg))
1943cab2bb3Spatrick return tctx->tid;
1953cab2bb3Spatrick }
196d89ec533Spatrick return kInvalidTid;
1973cab2bb3Spatrick }
1983cab2bb3Spatrick
1993cab2bb3Spatrick ThreadContextBase *
FindThreadContextLocked(FindThreadCallback cb,void * arg)2003cab2bb3Spatrick ThreadRegistry::FindThreadContextLocked(FindThreadCallback cb, void *arg) {
2013cab2bb3Spatrick CheckLocked();
202d89ec533Spatrick for (u32 tid = 0; tid < threads_.size(); tid++) {
2033cab2bb3Spatrick ThreadContextBase *tctx = threads_[tid];
2043cab2bb3Spatrick if (tctx != 0 && cb(tctx, arg))
2053cab2bb3Spatrick return tctx;
2063cab2bb3Spatrick }
2073cab2bb3Spatrick return 0;
2083cab2bb3Spatrick }
2093cab2bb3Spatrick
FindThreadContextByOsIdCallback(ThreadContextBase * tctx,void * arg)2103cab2bb3Spatrick static bool FindThreadContextByOsIdCallback(ThreadContextBase *tctx,
2113cab2bb3Spatrick void *arg) {
2123cab2bb3Spatrick return (tctx->os_id == (uptr)arg && tctx->status != ThreadStatusInvalid &&
2133cab2bb3Spatrick tctx->status != ThreadStatusDead);
2143cab2bb3Spatrick }
2153cab2bb3Spatrick
FindThreadContextByOsIDLocked(tid_t os_id)2163cab2bb3Spatrick ThreadContextBase *ThreadRegistry::FindThreadContextByOsIDLocked(tid_t os_id) {
2173cab2bb3Spatrick return FindThreadContextLocked(FindThreadContextByOsIdCallback,
2183cab2bb3Spatrick (void *)os_id);
2193cab2bb3Spatrick }
2203cab2bb3Spatrick
SetThreadName(u32 tid,const char * name)2213cab2bb3Spatrick void ThreadRegistry::SetThreadName(u32 tid, const char *name) {
222*810390e3Srobert ThreadRegistryLock l(this);
2233cab2bb3Spatrick ThreadContextBase *tctx = threads_[tid];
2243cab2bb3Spatrick CHECK_NE(tctx, 0);
2253cab2bb3Spatrick CHECK_EQ(SANITIZER_FUCHSIA ? ThreadStatusCreated : ThreadStatusRunning,
2263cab2bb3Spatrick tctx->status);
2273cab2bb3Spatrick tctx->SetName(name);
2283cab2bb3Spatrick }
2293cab2bb3Spatrick
SetThreadNameByUserId(uptr user_id,const char * name)2303cab2bb3Spatrick void ThreadRegistry::SetThreadNameByUserId(uptr user_id, const char *name) {
231*810390e3Srobert ThreadRegistryLock l(this);
232*810390e3Srobert if (const auto *tid = live_.find(user_id))
233*810390e3Srobert threads_[tid->second]->SetName(name);
2343cab2bb3Spatrick }
2353cab2bb3Spatrick
DetachThread(u32 tid,void * arg)2363cab2bb3Spatrick void ThreadRegistry::DetachThread(u32 tid, void *arg) {
237*810390e3Srobert ThreadRegistryLock l(this);
2383cab2bb3Spatrick ThreadContextBase *tctx = threads_[tid];
2393cab2bb3Spatrick CHECK_NE(tctx, 0);
2403cab2bb3Spatrick if (tctx->status == ThreadStatusInvalid) {
2413cab2bb3Spatrick Report("%s: Detach of non-existent thread\n", SanitizerToolName);
2423cab2bb3Spatrick return;
2433cab2bb3Spatrick }
2443cab2bb3Spatrick tctx->OnDetached(arg);
2453cab2bb3Spatrick if (tctx->status == ThreadStatusFinished) {
246*810390e3Srobert if (tctx->user_id)
247*810390e3Srobert live_.erase(tctx->user_id);
2483cab2bb3Spatrick tctx->SetDead();
2493cab2bb3Spatrick QuarantinePush(tctx);
2503cab2bb3Spatrick } else {
2513cab2bb3Spatrick tctx->detached = true;
2523cab2bb3Spatrick }
2533cab2bb3Spatrick }
2543cab2bb3Spatrick
JoinThread(u32 tid,void * arg)2553cab2bb3Spatrick void ThreadRegistry::JoinThread(u32 tid, void *arg) {
2563cab2bb3Spatrick bool destroyed = false;
2573cab2bb3Spatrick do {
2583cab2bb3Spatrick {
259*810390e3Srobert ThreadRegistryLock l(this);
2603cab2bb3Spatrick ThreadContextBase *tctx = threads_[tid];
2613cab2bb3Spatrick CHECK_NE(tctx, 0);
2623cab2bb3Spatrick if (tctx->status == ThreadStatusInvalid) {
2633cab2bb3Spatrick Report("%s: Join of non-existent thread\n", SanitizerToolName);
2643cab2bb3Spatrick return;
2653cab2bb3Spatrick }
2663cab2bb3Spatrick if ((destroyed = tctx->GetDestroyed())) {
267*810390e3Srobert if (tctx->user_id)
268*810390e3Srobert live_.erase(tctx->user_id);
2693cab2bb3Spatrick tctx->SetJoined(arg);
2703cab2bb3Spatrick QuarantinePush(tctx);
2713cab2bb3Spatrick }
2723cab2bb3Spatrick }
2733cab2bb3Spatrick if (!destroyed)
2743cab2bb3Spatrick internal_sched_yield();
2753cab2bb3Spatrick } while (!destroyed);
2763cab2bb3Spatrick }
2773cab2bb3Spatrick
2783cab2bb3Spatrick // Normally this is called when the thread is about to exit. If
2793cab2bb3Spatrick // called in ThreadStatusCreated state, then this thread was never
2803cab2bb3Spatrick // really started. We just did CreateThread for a prospective new
2813cab2bb3Spatrick // thread before trying to create it, and then failed to actually
2823cab2bb3Spatrick // create it, and so never called StartThread.
FinishThread(u32 tid)283d89ec533Spatrick ThreadStatus ThreadRegistry::FinishThread(u32 tid) {
284*810390e3Srobert ThreadRegistryLock l(this);
2853cab2bb3Spatrick CHECK_GT(alive_threads_, 0);
2863cab2bb3Spatrick alive_threads_--;
2873cab2bb3Spatrick ThreadContextBase *tctx = threads_[tid];
2883cab2bb3Spatrick CHECK_NE(tctx, 0);
2893cab2bb3Spatrick bool dead = tctx->detached;
290d89ec533Spatrick ThreadStatus prev_status = tctx->status;
2913cab2bb3Spatrick if (tctx->status == ThreadStatusRunning) {
2923cab2bb3Spatrick CHECK_GT(running_threads_, 0);
2933cab2bb3Spatrick running_threads_--;
2943cab2bb3Spatrick } else {
2953cab2bb3Spatrick // The thread never really existed.
2963cab2bb3Spatrick CHECK_EQ(tctx->status, ThreadStatusCreated);
2973cab2bb3Spatrick dead = true;
2983cab2bb3Spatrick }
2993cab2bb3Spatrick tctx->SetFinished();
3003cab2bb3Spatrick if (dead) {
301*810390e3Srobert if (tctx->user_id)
302*810390e3Srobert live_.erase(tctx->user_id);
3033cab2bb3Spatrick tctx->SetDead();
3043cab2bb3Spatrick QuarantinePush(tctx);
3053cab2bb3Spatrick }
3063cab2bb3Spatrick tctx->SetDestroyed();
307d89ec533Spatrick return prev_status;
3083cab2bb3Spatrick }
3093cab2bb3Spatrick
StartThread(u32 tid,tid_t os_id,ThreadType thread_type,void * arg)3103cab2bb3Spatrick void ThreadRegistry::StartThread(u32 tid, tid_t os_id, ThreadType thread_type,
3113cab2bb3Spatrick void *arg) {
312*810390e3Srobert ThreadRegistryLock l(this);
3133cab2bb3Spatrick running_threads_++;
3143cab2bb3Spatrick ThreadContextBase *tctx = threads_[tid];
3153cab2bb3Spatrick CHECK_NE(tctx, 0);
3163cab2bb3Spatrick CHECK_EQ(ThreadStatusCreated, tctx->status);
3173cab2bb3Spatrick tctx->SetStarted(os_id, thread_type, arg);
3183cab2bb3Spatrick }
3193cab2bb3Spatrick
QuarantinePush(ThreadContextBase * tctx)3203cab2bb3Spatrick void ThreadRegistry::QuarantinePush(ThreadContextBase *tctx) {
3213cab2bb3Spatrick if (tctx->tid == 0)
3223cab2bb3Spatrick return; // Don't reuse the main thread. It's a special snowflake.
3233cab2bb3Spatrick dead_threads_.push_back(tctx);
3243cab2bb3Spatrick if (dead_threads_.size() <= thread_quarantine_size_)
3253cab2bb3Spatrick return;
3263cab2bb3Spatrick tctx = dead_threads_.front();
3273cab2bb3Spatrick dead_threads_.pop_front();
3283cab2bb3Spatrick CHECK_EQ(tctx->status, ThreadStatusDead);
3293cab2bb3Spatrick tctx->Reset();
3303cab2bb3Spatrick tctx->reuse_count++;
3313cab2bb3Spatrick if (max_reuse_ > 0 && tctx->reuse_count >= max_reuse_)
3323cab2bb3Spatrick return;
3333cab2bb3Spatrick invalid_threads_.push_back(tctx);
3343cab2bb3Spatrick }
3353cab2bb3Spatrick
QuarantinePop()3363cab2bb3Spatrick ThreadContextBase *ThreadRegistry::QuarantinePop() {
3373cab2bb3Spatrick if (invalid_threads_.size() == 0)
3383cab2bb3Spatrick return 0;
3393cab2bb3Spatrick ThreadContextBase *tctx = invalid_threads_.front();
3403cab2bb3Spatrick invalid_threads_.pop_front();
3413cab2bb3Spatrick return tctx;
3423cab2bb3Spatrick }
3433cab2bb3Spatrick
ConsumeThreadUserId(uptr user_id)344*810390e3Srobert u32 ThreadRegistry::ConsumeThreadUserId(uptr user_id) {
345*810390e3Srobert ThreadRegistryLock l(this);
346*810390e3Srobert u32 tid;
347*810390e3Srobert auto *t = live_.find(user_id);
348*810390e3Srobert CHECK(t);
349*810390e3Srobert tid = t->second;
350*810390e3Srobert live_.erase(t);
351*810390e3Srobert auto *tctx = threads_[tid];
352*810390e3Srobert CHECK_EQ(tctx->user_id, user_id);
353*810390e3Srobert tctx->user_id = 0;
354*810390e3Srobert return tid;
355*810390e3Srobert }
356*810390e3Srobert
SetThreadUserId(u32 tid,uptr user_id)3573cab2bb3Spatrick void ThreadRegistry::SetThreadUserId(u32 tid, uptr user_id) {
358*810390e3Srobert ThreadRegistryLock l(this);
3593cab2bb3Spatrick ThreadContextBase *tctx = threads_[tid];
3603cab2bb3Spatrick CHECK_NE(tctx, 0);
3613cab2bb3Spatrick CHECK_NE(tctx->status, ThreadStatusInvalid);
3623cab2bb3Spatrick CHECK_NE(tctx->status, ThreadStatusDead);
3633cab2bb3Spatrick CHECK_EQ(tctx->user_id, 0);
3643cab2bb3Spatrick tctx->user_id = user_id;
365*810390e3Srobert CHECK(live_.try_emplace(user_id, tctx->tid).second);
366*810390e3Srobert }
367*810390e3Srobert
OnFork(u32 tid)368*810390e3Srobert u32 ThreadRegistry::OnFork(u32 tid) {
369*810390e3Srobert ThreadRegistryLock l(this);
370*810390e3Srobert // We only purge user_id (pthread_t) of live threads because
371*810390e3Srobert // they cause CHECK failures if new threads with matching pthread_t
372*810390e3Srobert // created after fork.
373*810390e3Srobert // Potentially we could purge more info (ThreadContextBase themselves),
374*810390e3Srobert // but it's hard to test and easy to introduce new issues by doing this.
375*810390e3Srobert for (auto *tctx : threads_) {
376*810390e3Srobert if (tctx->tid == tid || !tctx->user_id)
377*810390e3Srobert continue;
378*810390e3Srobert CHECK(live_.erase(tctx->user_id));
379*810390e3Srobert tctx->user_id = 0;
380*810390e3Srobert }
381*810390e3Srobert return alive_threads_;
3823cab2bb3Spatrick }
3833cab2bb3Spatrick
3843cab2bb3Spatrick } // namespace __sanitizer
385