1 | //===-- Tests for mtx_t operations ----------------------------------------===// |
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 "src/threads/mtx_destroy.h" |
10 | #include "src/threads/mtx_init.h" |
11 | #include "src/threads/mtx_lock.h" |
12 | #include "src/threads/mtx_unlock.h" |
13 | #include "src/threads/thrd_create.h" |
14 | #include "src/threads/thrd_join.h" |
15 | |
16 | #include "test/IntegrationTest/test.h" |
17 | |
18 | #include <threads.h> |
19 | |
20 | constexpr int START = 0; |
21 | constexpr int MAX = 10000; |
22 | |
23 | mtx_t mutex; |
24 | static int shared_int = START; |
25 | |
26 | int counter(void *arg) { |
27 | int last_count = START; |
28 | while (true) { |
29 | LIBC_NAMESPACE::mtx_lock(mutex: &mutex); |
30 | if (shared_int == last_count + 1) { |
31 | shared_int++; |
32 | last_count = shared_int; |
33 | } |
34 | LIBC_NAMESPACE::mtx_unlock(mutex: &mutex); |
35 | if (last_count >= MAX) |
36 | break; |
37 | } |
38 | return 0; |
39 | } |
40 | |
41 | void relay_counter() { |
42 | ASSERT_EQ(LIBC_NAMESPACE::mtx_init(&mutex, mtx_plain), |
43 | static_cast<int>(thrd_success)); |
44 | |
45 | // The idea of this test is that two competing threads will update |
46 | // a counter only if the other thread has updated it. |
47 | thrd_t thread; |
48 | LIBC_NAMESPACE::thrd_create(thread: &thread, func: counter, arg: nullptr); |
49 | |
50 | int last_count = START; |
51 | while (true) { |
52 | ASSERT_EQ(LIBC_NAMESPACE::mtx_lock(&mutex), static_cast<int>(thrd_success)); |
53 | if (shared_int == START) { |
54 | ++shared_int; |
55 | last_count = shared_int; |
56 | } else if (shared_int != last_count) { |
57 | ASSERT_EQ(shared_int, last_count + 1); |
58 | ++shared_int; |
59 | last_count = shared_int; |
60 | } |
61 | ASSERT_EQ(LIBC_NAMESPACE::mtx_unlock(&mutex), |
62 | static_cast<int>(thrd_success)); |
63 | if (last_count > MAX) |
64 | break; |
65 | } |
66 | |
67 | int retval = 123; |
68 | LIBC_NAMESPACE::thrd_join(thread, retval: &retval); |
69 | ASSERT_EQ(retval, 0); |
70 | |
71 | LIBC_NAMESPACE::mtx_destroy(mutex: &mutex); |
72 | } |
73 | |
74 | mtx_t start_lock, step_lock; |
75 | bool started, step; |
76 | |
77 | int stepper(void *arg) { |
78 | LIBC_NAMESPACE::mtx_lock(mutex: &start_lock); |
79 | started = true; |
80 | LIBC_NAMESPACE::mtx_unlock(mutex: &start_lock); |
81 | |
82 | LIBC_NAMESPACE::mtx_lock(mutex: &step_lock); |
83 | step = true; |
84 | LIBC_NAMESPACE::mtx_unlock(mutex: &step_lock); |
85 | return 0; |
86 | } |
87 | |
88 | void wait_and_step() { |
89 | ASSERT_EQ(LIBC_NAMESPACE::mtx_init(&start_lock, mtx_plain), |
90 | static_cast<int>(thrd_success)); |
91 | ASSERT_EQ(LIBC_NAMESPACE::mtx_init(&step_lock, mtx_plain), |
92 | static_cast<int>(thrd_success)); |
93 | |
94 | // In this test, we start a new thread but block it before it can make a |
95 | // step. Once we ensure that the thread is blocked, we unblock it. |
96 | // After unblocking, we then verify that the thread was indeed unblocked. |
97 | step = false; |
98 | started = false; |
99 | ASSERT_EQ(LIBC_NAMESPACE::mtx_lock(&step_lock), |
100 | static_cast<int>(thrd_success)); |
101 | |
102 | thrd_t thread; |
103 | LIBC_NAMESPACE::thrd_create(thread: &thread, func: stepper, arg: nullptr); |
104 | |
105 | while (true) { |
106 | // Make sure the thread actually started. |
107 | ASSERT_EQ(LIBC_NAMESPACE::mtx_lock(&start_lock), |
108 | static_cast<int>(thrd_success)); |
109 | bool s = started; |
110 | ASSERT_EQ(LIBC_NAMESPACE::mtx_unlock(&start_lock), |
111 | static_cast<int>(thrd_success)); |
112 | if (s) |
113 | break; |
114 | } |
115 | |
116 | // Since |step_lock| is still locked, |step| should be false. |
117 | ASSERT_FALSE(step); |
118 | |
119 | // Unlock the step lock and wait until the step is made. |
120 | ASSERT_EQ(LIBC_NAMESPACE::mtx_unlock(&step_lock), |
121 | static_cast<int>(thrd_success)); |
122 | |
123 | while (true) { |
124 | ASSERT_EQ(LIBC_NAMESPACE::mtx_lock(&step_lock), |
125 | static_cast<int>(thrd_success)); |
126 | bool current_step_value = step; |
127 | ASSERT_EQ(LIBC_NAMESPACE::mtx_unlock(&step_lock), |
128 | static_cast<int>(thrd_success)); |
129 | if (current_step_value) |
130 | break; |
131 | } |
132 | |
133 | int retval = 123; |
134 | LIBC_NAMESPACE::thrd_join(thread, retval: &retval); |
135 | ASSERT_EQ(retval, 0); |
136 | |
137 | LIBC_NAMESPACE::mtx_destroy(mutex: &start_lock); |
138 | LIBC_NAMESPACE::mtx_destroy(mutex: &step_lock); |
139 | } |
140 | |
141 | static constexpr int THREAD_COUNT = 10; |
142 | static mtx_t multiple_waiter_lock; |
143 | static mtx_t counter_lock; |
144 | static int wait_count = 0; |
145 | |
146 | int waiter_func(void *) { |
147 | LIBC_NAMESPACE::mtx_lock(mutex: &counter_lock); |
148 | ++wait_count; |
149 | LIBC_NAMESPACE::mtx_unlock(mutex: &counter_lock); |
150 | |
151 | // Block on the waiter lock until the main |
152 | // thread unblocks. |
153 | LIBC_NAMESPACE::mtx_lock(mutex: &multiple_waiter_lock); |
154 | LIBC_NAMESPACE::mtx_unlock(mutex: &multiple_waiter_lock); |
155 | |
156 | LIBC_NAMESPACE::mtx_lock(mutex: &counter_lock); |
157 | --wait_count; |
158 | LIBC_NAMESPACE::mtx_unlock(mutex: &counter_lock); |
159 | |
160 | return 0; |
161 | } |
162 | |
163 | void multiple_waiters() { |
164 | LIBC_NAMESPACE::mtx_init(mutex: &multiple_waiter_lock, type: mtx_plain); |
165 | LIBC_NAMESPACE::mtx_init(mutex: &counter_lock, type: mtx_plain); |
166 | |
167 | LIBC_NAMESPACE::mtx_lock(mutex: &multiple_waiter_lock); |
168 | thrd_t waiters[THREAD_COUNT]; |
169 | for (int i = 0; i < THREAD_COUNT; ++i) { |
170 | LIBC_NAMESPACE::thrd_create(thread: waiters + i, func: waiter_func, arg: nullptr); |
171 | } |
172 | |
173 | // Spin until the counter is incremented to the desired |
174 | // value. |
175 | while (true) { |
176 | LIBC_NAMESPACE::mtx_lock(mutex: &counter_lock); |
177 | if (wait_count == THREAD_COUNT) { |
178 | LIBC_NAMESPACE::mtx_unlock(mutex: &counter_lock); |
179 | break; |
180 | } |
181 | LIBC_NAMESPACE::mtx_unlock(mutex: &counter_lock); |
182 | } |
183 | |
184 | LIBC_NAMESPACE::mtx_unlock(mutex: &multiple_waiter_lock); |
185 | |
186 | int retval; |
187 | for (int i = 0; i < THREAD_COUNT; ++i) { |
188 | LIBC_NAMESPACE::thrd_join(thread: waiters[i], retval: &retval); |
189 | } |
190 | |
191 | ASSERT_EQ(wait_count, 0); |
192 | |
193 | LIBC_NAMESPACE::mtx_destroy(mutex: &multiple_waiter_lock); |
194 | LIBC_NAMESPACE::mtx_destroy(mutex: &counter_lock); |
195 | } |
196 | |
197 | TEST_MAIN() { |
198 | relay_counter(); |
199 | wait_and_step(); |
200 | multiple_waiters(); |
201 | return 0; |
202 | } |
203 | |