| 1 | //===-- sanitizer_thread_registry.cpp -------------------------------------===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | // |
| 9 | // This file is shared between sanitizer tools. |
| 10 | // |
| 11 | // General thread bookkeeping functionality. |
| 12 | //===----------------------------------------------------------------------===// |
| 13 | |
| 14 | #include "sanitizer_thread_registry.h" |
| 15 | |
| 16 | #include "sanitizer_placement_new.h" |
| 17 | |
| 18 | namespace __sanitizer { |
| 19 | |
| 20 | ThreadContextBase::ThreadContextBase(u32 tid) |
| 21 | : tid(tid), |
| 22 | unique_id(0), |
| 23 | reuse_count(), |
| 24 | os_id(0), |
| 25 | user_id(0), |
| 26 | status(ThreadStatusInvalid), |
| 27 | detached(false), |
| 28 | thread_type(ThreadType::Regular), |
| 29 | parent_tid(0), |
| 30 | stack_id(0), |
| 31 | next(0) { |
| 32 | name[0] = '\0'; |
| 33 | atomic_store(a: &thread_destroyed, v: 0, mo: memory_order_release); |
| 34 | } |
| 35 | |
| 36 | ThreadContextBase::~ThreadContextBase() { |
| 37 | // ThreadContextBase should never be deleted. |
| 38 | CHECK(0); |
| 39 | } |
| 40 | |
| 41 | void ThreadContextBase::SetName(const char *new_name) { |
| 42 | name[0] = '\0'; |
| 43 | if (new_name) { |
| 44 | internal_strncpy(dst: name, src: new_name, n: sizeof(name)); |
| 45 | name[sizeof(name) - 1] = '\0'; |
| 46 | } |
| 47 | } |
| 48 | |
| 49 | void ThreadContextBase::SetDead() { |
| 50 | CHECK(status == ThreadStatusRunning || status == ThreadStatusFinished); |
| 51 | status = ThreadStatusDead; |
| 52 | user_id = 0; |
| 53 | OnDead(); |
| 54 | } |
| 55 | |
| 56 | void ThreadContextBase::SetDestroyed() { |
| 57 | atomic_store(a: &thread_destroyed, v: 1, mo: memory_order_release); |
| 58 | } |
| 59 | |
| 60 | bool ThreadContextBase::GetDestroyed() { |
| 61 | return !!atomic_load(a: &thread_destroyed, mo: memory_order_acquire); |
| 62 | } |
| 63 | |
| 64 | void ThreadContextBase::SetJoined(void *arg) { |
| 65 | // FIXME(dvyukov): print message and continue (it's user error). |
| 66 | CHECK_EQ(false, detached); |
| 67 | CHECK_EQ(ThreadStatusFinished, status); |
| 68 | status = ThreadStatusDead; |
| 69 | user_id = 0; |
| 70 | OnJoined(arg); |
| 71 | } |
| 72 | |
| 73 | void ThreadContextBase::SetFinished() { |
| 74 | // ThreadRegistry::FinishThread calls here in ThreadStatusCreated state |
| 75 | // for a thread that never actually started. In that case the thread |
| 76 | // should go to ThreadStatusFinished regardless of whether it was created |
| 77 | // as detached. |
| 78 | if (!detached || status == ThreadStatusCreated) |
| 79 | status = ThreadStatusFinished; |
| 80 | OnFinished(); |
| 81 | } |
| 82 | |
| 83 | void ThreadContextBase::SetStarted(tid_t _os_id, ThreadType _thread_type, |
| 84 | void *arg) { |
| 85 | status = ThreadStatusRunning; |
| 86 | os_id = _os_id; |
| 87 | thread_type = _thread_type; |
| 88 | OnStarted(arg); |
| 89 | } |
| 90 | |
| 91 | void ThreadContextBase::SetCreated(uptr _user_id, u64 _unique_id, |
| 92 | bool _detached, u32 _parent_tid, |
| 93 | u32 _stack_tid, void *arg) { |
| 94 | status = ThreadStatusCreated; |
| 95 | user_id = _user_id; |
| 96 | unique_id = _unique_id; |
| 97 | detached = _detached; |
| 98 | // Parent tid makes no sense for the main thread. |
| 99 | if (tid != kMainTid) { |
| 100 | parent_tid = _parent_tid; |
| 101 | stack_id = _stack_tid; |
| 102 | } |
| 103 | OnCreated(arg); |
| 104 | } |
| 105 | |
| 106 | void ThreadContextBase::Reset() { |
| 107 | status = ThreadStatusInvalid; |
| 108 | SetName(0); |
| 109 | atomic_store(a: &thread_destroyed, v: 0, mo: memory_order_release); |
| 110 | OnReset(); |
| 111 | } |
| 112 | |
| 113 | // ThreadRegistry implementation. |
| 114 | |
| 115 | ThreadRegistry::ThreadRegistry(ThreadContextFactory factory) |
| 116 | : ThreadRegistry(factory, UINT32_MAX, UINT32_MAX, 0) {} |
| 117 | |
| 118 | ThreadRegistry::ThreadRegistry(ThreadContextFactory factory, u32 max_threads, |
| 119 | u32 thread_quarantine_size, u32 max_reuse) |
| 120 | : context_factory_(factory), |
| 121 | max_threads_(max_threads), |
| 122 | thread_quarantine_size_(thread_quarantine_size), |
| 123 | max_reuse_(max_reuse), |
| 124 | mtx_(MutexThreadRegistry), |
| 125 | total_threads_(0), |
| 126 | alive_threads_(0), |
| 127 | max_alive_threads_(0), |
| 128 | running_threads_(0) { |
| 129 | dead_threads_.clear(); |
| 130 | invalid_threads_.clear(); |
| 131 | } |
| 132 | |
| 133 | void ThreadRegistry::GetNumberOfThreads(uptr *total, uptr *running, |
| 134 | uptr *alive) { |
| 135 | ThreadRegistryLock l(this); |
| 136 | if (total) |
| 137 | *total = threads_.size(); |
| 138 | if (running) |
| 139 | *running = running_threads_; |
| 140 | if (alive) |
| 141 | *alive = alive_threads_; |
| 142 | } |
| 143 | |
| 144 | uptr ThreadRegistry::GetMaxAliveThreads() { |
| 145 | ThreadRegistryLock l(this); |
| 146 | return max_alive_threads_; |
| 147 | } |
| 148 | |
| 149 | u32 ThreadRegistry::CreateThread(uptr user_id, bool detached, u32 parent_tid, |
| 150 | u32 stack_tid, void *arg) { |
| 151 | ThreadRegistryLock l(this); |
| 152 | u32 tid = kInvalidTid; |
| 153 | ThreadContextBase *tctx = QuarantinePop(); |
| 154 | if (tctx) { |
| 155 | tid = tctx->tid; |
| 156 | } else if (threads_.size() < max_threads_) { |
| 157 | // Allocate new thread context and tid. |
| 158 | tid = threads_.size(); |
| 159 | tctx = context_factory_(tid); |
| 160 | threads_.push_back(element: tctx); |
| 161 | } else { |
| 162 | #if !SANITIZER_GO |
| 163 | Report(format: "%s: Thread limit (%u threads) exceeded. Dying.\n" , |
| 164 | SanitizerToolName, max_threads_); |
| 165 | #else |
| 166 | Printf( |
| 167 | "race: limit on %u simultaneously alive goroutines is exceeded," |
| 168 | " dying\n" , |
| 169 | max_threads_); |
| 170 | #endif |
| 171 | Die(); |
| 172 | } |
| 173 | CHECK_NE(tctx, 0); |
| 174 | CHECK_NE(tid, kInvalidTid); |
| 175 | CHECK_LT(tid, max_threads_); |
| 176 | CHECK_EQ(tctx->status, ThreadStatusInvalid); |
| 177 | alive_threads_++; |
| 178 | if (max_alive_threads_ < alive_threads_) { |
| 179 | max_alive_threads_++; |
| 180 | CHECK_EQ(alive_threads_, max_alive_threads_); |
| 181 | } |
| 182 | if (user_id) { |
| 183 | // Ensure that user_id is unique. If it's not the case we are screwed. |
| 184 | // Ignoring this situation may lead to very hard to debug false |
| 185 | // positives later (e.g. if we join a wrong thread). |
| 186 | CHECK(live_.try_emplace(user_id, tid).second); |
| 187 | } |
| 188 | tctx->SetCreated(user_id: user_id, unique_id: total_threads_++, detached: detached, parent_tid: parent_tid, stack_tid: stack_tid, |
| 189 | arg); |
| 190 | return tid; |
| 191 | } |
| 192 | |
| 193 | void ThreadRegistry::RunCallbackForEachThreadLocked(ThreadCallback cb, |
| 194 | void *arg) { |
| 195 | CheckLocked(); |
| 196 | for (u32 tid = 0; tid < threads_.size(); tid++) { |
| 197 | ThreadContextBase *tctx = threads_[tid]; |
| 198 | if (tctx == 0) |
| 199 | continue; |
| 200 | cb(tctx, arg); |
| 201 | } |
| 202 | } |
| 203 | |
| 204 | u32 ThreadRegistry::FindThread(FindThreadCallback cb, void *arg) { |
| 205 | ThreadRegistryLock l(this); |
| 206 | for (u32 tid = 0; tid < threads_.size(); tid++) { |
| 207 | ThreadContextBase *tctx = threads_[tid]; |
| 208 | if (tctx != 0 && cb(tctx, arg)) |
| 209 | return tctx->tid; |
| 210 | } |
| 211 | return kInvalidTid; |
| 212 | } |
| 213 | |
| 214 | ThreadContextBase *ThreadRegistry::FindThreadContextLocked( |
| 215 | FindThreadCallback cb, void *arg) { |
| 216 | CheckLocked(); |
| 217 | for (u32 tid = 0; tid < threads_.size(); tid++) { |
| 218 | ThreadContextBase *tctx = threads_[tid]; |
| 219 | if (tctx != 0 && cb(tctx, arg)) |
| 220 | return tctx; |
| 221 | } |
| 222 | return 0; |
| 223 | } |
| 224 | |
| 225 | static bool FindThreadContextByOsIdCallback(ThreadContextBase *tctx, |
| 226 | void *arg) { |
| 227 | return (tctx->os_id == (uptr)arg && tctx->status != ThreadStatusInvalid && |
| 228 | tctx->status != ThreadStatusDead); |
| 229 | } |
| 230 | |
| 231 | ThreadContextBase *ThreadRegistry::FindThreadContextByOsIDLocked(tid_t os_id) { |
| 232 | return FindThreadContextLocked(cb: FindThreadContextByOsIdCallback, |
| 233 | arg: (void *)os_id); |
| 234 | } |
| 235 | |
| 236 | void ThreadRegistry::SetThreadName(u32 tid, const char *name) { |
| 237 | ThreadRegistryLock l(this); |
| 238 | ThreadContextBase *tctx = threads_[tid]; |
| 239 | CHECK_NE(tctx, 0); |
| 240 | CHECK_EQ(SANITIZER_FUCHSIA ? ThreadStatusCreated : ThreadStatusRunning, |
| 241 | tctx->status); |
| 242 | tctx->SetName(name); |
| 243 | } |
| 244 | |
| 245 | void ThreadRegistry::SetThreadNameByUserId(uptr user_id, const char *name) { |
| 246 | ThreadRegistryLock l(this); |
| 247 | if (const auto *tid = live_.find(Key: user_id)) |
| 248 | threads_[tid->second]->SetName(name); |
| 249 | } |
| 250 | |
| 251 | void ThreadRegistry::DetachThread(u32 tid, void *arg) { |
| 252 | ThreadRegistryLock l(this); |
| 253 | ThreadContextBase *tctx = threads_[tid]; |
| 254 | CHECK_NE(tctx, 0); |
| 255 | if (tctx->status == ThreadStatusInvalid) { |
| 256 | Report(format: "%s: Detach of non-existent thread\n" , SanitizerToolName); |
| 257 | return; |
| 258 | } |
| 259 | tctx->OnDetached(arg); |
| 260 | if (tctx->status == ThreadStatusFinished) { |
| 261 | if (tctx->user_id) |
| 262 | live_.erase(Val: tctx->user_id); |
| 263 | tctx->SetDead(); |
| 264 | QuarantinePush(tctx); |
| 265 | } else { |
| 266 | tctx->detached = true; |
| 267 | } |
| 268 | } |
| 269 | |
| 270 | void ThreadRegistry::JoinThread(u32 tid, void *arg) { |
| 271 | bool destroyed = false; |
| 272 | do { |
| 273 | { |
| 274 | ThreadRegistryLock l(this); |
| 275 | ThreadContextBase *tctx = threads_[tid]; |
| 276 | CHECK_NE(tctx, 0); |
| 277 | if (tctx->status == ThreadStatusInvalid) { |
| 278 | Report(format: "%s: Join of non-existent thread\n" , SanitizerToolName); |
| 279 | return; |
| 280 | } |
| 281 | if ((destroyed = tctx->GetDestroyed())) { |
| 282 | if (tctx->user_id) |
| 283 | live_.erase(Val: tctx->user_id); |
| 284 | tctx->SetJoined(arg); |
| 285 | QuarantinePush(tctx); |
| 286 | } |
| 287 | } |
| 288 | if (!destroyed) |
| 289 | internal_sched_yield(); |
| 290 | } while (!destroyed); |
| 291 | } |
| 292 | |
| 293 | // Normally this is called when the thread is about to exit. If |
| 294 | // called in ThreadStatusCreated state, then this thread was never |
| 295 | // really started. We just did CreateThread for a prospective new |
| 296 | // thread before trying to create it, and then failed to actually |
| 297 | // create it, and so never called StartThread. |
| 298 | ThreadStatus ThreadRegistry::FinishThread(u32 tid) { |
| 299 | ThreadRegistryLock l(this); |
| 300 | CHECK_GT(alive_threads_, 0); |
| 301 | alive_threads_--; |
| 302 | ThreadContextBase *tctx = threads_[tid]; |
| 303 | CHECK_NE(tctx, 0); |
| 304 | bool dead = tctx->detached; |
| 305 | ThreadStatus prev_status = tctx->status; |
| 306 | if (tctx->status == ThreadStatusRunning) { |
| 307 | CHECK_GT(running_threads_, 0); |
| 308 | running_threads_--; |
| 309 | } else { |
| 310 | // The thread never really existed. |
| 311 | CHECK_EQ(tctx->status, ThreadStatusCreated); |
| 312 | dead = true; |
| 313 | } |
| 314 | tctx->SetFinished(); |
| 315 | if (dead) { |
| 316 | if (tctx->user_id) |
| 317 | live_.erase(Val: tctx->user_id); |
| 318 | tctx->SetDead(); |
| 319 | QuarantinePush(tctx); |
| 320 | } |
| 321 | tctx->SetDestroyed(); |
| 322 | return prev_status; |
| 323 | } |
| 324 | |
| 325 | void ThreadRegistry::StartThread(u32 tid, tid_t os_id, ThreadType thread_type, |
| 326 | void *arg) { |
| 327 | ThreadRegistryLock l(this); |
| 328 | running_threads_++; |
| 329 | ThreadContextBase *tctx = threads_[tid]; |
| 330 | CHECK_NE(tctx, 0); |
| 331 | CHECK_EQ(ThreadStatusCreated, tctx->status); |
| 332 | tctx->SetStarted(os_id: os_id, thread_type: thread_type, arg); |
| 333 | } |
| 334 | |
| 335 | void ThreadRegistry::QuarantinePush(ThreadContextBase *tctx) { |
| 336 | if (tctx->tid == 0) |
| 337 | return; // Don't reuse the main thread. It's a special snowflake. |
| 338 | dead_threads_.push_back(x: tctx); |
| 339 | if (dead_threads_.size() <= thread_quarantine_size_) |
| 340 | return; |
| 341 | tctx = dead_threads_.front(); |
| 342 | dead_threads_.pop_front(); |
| 343 | CHECK_EQ(tctx->status, ThreadStatusDead); |
| 344 | tctx->Reset(); |
| 345 | tctx->reuse_count++; |
| 346 | if (max_reuse_ > 0 && tctx->reuse_count >= max_reuse_) |
| 347 | return; |
| 348 | invalid_threads_.push_back(x: tctx); |
| 349 | } |
| 350 | |
| 351 | ThreadContextBase *ThreadRegistry::QuarantinePop() { |
| 352 | if (invalid_threads_.size() == 0) |
| 353 | return nullptr; |
| 354 | ThreadContextBase *tctx = invalid_threads_.front(); |
| 355 | invalid_threads_.pop_front(); |
| 356 | return tctx; |
| 357 | } |
| 358 | |
| 359 | u32 ThreadRegistry::ConsumeThreadUserId(uptr user_id) { |
| 360 | ThreadRegistryLock l(this); |
| 361 | u32 tid; |
| 362 | auto *t = live_.find(Key: user_id); |
| 363 | CHECK(t); |
| 364 | tid = t->second; |
| 365 | live_.erase(I: t); |
| 366 | auto *tctx = threads_[tid]; |
| 367 | CHECK_EQ(tctx->user_id, user_id); |
| 368 | tctx->user_id = 0; |
| 369 | return tid; |
| 370 | } |
| 371 | |
| 372 | void ThreadRegistry::SetThreadUserId(u32 tid, uptr user_id) { |
| 373 | ThreadRegistryLock l(this); |
| 374 | ThreadContextBase *tctx = threads_[tid]; |
| 375 | CHECK_NE(tctx, 0); |
| 376 | CHECK_NE(tctx->status, ThreadStatusInvalid); |
| 377 | CHECK_NE(tctx->status, ThreadStatusDead); |
| 378 | CHECK_EQ(tctx->user_id, 0); |
| 379 | tctx->user_id = user_id; |
| 380 | CHECK(live_.try_emplace(user_id, tctx->tid).second); |
| 381 | } |
| 382 | |
| 383 | u32 ThreadRegistry::OnFork(u32 tid) { |
| 384 | ThreadRegistryLock l(this); |
| 385 | // We only purge user_id (pthread_t) of live threads because |
| 386 | // they cause CHECK failures if new threads with matching pthread_t |
| 387 | // created after fork. |
| 388 | // Potentially we could purge more info (ThreadContextBase themselves), |
| 389 | // but it's hard to test and easy to introduce new issues by doing this. |
| 390 | for (auto *tctx : threads_) { |
| 391 | if (tctx->tid == tid || !tctx->user_id) |
| 392 | continue; |
| 393 | CHECK(live_.erase(tctx->user_id)); |
| 394 | tctx->user_id = 0; |
| 395 | } |
| 396 | return alive_threads_; |
| 397 | } |
| 398 | |
| 399 | } // namespace __sanitizer |
| 400 | |