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 |
Definitions
- ThreadContextBase
- ~ThreadContextBase
- SetName
- SetDead
- SetDestroyed
- GetDestroyed
- SetJoined
- SetFinished
- SetStarted
- SetCreated
- Reset
- ThreadRegistry
- ThreadRegistry
- GetNumberOfThreads
- GetMaxAliveThreads
- CreateThread
- RunCallbackForEachThreadLocked
- FindThread
- FindThreadContextLocked
- FindThreadContextByOsIdCallback
- FindThreadContextByOsIDLocked
- SetThreadName
- SetThreadNameByUserId
- DetachThread
- JoinThread
- FinishThread
- StartThread
- QuarantinePush
- QuarantinePop
- ConsumeThreadUserId
- SetThreadUserId
Learn to use CMake with our Intro Training
Find out more