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 <vector> |
15 | |
16 | #include "gtest/gtest.h" |
17 | #include "sanitizer_pthread_wrappers.h" |
18 | |
19 | namespace __sanitizer { |
20 | |
21 | static Mutex tctx_allocator_lock; |
22 | static LowLevelAllocator tctx_allocator; |
23 | |
24 | template<typename TCTX> |
25 | static ThreadContextBase *GetThreadContext(u32 tid) { |
26 | Lock l(&tctx_allocator_lock); |
27 | return new(tctx_allocator) TCTX(tid); |
28 | } |
29 | |
30 | static const u32 kMaxRegistryThreads = 1000; |
31 | static const u32 kRegistryQuarantine = 2; |
32 | |
33 | static void CheckThreadQuantity(ThreadRegistry *registry, uptr exp_total, |
34 | uptr exp_running, uptr exp_alive) { |
35 | uptr total, running, alive; |
36 | registry->GetNumberOfThreads(total: &total, running: &running, alive: &alive); |
37 | EXPECT_EQ(exp_total, total); |
38 | EXPECT_EQ(exp_running, running); |
39 | EXPECT_EQ(exp_alive, alive); |
40 | } |
41 | |
42 | static bool is_detached(u32 tid) { |
43 | return (tid % 2 == 0); |
44 | } |
45 | |
46 | static uptr get_uid(u32 tid) { |
47 | return tid * 2; |
48 | } |
49 | |
50 | static bool HasName(ThreadContextBase *tctx, void *arg) { |
51 | char *name = (char*)arg; |
52 | return (0 == internal_strcmp(s1: tctx->name, s2: name)); |
53 | } |
54 | |
55 | static bool HasUid(ThreadContextBase *tctx, void *arg) { |
56 | uptr uid = (uptr)arg; |
57 | return (tctx->user_id == uid); |
58 | } |
59 | |
60 | static void MarkUidAsPresent(ThreadContextBase *tctx, void *arg) { |
61 | bool *arr = (bool*)arg; |
62 | arr[tctx->tid] = true; |
63 | } |
64 | |
65 | static void TestRegistry(ThreadRegistry *registry, bool has_quarantine) { |
66 | // Create and start a main thread. |
67 | EXPECT_EQ(0U, registry->CreateThread(user_id: get_uid(tid: 0), detached: true, parent_tid: -1, arg: 0)); |
68 | registry->StartThread(tid: 0, os_id: 0, thread_type: ThreadType::Regular, arg: 0); |
69 | // Create a bunch of threads. |
70 | for (u32 i = 1; i <= 10; i++) { |
71 | EXPECT_EQ(i, registry->CreateThread(user_id: get_uid(tid: i), detached: is_detached(tid: i), parent_tid: 0, arg: 0)); |
72 | } |
73 | CheckThreadQuantity(registry, exp_total: 11, exp_running: 1, exp_alive: 11); |
74 | // Start some of them. |
75 | for (u32 i = 1; i <= 5; i++) { |
76 | registry->StartThread(tid: i, os_id: 0, thread_type: ThreadType::Regular, arg: 0); |
77 | } |
78 | CheckThreadQuantity(registry, exp_total: 11, exp_running: 6, exp_alive: 11); |
79 | // Finish, create and start more threads. |
80 | for (u32 i = 1; i <= 5; i++) { |
81 | registry->FinishThread(tid: i); |
82 | if (!is_detached(tid: i)) |
83 | registry->JoinThread(tid: i, arg: 0); |
84 | } |
85 | for (u32 i = 6; i <= 10; i++) { |
86 | registry->StartThread(tid: i, os_id: 0, thread_type: ThreadType::Regular, arg: 0); |
87 | } |
88 | std::vector<u32> new_tids; |
89 | for (u32 i = 11; i <= 15; i++) { |
90 | new_tids.push_back( |
91 | registry->CreateThread(get_uid(i), is_detached(i), 0, 0)); |
92 | } |
93 | ASSERT_LE(kRegistryQuarantine, 5U); |
94 | u32 exp_total = 16 - (has_quarantine ? 5 - kRegistryQuarantine : 0); |
95 | CheckThreadQuantity(registry, exp_total, exp_running: 6, exp_alive: 11); |
96 | // Test SetThreadName and FindThread. |
97 | registry->SetThreadName(tid: 6, name: "six" ); |
98 | registry->SetThreadName(tid: 7, name: "seven" ); |
99 | EXPECT_EQ(7U, registry->FindThread(cb: HasName, arg: (void*)"seven" )); |
100 | EXPECT_EQ(kInvalidTid, registry->FindThread(cb: HasName, arg: (void *)"none" )); |
101 | EXPECT_EQ(0U, registry->FindThread(cb: HasUid, arg: (void*)get_uid(tid: 0))); |
102 | EXPECT_EQ(10U, registry->FindThread(cb: HasUid, arg: (void*)get_uid(tid: 10))); |
103 | EXPECT_EQ(kInvalidTid, registry->FindThread(cb: HasUid, arg: (void *)0x1234)); |
104 | // Detach and finish and join remaining threads. |
105 | for (u32 i = 6; i <= 10; i++) { |
106 | registry->DetachThread(tid: i, arg: 0); |
107 | registry->FinishThread(tid: i); |
108 | } |
109 | for (u32 i = 0; i < new_tids.size(); i++) { |
110 | u32 tid = new_tids[i]; |
111 | registry->StartThread(tid, 0, ThreadType::Regular, 0); |
112 | registry->DetachThread(tid, 0); |
113 | registry->FinishThread(tid); |
114 | } |
115 | CheckThreadQuantity(registry, exp_total, exp_running: 1, exp_alive: 1); |
116 | // Test methods that require the caller to hold a ThreadRegistryLock. |
117 | bool has_tid[16]; |
118 | internal_memset(s: &has_tid[0], c: 0, n: sizeof(has_tid)); |
119 | { |
120 | ThreadRegistryLock l(registry); |
121 | registry->RunCallbackForEachThreadLocked(cb: MarkUidAsPresent, arg: &has_tid[0]); |
122 | } |
123 | for (u32 i = 0; i < exp_total; i++) { |
124 | EXPECT_TRUE(has_tid[i]); |
125 | } |
126 | { |
127 | ThreadRegistryLock l(registry); |
128 | registry->CheckLocked(); |
129 | ThreadContextBase *main_thread = registry->GetThreadLocked(tid: 0); |
130 | EXPECT_EQ(main_thread, registry->FindThreadContextLocked( |
131 | cb: HasUid, arg: (void*)get_uid(tid: 0))); |
132 | } |
133 | EXPECT_EQ(11U, registry->GetMaxAliveThreads()); |
134 | } |
135 | |
136 | TEST(SanitizerCommon, ThreadRegistryTest) { |
137 | ThreadRegistry quarantine_registry(GetThreadContext<ThreadContextBase>, |
138 | kMaxRegistryThreads, kRegistryQuarantine, |
139 | 0); |
140 | TestRegistry(registry: &quarantine_registry, has_quarantine: true); |
141 | |
142 | ThreadRegistry no_quarantine_registry(GetThreadContext<ThreadContextBase>, |
143 | kMaxRegistryThreads, |
144 | kMaxRegistryThreads, 0); |
145 | TestRegistry(registry: &no_quarantine_registry, has_quarantine: false); |
146 | } |
147 | |
148 | static const int kThreadsPerShard = 20; |
149 | static const int kNumShards = 25; |
150 | |
151 | static int num_created[kNumShards + 1]; |
152 | static int num_started[kNumShards + 1]; |
153 | static int num_joined[kNumShards + 1]; |
154 | |
155 | namespace { |
156 | |
157 | struct RunThreadArgs { |
158 | ThreadRegistry *registry; |
159 | uptr shard; // started from 1. |
160 | }; |
161 | |
162 | class TestThreadContext final : public ThreadContextBase { |
163 | public: |
164 | explicit TestThreadContext(int tid) : ThreadContextBase(tid) {} |
165 | void OnJoined(void *arg) { |
166 | uptr shard = (uptr)arg; |
167 | num_joined[shard]++; |
168 | } |
169 | void OnStarted(void *arg) { |
170 | uptr shard = (uptr)arg; |
171 | num_started[shard]++; |
172 | } |
173 | void OnCreated(void *arg) { |
174 | uptr shard = (uptr)arg; |
175 | num_created[shard]++; |
176 | } |
177 | }; |
178 | |
179 | } // namespace |
180 | |
181 | void *RunThread(void *arg) { |
182 | RunThreadArgs *args = static_cast<RunThreadArgs*>(arg); |
183 | std::vector<int> tids; |
184 | for (int i = 0; i < kThreadsPerShard; i++) |
185 | tids.push_back( |
186 | args->registry->CreateThread(0, false, 0, (void*)args->shard)); |
187 | for (int i = 0; i < kThreadsPerShard; i++) |
188 | args->registry->StartThread(tids[i], 0, ThreadType::Regular, |
189 | (void*)args->shard); |
190 | for (int i = 0; i < kThreadsPerShard; i++) |
191 | args->registry->FinishThread(tids[i]); |
192 | for (int i = 0; i < kThreadsPerShard; i++) |
193 | args->registry->JoinThread(tids[i], (void*)args->shard); |
194 | return 0; |
195 | } |
196 | |
197 | static void ThreadedTestRegistry(ThreadRegistry *registry) { |
198 | // Create and start a main thread. |
199 | EXPECT_EQ(0U, registry->CreateThread(user_id: 0, detached: true, parent_tid: -1, arg: 0)); |
200 | registry->StartThread(tid: 0, os_id: 0, thread_type: ThreadType::Regular, arg: 0); |
201 | pthread_t threads[kNumShards]; |
202 | RunThreadArgs args[kNumShards]; |
203 | for (int i = 0; i < kNumShards; i++) { |
204 | args[i].registry = registry; |
205 | args[i].shard = i + 1; |
206 | PTHREAD_CREATE(&threads[i], 0, RunThread, &args[i]); |
207 | } |
208 | for (int i = 0; i < kNumShards; i++) { |
209 | PTHREAD_JOIN(threads[i], 0); |
210 | } |
211 | // Check that each thread created/started/joined correct amount |
212 | // of "threads" in thread_registry. |
213 | EXPECT_EQ(1, num_created[0]); |
214 | EXPECT_EQ(1, num_started[0]); |
215 | EXPECT_EQ(0, num_joined[0]); |
216 | for (int i = 1; i <= kNumShards; i++) { |
217 | EXPECT_EQ(kThreadsPerShard, num_created[i]); |
218 | EXPECT_EQ(kThreadsPerShard, num_started[i]); |
219 | EXPECT_EQ(kThreadsPerShard, num_joined[i]); |
220 | } |
221 | } |
222 | |
223 | TEST(SanitizerCommon, ThreadRegistryThreadedTest) { |
224 | memset(&num_created, 0, sizeof(num_created)); |
225 | memset(&num_started, 0, sizeof(num_created)); |
226 | memset(&num_joined, 0, sizeof(num_created)); |
227 | |
228 | ThreadRegistry registry(GetThreadContext<TestThreadContext>, |
229 | kThreadsPerShard * kNumShards + 1, 10, 0); |
230 | ThreadedTestRegistry(registry: ®istry); |
231 | } |
232 | |
233 | } // namespace __sanitizer |
234 | |