1 | //===-- sanitizer_thread_registry_test.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 a part of shared sanitizer runtime. |
10 | // |
11 | //===----------------------------------------------------------------------===// |
12 | #include "sanitizer_common/sanitizer_thread_registry.h" |
13 | |
14 | #include <iostream> |
15 | #include <vector> |
16 | |
17 | #include "gmock/gmock.h" |
18 | #include "gtest/gtest.h" |
19 | #include "sanitizer_common/sanitizer_common.h" |
20 | #include "sanitizer_common/sanitizer_stackdepot.h" |
21 | #include "sanitizer_common/sanitizer_stacktrace.h" |
22 | #include "sanitizer_common/sanitizer_thread_history.h" |
23 | #include "sanitizer_pthread_wrappers.h" |
24 | |
25 | using testing::HasSubstr; |
26 | |
27 | namespace __sanitizer { |
28 | |
29 | static Mutex tctx_allocator_lock; |
30 | static LowLevelAllocator tctx_allocator; |
31 | |
32 | template<typename TCTX> |
33 | static ThreadContextBase *GetThreadContext(u32 tid) { |
34 | Lock l(&tctx_allocator_lock); |
35 | return new(tctx_allocator) TCTX(tid); |
36 | } |
37 | |
38 | static const u32 kMaxRegistryThreads = 1000; |
39 | static const u32 kRegistryQuarantine = 2; |
40 | |
41 | static void CheckThreadQuantity(ThreadRegistry *registry, uptr exp_total, |
42 | uptr exp_running, uptr exp_alive) { |
43 | uptr total, running, alive; |
44 | registry->GetNumberOfThreads(total: &total, running: &running, alive: &alive); |
45 | EXPECT_EQ(exp_total, total); |
46 | EXPECT_EQ(exp_running, running); |
47 | EXPECT_EQ(exp_alive, alive); |
48 | } |
49 | |
50 | static bool is_detached(u32 tid) { |
51 | return (tid % 2 == 0); |
52 | } |
53 | |
54 | static uptr get_uid(u32 tid) { |
55 | return tid * 2; |
56 | } |
57 | |
58 | static bool HasName(ThreadContextBase *tctx, void *arg) { |
59 | char *name = (char*)arg; |
60 | return (0 == internal_strcmp(s1: tctx->name, s2: name)); |
61 | } |
62 | |
63 | static bool HasUid(ThreadContextBase *tctx, void *arg) { |
64 | uptr uid = (uptr)arg; |
65 | return (tctx->user_id == uid); |
66 | } |
67 | |
68 | static void MarkUidAsPresent(ThreadContextBase *tctx, void *arg) { |
69 | bool *arr = (bool*)arg; |
70 | arr[tctx->tid] = true; |
71 | } |
72 | |
73 | static void TestRegistry(ThreadRegistry *registry, bool has_quarantine) { |
74 | // Create and start a main thread. |
75 | EXPECT_EQ(0U, registry->CreateThread(user_id: get_uid(tid: 0), detached: true, parent_tid: -1, stack_tid: 0, arg: nullptr)); |
76 | registry->StartThread(tid: 0, os_id: 0, thread_type: ThreadType::Regular, arg: 0); |
77 | // Create a bunch of threads. |
78 | for (u32 i = 1; i <= 10; i++) { |
79 | EXPECT_EQ(i, registry->CreateThread(user_id: get_uid(tid: i), detached: is_detached(tid: i), parent_tid: 100 + i, |
80 | stack_tid: 200 + i, arg: nullptr)); |
81 | } |
82 | CheckThreadQuantity(registry, exp_total: 11, exp_running: 1, exp_alive: 11); |
83 | // Start some of them. |
84 | for (u32 i = 1; i <= 5; i++) { |
85 | registry->StartThread(tid: i, os_id: 0, thread_type: ThreadType::Regular, arg: 0); |
86 | } |
87 | CheckThreadQuantity(registry, exp_total: 11, exp_running: 6, exp_alive: 11); |
88 | // Finish, create and start more threads. |
89 | for (u32 i = 1; i <= 5; i++) { |
90 | registry->FinishThread(tid: i); |
91 | if (!is_detached(tid: i)) |
92 | registry->JoinThread(tid: i, arg: 0); |
93 | } |
94 | for (u32 i = 6; i <= 10; i++) { |
95 | registry->StartThread(tid: i, os_id: 0, thread_type: ThreadType::Regular, arg: 0); |
96 | } |
97 | std::vector<u32> new_tids; |
98 | for (u32 i = 11; i <= 15; i++) { |
99 | new_tids.push_back( |
100 | registry->CreateThread(get_uid(i), is_detached(i), 0, 0, nullptr)); |
101 | } |
102 | ASSERT_LE(kRegistryQuarantine, 5U); |
103 | u32 exp_total = 16 - (has_quarantine ? 5 - kRegistryQuarantine : 0); |
104 | CheckThreadQuantity(registry, exp_total, exp_running: 6, exp_alive: 11); |
105 | // Test SetThreadName and FindThread. |
106 | registry->SetThreadName(tid: 6, name: "six" ); |
107 | registry->SetThreadName(tid: 7, name: "seven" ); |
108 | EXPECT_EQ(7U, registry->FindThread(cb: HasName, arg: (void *)"seven" )); |
109 | EXPECT_EQ(kInvalidTid, registry->FindThread(cb: HasName, arg: (void *)"none" )); |
110 | EXPECT_EQ(0U, registry->FindThread(cb: HasUid, arg: (void *)get_uid(tid: 0))); |
111 | EXPECT_EQ(10U, registry->FindThread(cb: HasUid, arg: (void *)get_uid(tid: 10))); |
112 | EXPECT_EQ(kInvalidTid, registry->FindThread(cb: HasUid, arg: (void *)0x1234)); |
113 | EXPECT_EQ(7U, |
114 | registry->FindThread(cb: [](ThreadContextBase *tctx, |
115 | void *) { return tctx->parent_tid == 107; }, |
116 | arg: nullptr)); |
117 | EXPECT_EQ(8U, |
118 | registry->FindThread(cb: [](ThreadContextBase *tctx, |
119 | void *) { return tctx->stack_id == 208; }, |
120 | arg: nullptr)); |
121 | // Detach and finish and join remaining threads. |
122 | for (u32 i = 6; i <= 10; i++) { |
123 | registry->DetachThread(tid: i, arg: 0); |
124 | registry->FinishThread(tid: i); |
125 | } |
126 | for (u32 i = 0; i < new_tids.size(); i++) { |
127 | u32 tid = new_tids[i]; |
128 | registry->StartThread(tid, 0, ThreadType::Regular, 0); |
129 | registry->DetachThread(tid, 0); |
130 | registry->FinishThread(tid); |
131 | } |
132 | CheckThreadQuantity(registry, exp_total, exp_running: 1, exp_alive: 1); |
133 | // Test methods that require the caller to hold a ThreadRegistryLock. |
134 | bool has_tid[16]; |
135 | internal_memset(s: &has_tid[0], c: 0, n: sizeof(has_tid)); |
136 | { |
137 | ThreadRegistryLock l(registry); |
138 | registry->RunCallbackForEachThreadLocked(cb: MarkUidAsPresent, arg: &has_tid[0]); |
139 | } |
140 | for (u32 i = 0; i < exp_total; i++) { |
141 | EXPECT_TRUE(has_tid[i]); |
142 | } |
143 | { |
144 | ThreadRegistryLock l(registry); |
145 | registry->CheckLocked(); |
146 | ThreadContextBase *main_thread = registry->GetThreadLocked(tid: 0); |
147 | EXPECT_EQ(main_thread, registry->FindThreadContextLocked( |
148 | cb: HasUid, arg: (void*)get_uid(tid: 0))); |
149 | } |
150 | EXPECT_EQ(11U, registry->GetMaxAliveThreads()); |
151 | } |
152 | |
153 | TEST(SanitizerCommon, ThreadRegistryTest) { |
154 | ThreadRegistry quarantine_registry(GetThreadContext<ThreadContextBase>, |
155 | kMaxRegistryThreads, kRegistryQuarantine, |
156 | 0); |
157 | TestRegistry(registry: &quarantine_registry, has_quarantine: true); |
158 | |
159 | ThreadRegistry no_quarantine_registry(GetThreadContext<ThreadContextBase>, |
160 | kMaxRegistryThreads, |
161 | kMaxRegistryThreads, 0); |
162 | TestRegistry(registry: &no_quarantine_registry, has_quarantine: false); |
163 | } |
164 | |
165 | static const int kThreadsPerShard = 20; |
166 | static const int kNumShards = 25; |
167 | |
168 | static int num_created[kNumShards + 1]; |
169 | static int num_started[kNumShards + 1]; |
170 | static int num_joined[kNumShards + 1]; |
171 | |
172 | namespace { |
173 | |
174 | struct RunThreadArgs { |
175 | ThreadRegistry *registry; |
176 | uptr shard; // started from 1. |
177 | }; |
178 | |
179 | class TestThreadContext final : public ThreadContextBase { |
180 | public: |
181 | explicit TestThreadContext(int tid) : ThreadContextBase(tid) {} |
182 | void OnJoined(void *arg) { |
183 | uptr shard = (uptr)arg; |
184 | num_joined[shard]++; |
185 | } |
186 | void OnStarted(void *arg) { |
187 | uptr shard = (uptr)arg; |
188 | num_started[shard]++; |
189 | } |
190 | void OnCreated(void *arg) { |
191 | uptr shard = (uptr)arg; |
192 | num_created[shard]++; |
193 | } |
194 | }; |
195 | |
196 | } // namespace |
197 | |
198 | void *RunThread(void *arg) { |
199 | RunThreadArgs *args = static_cast<RunThreadArgs*>(arg); |
200 | std::vector<int> tids; |
201 | for (int i = 0; i < kThreadsPerShard; i++) |
202 | tids.push_back( |
203 | args->registry->CreateThread(0, false, 0, (void*)args->shard)); |
204 | for (int i = 0; i < kThreadsPerShard; i++) |
205 | args->registry->StartThread(tids[i], 0, ThreadType::Regular, |
206 | (void*)args->shard); |
207 | for (int i = 0; i < kThreadsPerShard; i++) |
208 | args->registry->FinishThread(tids[i]); |
209 | for (int i = 0; i < kThreadsPerShard; i++) |
210 | args->registry->JoinThread(tids[i], (void*)args->shard); |
211 | return 0; |
212 | } |
213 | |
214 | static void ThreadedTestRegistry(ThreadRegistry *registry) { |
215 | // Create and start a main thread. |
216 | EXPECT_EQ(0U, registry->CreateThread(user_id: 0, detached: true, parent_tid: -1, arg: 0)); |
217 | registry->StartThread(tid: 0, os_id: 0, thread_type: ThreadType::Regular, arg: 0); |
218 | pthread_t threads[kNumShards]; |
219 | RunThreadArgs args[kNumShards]; |
220 | for (int i = 0; i < kNumShards; i++) { |
221 | args[i].registry = registry; |
222 | args[i].shard = i + 1; |
223 | PTHREAD_CREATE(&threads[i], 0, RunThread, &args[i]); |
224 | } |
225 | for (int i = 0; i < kNumShards; i++) { |
226 | PTHREAD_JOIN(threads[i], 0); |
227 | } |
228 | // Check that each thread created/started/joined correct amount |
229 | // of "threads" in thread_registry. |
230 | EXPECT_EQ(1, num_created[0]); |
231 | EXPECT_EQ(1, num_started[0]); |
232 | EXPECT_EQ(0, num_joined[0]); |
233 | for (int i = 1; i <= kNumShards; i++) { |
234 | EXPECT_EQ(kThreadsPerShard, num_created[i]); |
235 | EXPECT_EQ(kThreadsPerShard, num_started[i]); |
236 | EXPECT_EQ(kThreadsPerShard, num_joined[i]); |
237 | } |
238 | } |
239 | |
240 | TEST(SanitizerCommon, ThreadRegistryThreadedTest) { |
241 | memset(&num_created, 0, sizeof(num_created)); |
242 | memset(&num_started, 0, sizeof(num_created)); |
243 | memset(&num_joined, 0, sizeof(num_created)); |
244 | |
245 | ThreadRegistry registry(GetThreadContext<TestThreadContext>, |
246 | kThreadsPerShard * kNumShards + 1, 10, 0); |
247 | ThreadedTestRegistry(registry: ®istry); |
248 | } |
249 | |
250 | TEST(SanitizerCommon, PrintThreadHistory) { |
251 | ThreadRegistry registry(GetThreadContext<TestThreadContext>, |
252 | kThreadsPerShard * kNumShards + 1, 10, 0); |
253 | |
254 | UNINITIALIZED BufferedStackTrace stack1; |
255 | stack1.Unwind(StackTrace::GetCurrentPc(), GET_CURRENT_FRAME(), nullptr, false, |
256 | /*max_depth=*/1); |
257 | |
258 | UNINITIALIZED BufferedStackTrace stack2; |
259 | stack2.Unwind(StackTrace::GetCurrentPc(), GET_CURRENT_FRAME(), nullptr, false, |
260 | /*max_depth=*/1); |
261 | |
262 | EXPECT_EQ(0U, registry.CreateThread(0, true, -1, 0, nullptr)); |
263 | for (int i = 0; i < 5; i++) { |
264 | registry.CreateThread(0, true, 0, StackDepotPut(stack1), nullptr); |
265 | registry.CreateThread(0, true, 0, StackDepotPut(stack2), nullptr); |
266 | } |
267 | |
268 | InternalScopedString out; |
269 | PrintThreadHistory(registry, out); |
270 | |
271 | std::string substrings[] = { |
272 | "Thread T0/0 was created by T-1" , |
273 | "<empty stack>" , |
274 | "" , |
275 | "Thread T1/0 was created by T0/0" , |
276 | "Thread T3/0 was created by T0/0" , |
277 | "Thread T5/0 was created by T0/0" , |
278 | "Thread T7/0 was created by T0/0" , |
279 | "Thread T9/0 was created by T0/0" , |
280 | "#0 0x" , |
281 | "" , |
282 | "Thread T2/0 was created by T0/0" , |
283 | "Thread T4/0 was created by T0/0" , |
284 | "Thread T6/0 was created by T0/0" , |
285 | "Thread T8/0 was created by T0/0" , |
286 | "Thread T10/0 was created by T0/0" , |
287 | "#0 0x" , |
288 | "" , |
289 | }; |
290 | |
291 | std::stringstream ss(out.data()); |
292 | std::string line; |
293 | |
294 | for (auto substr : substrings) { |
295 | std::getline(ss, line); |
296 | EXPECT_THAT(line, HasSubstr(substr)) << line; |
297 | } |
298 | |
299 | EXPECT_FALSE(std::getline(ss, line)) << "Unmatched line: " << line; |
300 | } |
301 | |
302 | } // namespace __sanitizer |
303 | |