Serenity Operating System
1/*
2 * Copyright (c) 2020, Sergey Bugaev <bugaevc@serenityos.org>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include <AK/Assertions.h>
8#include <LibMain/Main.h>
9#include <LibThreading/Thread.h>
10#include <errno.h>
11#include <pthread.h>
12#include <semaphore.h>
13#include <unistd.h>
14
15static ErrorOr<void> test_once()
16{
17 constexpr size_t threads_count = 10;
18
19 static Vector<int> v;
20 v.clear();
21 pthread_once_t once = PTHREAD_ONCE_INIT;
22 Vector<NonnullRefPtr<Threading::Thread>, threads_count> threads;
23
24 for (size_t i = 0; i < threads_count; i++) {
25 threads.unchecked_append(TRY(Threading::Thread::try_create([&] {
26 return pthread_once(&once, [] {
27 v.append(35);
28 sleep(1);
29 });
30 })));
31 threads.last()->start();
32 }
33 // clang-format off
34 // It wants to put [[maybe_unused]] on its own line, for some reason.
35 for (auto& thread : threads)
36 [[maybe_unused]] auto res = thread->join();
37 // clang-format on
38
39 VERIFY(v.size() == 1);
40
41 return {};
42}
43
44static ErrorOr<void> test_mutex()
45{
46 constexpr size_t threads_count = 10;
47 constexpr size_t num_times = 100;
48
49 Vector<int> v;
50 Vector<NonnullRefPtr<Threading::Thread>, threads_count> threads;
51 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
52
53 for (size_t i = 0; i < threads_count; i++) {
54 threads.unchecked_append(TRY(Threading::Thread::try_create([&] {
55 for (size_t j = 0; j < num_times; j++) {
56 pthread_mutex_lock(&mutex);
57 v.append(35);
58 sched_yield();
59 pthread_mutex_unlock(&mutex);
60 sched_yield();
61 }
62 return 0;
63 })));
64 threads.last()->start();
65 }
66 // clang-format off
67 // It wants to put [[maybe_unused]] on its own line, for some reason.
68 for (auto& thread : threads)
69 [[maybe_unused]] auto res = thread->join();
70 // clang-format on
71
72 VERIFY(v.size() == threads_count * num_times);
73 VERIFY(pthread_mutex_trylock(&mutex) == 0);
74 VERIFY(pthread_mutex_trylock(&mutex) == EBUSY);
75
76 return {};
77}
78
79static ErrorOr<void> test_semaphore_as_lock()
80{
81 constexpr size_t threads_count = 10;
82 constexpr size_t num_times = 100;
83
84 Vector<int> v;
85 Vector<NonnullRefPtr<Threading::Thread>, threads_count> threads;
86 sem_t semaphore;
87 sem_init(&semaphore, 0, 1);
88
89 for (size_t i = 0; i < threads_count; i++) {
90 threads.unchecked_append(TRY(Threading::Thread::try_create([&] {
91 for (size_t j = 0; j < num_times; j++) {
92 sem_wait(&semaphore);
93 v.append(35);
94 sched_yield();
95 sem_post(&semaphore);
96 sched_yield();
97 }
98 return 0;
99 })));
100 threads.last()->start();
101 }
102 // clang-format off
103 // It wants to put [[maybe_unused]] on its own line, for some reason.
104 for (auto& thread : threads)
105 [[maybe_unused]] auto res = thread->join();
106 // clang-format on
107
108 VERIFY(v.size() == threads_count * num_times);
109 VERIFY(sem_trywait(&semaphore) == 0);
110 VERIFY(sem_trywait(&semaphore) == EAGAIN);
111
112 return {};
113}
114
115static ErrorOr<void> test_semaphore_as_event()
116{
117 Vector<int> v;
118 sem_t semaphore;
119 sem_init(&semaphore, 0, 0);
120
121 auto reader = TRY(Threading::Thread::try_create([&] {
122 sem_wait(&semaphore);
123 VERIFY(v.size() == 1);
124 return 0;
125 }));
126 reader->start();
127
128 auto writer = TRY(Threading::Thread::try_create([&] {
129 sched_yield();
130 v.append(35);
131 sem_post(&semaphore);
132 return 0;
133 }));
134 writer->start();
135
136 [[maybe_unused]] auto r1 = reader->join();
137 [[maybe_unused]] auto r2 = writer->join();
138
139 VERIFY(sem_trywait(&semaphore) == EAGAIN);
140
141 return {};
142}
143
144static ErrorOr<void> test_semaphore_nonbinary()
145{
146 constexpr size_t num = 5;
147 constexpr size_t threads_count = 10;
148 constexpr size_t num_times = 100;
149
150 Vector<NonnullRefPtr<Threading::Thread>, threads_count> threads;
151 sem_t semaphore;
152 sem_init(&semaphore, 0, num);
153
154 Atomic<u32, AK::memory_order_relaxed> value = 0;
155 Atomic<bool, AK::memory_order_relaxed> seen_more_than_two = false;
156
157 for (size_t i = 0; i < threads_count; i++) {
158 threads.unchecked_append(TRY(Threading::Thread::try_create([&] {
159 for (size_t j = 0; j < num_times; j++) {
160 sem_wait(&semaphore);
161 u32 v = 1 + value.fetch_add(1);
162 VERIFY(v <= num);
163 if (v > 2)
164 seen_more_than_two.store(true);
165 sched_yield();
166 value.fetch_sub(1);
167 sem_post(&semaphore);
168 }
169 return 0;
170 })));
171 threads.last()->start();
172 }
173 // clang-format off
174 // It wants to put [[maybe_unused]] on its own line, for some reason.
175 for (auto& thread : threads)
176 [[maybe_unused]] auto res = thread->join();
177 // clang-format on
178
179 VERIFY(value.load() == 0);
180 VERIFY(seen_more_than_two.load());
181 for (size_t i = 0; i < num; i++) {
182 VERIFY(sem_trywait(&semaphore) == 0);
183 }
184 VERIFY(sem_trywait(&semaphore) == EAGAIN);
185
186 return {};
187}
188
189ErrorOr<int> serenity_main(Main::Arguments)
190{
191 TRY(test_once());
192 TRY(test_mutex());
193
194 TRY(test_semaphore_as_lock());
195 TRY(test_semaphore_as_event());
196 TRY(test_semaphore_nonbinary());
197
198 return 0;
199}