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
20constexpr int START = 0;
21constexpr int MAX = 10000;
22
23mtx_t mutex;
24static int shared_int = START;
25
26int 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
41void 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
74mtx_t start_lock, step_lock;
75bool started, step;
76
77int 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
88void 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
141static constexpr int THREAD_COUNT = 10;
142static mtx_t multiple_waiter_lock;
143static mtx_t counter_lock;
144static int wait_count = 0;
145
146int 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
163void 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
197TEST_MAIN() {
198 relay_counter();
199 wait_and_step();
200 multiple_waiters();
201 return 0;
202}
203

source code of libc/test/integration/src/threads/mtx_test.cpp