1 | //===-- Tests for pthread_rwlock ------------------------------------------===// |
---|---|
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 | #include "hdr/errno_macros.h" |
10 | #include "hdr/time_macros.h" |
11 | #include "src/__support/CPP/atomic.h" |
12 | #include "src/__support/CPP/new.h" |
13 | #include "src/__support/OSUtil/syscall.h" |
14 | #include "src/__support/macros/config.h" |
15 | #include "src/__support/threads/linux/raw_mutex.h" |
16 | #include "src/__support/threads/linux/rwlock.h" |
17 | #include "src/__support/threads/sleep.h" |
18 | #include "src/pthread/pthread_create.h" |
19 | #include "src/pthread/pthread_join.h" |
20 | #include "src/pthread/pthread_rwlock_clockrdlock.h" |
21 | #include "src/pthread/pthread_rwlock_clockwrlock.h" |
22 | #include "src/pthread/pthread_rwlock_destroy.h" |
23 | #include "src/pthread/pthread_rwlock_init.h" |
24 | #include "src/pthread/pthread_rwlock_rdlock.h" |
25 | #include "src/pthread/pthread_rwlock_timedrdlock.h" |
26 | #include "src/pthread/pthread_rwlock_timedwrlock.h" |
27 | #include "src/pthread/pthread_rwlock_tryrdlock.h" |
28 | #include "src/pthread/pthread_rwlock_trywrlock.h" |
29 | #include "src/pthread/pthread_rwlock_unlock.h" |
30 | #include "src/pthread/pthread_rwlock_wrlock.h" |
31 | #include "src/pthread/pthread_rwlockattr_destroy.h" |
32 | #include "src/pthread/pthread_rwlockattr_init.h" |
33 | #include "src/pthread/pthread_rwlockattr_setkind_np.h" |
34 | #include "src/pthread/pthread_rwlockattr_setpshared.h" |
35 | #include "src/stdio/printf.h" |
36 | #include "src/stdlib/exit.h" |
37 | #include "src/stdlib/getenv.h" |
38 | #include "src/sys/mman/mmap.h" |
39 | #include "src/sys/mman/munmap.h" |
40 | #include "src/sys/random/getrandom.h" |
41 | #include "src/sys/wait/waitpid.h" |
42 | #include "src/time/clock_gettime.h" |
43 | #include "src/unistd/fork.h" |
44 | #include "test/IntegrationTest/test.h" |
45 | #include <pthread.h> |
46 | |
47 | namespace LIBC_NAMESPACE_DECL { |
48 | namespace rwlock { |
49 | class RwLockTester { |
50 | public: |
51 | static constexpr int full_reader_state() { |
52 | return (~0) & (~RwState::PENDING_MASK) & (~RwState::ACTIVE_WRITER_BIT); |
53 | } |
54 | }; |
55 | } // namespace rwlock |
56 | } // namespace LIBC_NAMESPACE_DECL |
57 | |
58 | static void smoke_test() { |
59 | pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER; |
60 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_init(&rwlock, nullptr), 0); |
61 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_rdlock(&rwlock), 0); |
62 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_tryrdlock(&rwlock), 0); |
63 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_trywrlock(&rwlock), EBUSY); |
64 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0); |
65 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0); |
66 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_wrlock(&rwlock), 0); |
67 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_rdlock(&rwlock), EDEADLK); |
68 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_wrlock(&rwlock), EDEADLK); |
69 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_tryrdlock(&rwlock), EBUSY); |
70 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_trywrlock(&rwlock), EBUSY); |
71 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0); |
72 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_destroy(&rwlock), 0); |
73 | } |
74 | |
75 | static void deadlock_detection_test() { |
76 | pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER; |
77 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_init(&rwlock, nullptr), 0); |
78 | // We only detect RAW, WAW deadlocks. |
79 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_wrlock(&rwlock), 0); |
80 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_wrlock(&rwlock), EDEADLK); |
81 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0); |
82 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_destroy(&rwlock), 0); |
83 | } |
84 | |
85 | static void try_lock_test() { |
86 | pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER; |
87 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_init(&rwlock, nullptr), 0); |
88 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_wrlock(&rwlock), 0); |
89 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_trywrlock(&rwlock), EBUSY); |
90 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_tryrdlock(&rwlock), EBUSY); |
91 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0); |
92 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_tryrdlock(&rwlock), 0); |
93 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_rdlock(&rwlock), 0); |
94 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_tryrdlock(&rwlock), 0); |
95 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_trywrlock(&rwlock), EBUSY); |
96 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0); |
97 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0); |
98 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0); |
99 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_destroy(&rwlock), 0); |
100 | } |
101 | |
102 | static void destroy_before_unlock_test() { |
103 | pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER; |
104 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_init(&rwlock, nullptr), 0); |
105 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_wrlock(&rwlock), 0); |
106 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_destroy(&rwlock), EBUSY); |
107 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0); |
108 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_destroy(&rwlock), 0); |
109 | } |
110 | |
111 | static void nullptr_test() { |
112 | timespec ts = {}; |
113 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_rdlock(nullptr), EINVAL); |
114 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_wrlock(nullptr), EINVAL); |
115 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_timedrdlock(nullptr, &ts), EINVAL); |
116 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_timedwrlock(nullptr, &ts), EINVAL); |
117 | ASSERT_EQ( |
118 | LIBC_NAMESPACE::pthread_rwlock_clockrdlock(nullptr, CLOCK_MONOTONIC, &ts), |
119 | EINVAL); |
120 | ASSERT_EQ( |
121 | LIBC_NAMESPACE::pthread_rwlock_clockwrlock(nullptr, CLOCK_MONOTONIC, &ts), |
122 | EINVAL); |
123 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_tryrdlock(nullptr), EINVAL); |
124 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_trywrlock(nullptr), EINVAL); |
125 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(nullptr), EINVAL); |
126 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_destroy(nullptr), EINVAL); |
127 | } |
128 | |
129 | // If you are a user reading this code, please do not do something like this. |
130 | // We manually modify the internal state of the rwlock to test high reader |
131 | // counts. |
132 | static void high_reader_count_test() { |
133 | pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER; |
134 | rwlock.__state = LIBC_NAMESPACE::rwlock::RwLockTester::full_reader_state(); |
135 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_rdlock(&rwlock), EAGAIN); |
136 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_tryrdlock(&rwlock), EAGAIN); |
137 | // allocate 4 reader slots. |
138 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0); |
139 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0); |
140 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0); |
141 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0); |
142 | |
143 | pthread_t threads[20]; |
144 | for (auto &i : threads) |
145 | ASSERT_EQ(LIBC_NAMESPACE::pthread_create( |
146 | &i, nullptr, |
147 | [](void *arg) -> void * { |
148 | pthread_rwlock_t *rwlock = |
149 | reinterpret_cast<pthread_rwlock_t *>(arg); |
150 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_trywrlock(rwlock), |
151 | EBUSY); |
152 | while (LIBC_NAMESPACE::pthread_rwlock_rdlock(rwlock) == |
153 | EAGAIN) |
154 | LIBC_NAMESPACE::sleep_briefly(); |
155 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(rwlock), 0); |
156 | return nullptr; |
157 | }, |
158 | &rwlock), |
159 | 0); |
160 | |
161 | for (auto &i : threads) |
162 | ASSERT_EQ(LIBC_NAMESPACE::pthread_join(i, nullptr), 0); |
163 | } |
164 | |
165 | static void unusual_timespec_test() { |
166 | pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER; |
167 | timespec ts = {.tv_sec: 0, .tv_nsec: -1}; |
168 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_timedrdlock(&rwlock, &ts), EINVAL); |
169 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_timedwrlock(&rwlock, &ts), EINVAL); |
170 | ASSERT_EQ( |
171 | LIBC_NAMESPACE::pthread_rwlock_clockrdlock(&rwlock, CLOCK_MONOTONIC, &ts), |
172 | EINVAL); |
173 | ASSERT_EQ( |
174 | LIBC_NAMESPACE::pthread_rwlock_clockwrlock(&rwlock, CLOCK_MONOTONIC, &ts), |
175 | EINVAL); |
176 | ts.tv_nsec = 1'000'000'000; |
177 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_timedrdlock(&rwlock, &ts), EINVAL); |
178 | ASSERT_EQ( |
179 | LIBC_NAMESPACE::pthread_rwlock_clockrdlock(&rwlock, CLOCK_MONOTONIC, &ts), |
180 | EINVAL); |
181 | ASSERT_EQ( |
182 | LIBC_NAMESPACE::pthread_rwlock_clockwrlock(&rwlock, CLOCK_MONOTONIC, &ts), |
183 | EINVAL); |
184 | ts.tv_nsec += 1; |
185 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_timedwrlock(&rwlock, &ts), EINVAL); |
186 | ASSERT_EQ( |
187 | LIBC_NAMESPACE::pthread_rwlock_clockrdlock(&rwlock, CLOCK_MONOTONIC, &ts), |
188 | EINVAL); |
189 | ASSERT_EQ( |
190 | LIBC_NAMESPACE::pthread_rwlock_clockwrlock(&rwlock, CLOCK_MONOTONIC, &ts), |
191 | EINVAL); |
192 | ts.tv_nsec = 0; |
193 | ts.tv_sec = -1; |
194 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_timedrdlock(&rwlock, &ts), |
195 | ETIMEDOUT); |
196 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_timedwrlock(&rwlock, &ts), |
197 | ETIMEDOUT); |
198 | ASSERT_EQ( |
199 | LIBC_NAMESPACE::pthread_rwlock_clockrdlock(&rwlock, CLOCK_MONOTONIC, &ts), |
200 | ETIMEDOUT); |
201 | ASSERT_EQ( |
202 | LIBC_NAMESPACE::pthread_rwlock_clockwrlock(&rwlock, CLOCK_MONOTONIC, &ts), |
203 | ETIMEDOUT); |
204 | } |
205 | |
206 | static void timedlock_with_deadlock_test() { |
207 | pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER; |
208 | timespec ts{}; |
209 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_rdlock(&rwlock), 0); |
210 | LIBC_NAMESPACE::clock_gettime(CLOCK_REALTIME, &ts); |
211 | ts.tv_nsec += 50'000; |
212 | if (ts.tv_nsec >= 1'000'000'000) { |
213 | ts.tv_nsec -= 1'000'000'000; |
214 | ts.tv_sec += 1; |
215 | } |
216 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_timedwrlock(&rwlock, &ts), |
217 | ETIMEDOUT); |
218 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_timedrdlock(&rwlock, &ts), 0); |
219 | ASSERT_EQ( |
220 | LIBC_NAMESPACE::pthread_rwlock_clockwrlock(&rwlock, CLOCK_REALTIME, &ts), |
221 | ETIMEDOUT); |
222 | ASSERT_EQ( |
223 | LIBC_NAMESPACE::pthread_rwlock_clockrdlock(&rwlock, CLOCK_REALTIME, &ts), |
224 | 0); |
225 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0); |
226 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0); |
227 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0); |
228 | // notice that ts is already expired, but the following should still succeed. |
229 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_tryrdlock(&rwlock), 0); |
230 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0); |
231 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_trywrlock(&rwlock), 0); |
232 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0); |
233 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_rdlock(&rwlock), 0); |
234 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0); |
235 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_wrlock(&rwlock), 0); |
236 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0); |
237 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_destroy(&rwlock), 0); |
238 | } |
239 | |
240 | static void attributed_initialization_test() { |
241 | pthread_rwlockattr_t attr{}; |
242 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlockattr_init(&attr), 0); |
243 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlockattr_setkind_np( |
244 | &attr, PTHREAD_RWLOCK_PREFER_READER_NP), |
245 | 0); |
246 | { |
247 | pthread_rwlock_t rwlock{}; |
248 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_init(&rwlock, &attr), 0); |
249 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_destroy(&rwlock), 0); |
250 | } |
251 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlockattr_setkind_np( |
252 | &attr, PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP), |
253 | 0); |
254 | { |
255 | pthread_rwlock_t rwlock{}; |
256 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_init(&rwlock, &attr), 0); |
257 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_destroy(&rwlock), 0); |
258 | } |
259 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlockattr_setkind_np( |
260 | &attr, PTHREAD_RWLOCK_PREFER_WRITER_NP), |
261 | 0); |
262 | { |
263 | pthread_rwlock_t rwlock{}; |
264 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_init(&rwlock, &attr), EINVAL); |
265 | } |
266 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlockattr_setkind_np( |
267 | &attr, PTHREAD_RWLOCK_PREFER_READER_NP), |
268 | 0); |
269 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlockattr_setpshared( |
270 | &attr, PTHREAD_PROCESS_PRIVATE), |
271 | 0); |
272 | { |
273 | pthread_rwlock_t rwlock{}; |
274 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_init(&rwlock, &attr), 0); |
275 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_destroy(&rwlock), 0); |
276 | } |
277 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlockattr_setpshared( |
278 | &attr, PTHREAD_PROCESS_SHARED), |
279 | 0); |
280 | { |
281 | pthread_rwlock_t rwlock{}; |
282 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_init(&rwlock, &attr), 0); |
283 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_destroy(&rwlock), 0); |
284 | } |
285 | attr.pref = -1; |
286 | { |
287 | pthread_rwlock_t rwlock{}; |
288 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_init(&rwlock, &attr), EINVAL); |
289 | } |
290 | attr.pref = PTHREAD_RWLOCK_PREFER_READER_NP; |
291 | attr.pshared = -1; |
292 | { |
293 | pthread_rwlock_t rwlock{}; |
294 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_init(&rwlock, &attr), EINVAL); |
295 | } |
296 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlockattr_destroy(&attr), 0); |
297 | } |
298 | |
299 | struct SharedData { |
300 | pthread_rwlock_t lock; |
301 | int data; |
302 | LIBC_NAMESPACE::cpp::Atomic<int> reader_count; |
303 | bool writer_flag; |
304 | LIBC_NAMESPACE::cpp::Atomic<int> total_writer_count; |
305 | }; |
306 | |
307 | enum class Operation : int { |
308 | READ = 0, |
309 | WRITE = 1, |
310 | TIMED_READ = 2, |
311 | TIMED_WRITE = 3, |
312 | CLOCK_READ = 4, |
313 | CLOCK_WRITE = 5, |
314 | TRY_READ = 6, |
315 | TRY_WRITE = 7, |
316 | COUNT = 8 |
317 | }; |
318 | |
319 | LIBC_NAMESPACE::RawMutex *io_mutex; |
320 | struct ThreadGuard { |
321 | Operation record[64]{}; |
322 | size_t cursor = 0; |
323 | void push(Operation op) { record[cursor++] = op; } |
324 | ~ThreadGuard() { |
325 | if (!LIBC_NAMESPACE::getenv("LIBC_PTHREAD_RWLOCK_TEST_VERBOSE")) |
326 | return; |
327 | pid_t pid = static_cast<pid_t>(LIBC_NAMESPACE::syscall_impl(SYS_getpid)); |
328 | pid_t tid = static_cast<pid_t>(LIBC_NAMESPACE::syscall_impl(SYS_gettid)); |
329 | io_mutex->lock(LIBC_NAMESPACE::cpp::nullopt, true); |
330 | LIBC_NAMESPACE::printf("process %d thread %d: ", pid, tid); |
331 | for (size_t i = 0; i < cursor; ++i) |
332 | LIBC_NAMESPACE::printf("%d ", static_cast<int>(record[i])); |
333 | LIBC_NAMESPACE::printf("\n"); |
334 | io_mutex->unlock(true); |
335 | } |
336 | }; |
337 | |
338 | static void randomized_thread_operation(SharedData *data, ThreadGuard &guard) { |
339 | int buffer; |
340 | // We cannot reason about thread order anyway, let's go wild and randomize it |
341 | // directly using getrandom. |
342 | LIBC_NAMESPACE::getrandom(&buffer, sizeof(buffer), 0); |
343 | constexpr int TOTAL = static_cast<int>(Operation::COUNT); |
344 | Operation op = static_cast<Operation>(((buffer % TOTAL) + TOTAL) % TOTAL); |
345 | guard.push(op); |
346 | auto read_ops = [data]() { |
347 | ASSERT_FALSE(data->writer_flag); |
348 | data->reader_count.fetch_add(1, LIBC_NAMESPACE::cpp::MemoryOrder::RELAXED); |
349 | for (int i = 0; i < 10; ++i) |
350 | LIBC_NAMESPACE::sleep_briefly(); |
351 | data->reader_count.fetch_sub(1, LIBC_NAMESPACE::cpp::MemoryOrder::RELAXED); |
352 | }; |
353 | auto write_ops = [data]() { |
354 | ASSERT_FALSE(data->writer_flag); |
355 | data->data += 1; |
356 | data->writer_flag = true; |
357 | for (int i = 0; i < 10; ++i) |
358 | LIBC_NAMESPACE::sleep_briefly(); |
359 | ASSERT_EQ(data->reader_count, 0); |
360 | data->writer_flag = false; |
361 | data->total_writer_count.fetch_add(1); |
362 | }; |
363 | auto get_ts = []() { |
364 | timespec ts{}; |
365 | LIBC_NAMESPACE::clock_gettime(CLOCK_REALTIME, &ts); |
366 | ts.tv_nsec += 5'000; |
367 | if (ts.tv_nsec >= 1'000'000'000) { |
368 | ts.tv_nsec -= 1'000'000'000; |
369 | ts.tv_sec += 1; |
370 | } |
371 | return ts; |
372 | }; |
373 | switch (op) { |
374 | case Operation::READ: { |
375 | LIBC_NAMESPACE::pthread_rwlock_rdlock(&data->lock); |
376 | read_ops(); |
377 | LIBC_NAMESPACE::pthread_rwlock_unlock(&data->lock); |
378 | break; |
379 | } |
380 | case Operation::WRITE: { |
381 | LIBC_NAMESPACE::pthread_rwlock_wrlock(&data->lock); |
382 | write_ops(); |
383 | LIBC_NAMESPACE::pthread_rwlock_unlock(&data->lock); |
384 | break; |
385 | } |
386 | case Operation::TIMED_READ: { |
387 | timespec ts = get_ts(); |
388 | if (LIBC_NAMESPACE::pthread_rwlock_timedrdlock(&data->lock, &ts) == 0) { |
389 | read_ops(); |
390 | LIBC_NAMESPACE::pthread_rwlock_unlock(&data->lock); |
391 | } |
392 | break; |
393 | } |
394 | case Operation::TIMED_WRITE: { |
395 | timespec ts = get_ts(); |
396 | if (LIBC_NAMESPACE::pthread_rwlock_timedwrlock(&data->lock, &ts) == 0) { |
397 | write_ops(); |
398 | LIBC_NAMESPACE::pthread_rwlock_unlock(&data->lock); |
399 | } |
400 | break; |
401 | } |
402 | case Operation::CLOCK_READ: { |
403 | timespec ts = get_ts(); |
404 | if (LIBC_NAMESPACE::pthread_rwlock_clockrdlock(&data->lock, CLOCK_MONOTONIC, |
405 | &ts) == 0) { |
406 | read_ops(); |
407 | LIBC_NAMESPACE::pthread_rwlock_unlock(&data->lock); |
408 | } |
409 | break; |
410 | } |
411 | case Operation::CLOCK_WRITE: { |
412 | timespec ts = get_ts(); |
413 | if (LIBC_NAMESPACE::pthread_rwlock_clockwrlock(&data->lock, CLOCK_MONOTONIC, |
414 | &ts) == 0) { |
415 | write_ops(); |
416 | LIBC_NAMESPACE::pthread_rwlock_unlock(&data->lock); |
417 | } |
418 | break; |
419 | } |
420 | case Operation::TRY_READ: { |
421 | if (LIBC_NAMESPACE::pthread_rwlock_tryrdlock(&data->lock) == 0) { |
422 | read_ops(); |
423 | LIBC_NAMESPACE::pthread_rwlock_unlock(&data->lock); |
424 | } |
425 | break; |
426 | } |
427 | case Operation::TRY_WRITE: { |
428 | if (LIBC_NAMESPACE::pthread_rwlock_trywrlock(&data->lock) == 0) { |
429 | write_ops(); |
430 | LIBC_NAMESPACE::pthread_rwlock_unlock(&data->lock); |
431 | } |
432 | break; |
433 | } |
434 | case Operation::COUNT: |
435 | __builtin_trap(); |
436 | } |
437 | } |
438 | |
439 | static void |
440 | randomized_process_operation(SharedData &data, |
441 | LIBC_NAMESPACE::cpp::Atomic<int> &finish_count, |
442 | int expected_count) { |
443 | pthread_t threads[32]; |
444 | for (auto &i : threads) |
445 | ASSERT_EQ(LIBC_NAMESPACE::pthread_create( |
446 | &i, nullptr, |
447 | [](void *arg) -> void * { |
448 | ThreadGuard guard{}; |
449 | for (int i = 0; i < 64; ++i) |
450 | randomized_thread_operation( |
451 | reinterpret_cast<SharedData *>(arg), guard); |
452 | return nullptr; |
453 | }, |
454 | &data), |
455 | 0); |
456 | |
457 | for (auto &i : threads) |
458 | ASSERT_EQ(LIBC_NAMESPACE::pthread_join(i, nullptr), 0); |
459 | |
460 | finish_count.fetch_add(1); |
461 | while (finish_count.load() != expected_count) |
462 | LIBC_NAMESPACE::sleep_briefly(); |
463 | |
464 | ASSERT_EQ(data.total_writer_count.load(), data.data); |
465 | ASSERT_FALSE(data.writer_flag); |
466 | ASSERT_EQ(data.reader_count, 0); |
467 | } |
468 | |
469 | static void single_process_test(int preference) { |
470 | SharedData data{}; |
471 | data.data = 0; |
472 | data.reader_count = 0; |
473 | data.writer_flag = false; |
474 | data.total_writer_count.store(0); |
475 | pthread_rwlockattr_t attr{}; |
476 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlockattr_init(&attr), 0); |
477 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlockattr_setkind_np(&attr, preference), |
478 | 0); |
479 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_init(&data.lock, nullptr), 0); |
480 | LIBC_NAMESPACE::cpp::Atomic<int> finish_count{0}; |
481 | randomized_process_operation(data, finish_count, 1); |
482 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_destroy(&data.lock), 0); |
483 | } |
484 | |
485 | static void multiple_process_test(int preference) { |
486 | struct PShared { |
487 | SharedData data; |
488 | LIBC_NAMESPACE::cpp::Atomic<int> finish_count; |
489 | }; |
490 | PShared *shared_data = reinterpret_cast<PShared *>( |
491 | LIBC_NAMESPACE::mmap(nullptr, sizeof(PShared), PROT_READ | PROT_WRITE, |
492 | MAP_SHARED | MAP_ANONYMOUS, -1, 0)); |
493 | shared_data->data.data = 0; |
494 | shared_data->data.reader_count = 0; |
495 | shared_data->data.writer_flag = false; |
496 | shared_data->data.total_writer_count.store(0); |
497 | shared_data->finish_count.store(0); |
498 | pthread_rwlockattr_t attr{}; |
499 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlockattr_init(&attr), 0); |
500 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlockattr_setkind_np(&attr, preference), |
501 | 0); |
502 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlockattr_setpshared( |
503 | &attr, PTHREAD_PROCESS_SHARED), |
504 | 0); |
505 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_init(&shared_data->data.lock, &attr), |
506 | 0); |
507 | int pid = LIBC_NAMESPACE::fork(); |
508 | randomized_process_operation(shared_data->data, shared_data->finish_count, 2); |
509 | if (pid == 0) |
510 | LIBC_NAMESPACE::exit(0); |
511 | else { |
512 | int status; |
513 | LIBC_NAMESPACE::waitpid(pid, &status, 0); |
514 | ASSERT_EQ(status, 0); |
515 | } |
516 | ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_destroy(&shared_data->data.lock), 0); |
517 | LIBC_NAMESPACE::munmap(shared_data, sizeof(PShared)); |
518 | } |
519 | |
520 | TEST_MAIN() { |
521 | io_mutex = new (LIBC_NAMESPACE::mmap( |
522 | nullptr, sizeof(LIBC_NAMESPACE::RawMutex), PROT_READ | PROT_WRITE, |
523 | MAP_ANONYMOUS | MAP_SHARED, -1, 0)) LIBC_NAMESPACE::RawMutex(); |
524 | smoke_test(); |
525 | deadlock_detection_test(); |
526 | try_lock_test(); |
527 | destroy_before_unlock_test(); |
528 | nullptr_test(); |
529 | high_reader_count_test(); |
530 | unusual_timespec_test(); |
531 | timedlock_with_deadlock_test(); |
532 | attributed_initialization_test(); |
533 | single_process_test(preference: PTHREAD_RWLOCK_PREFER_READER_NP); |
534 | single_process_test(preference: PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP); |
535 | multiple_process_test(preference: PTHREAD_RWLOCK_PREFER_READER_NP); |
536 | multiple_process_test(preference: PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP); |
537 | io_mutex->~RawMutex(); |
538 | LIBC_NAMESPACE::munmap(io_mutex, sizeof(LIBC_NAMESPACE::RawMutex)); |
539 | return 0; |
540 | } |
541 |
Definitions
- RwLockTester
- full_reader_state
- smoke_test
- deadlock_detection_test
- try_lock_test
- destroy_before_unlock_test
- nullptr_test
- high_reader_count_test
- unusual_timespec_test
- timedlock_with_deadlock_test
- attributed_initialization_test
- SharedData
- Operation
- io_mutex
- ThreadGuard
- push
- ~ThreadGuard
- randomized_thread_operation
- randomized_process_operation
- single_process_test
- multiple_process_test
Update your C++ knowledge – Modern C++11/14/17 Training
Find out more