1#include "sync/tests/test_internal.h"
2
3static struct rwlock rw_two_writers = RWLOCK_INIT(THREAD_PRIO_CLASS_TIMESHARE);
4static atomic_bool rw_two_done = false;
5
6static void rw_two_writer_thread(void *arg) {
7 cc_ignore(arg);
8 rw_lock(&rw_two_writers, RWLOCK_WRITE);
9 rw_unlock(&rw_two_writers);
10
11 atomic_store(&rw_two_done, true);
12}
13
14TEST_DEFINE_INTEGRATION(rwlock, two_writers) {
15 atomic_store(&rw_two_done, false);
16 rw_lock(&rw_two_writers, RWLOCK_WRITE);
17
18 struct thread *w = thread_spawn("rw_two_writer", rw_two_writer_thread,
19 .on_cpu = 0, .joinable = true);
20
21 scheduler_yield(); // let second writer block
22
23 rw_unlock(&rw_two_writers);
24
25 TEST_ASSERT_NONNULL(w);
26 thread_join(t: w);
27 TEST_ASSERT(atomic_load(&rw_two_done));
28
29 return TEST_SUCCESS;
30}
31
32#define RWLOCK_READER_MAX 64
33#define RWLOCK_READER_COUNT_LOOPS 500
34#define RWLOCK_READER_PRINT_INTERVAL 10000
35
36static struct rwlock rw_readers = RWLOCK_INIT(THREAD_PRIO_CLASS_TIMESHARE);
37static atomic_uint32_t rw_readers_left = 0;
38
39static void rw_reader_worker(void *arg) {
40 cc_ignore(arg);
41 time_ms_t last_print = time_get_ms();
42 for (size_t i = 0; i < RWLOCK_READER_COUNT_LOOPS; i++) {
43 rw_lock(&rw_readers, RWLOCK_READ);
44 scheduler_yield();
45 rw_unlock(&rw_readers);
46 time_ms_t now = time_get_ms();
47 if ((now - last_print) > RWLOCK_READER_PRINT_INTERVAL) {
48 test_info("RWlock reader %s on iteration %zu",
49 thread_get_current()->name, i);
50 }
51 last_print = now;
52 }
53
54 atomic_dec(&rw_readers_left);
55}
56
57TEST_DEFINE_INTEGRATION(rwlock, mt_many_readers, TEST_INTENSITY(4, 20, 64)) {
58 size_t num_readers = ctx->intensity_val ? ctx->intensity_val : 20;
59 if (num_readers > RWLOCK_READER_MAX)
60 num_readers = RWLOCK_READER_MAX;
61
62 atomic_store(&rw_readers_left, (uint32_t) num_readers);
63 struct thread *readers[RWLOCK_READER_MAX];
64
65 enum irql irql = irql_raise(new_level: IRQL_DISPATCH_LEVEL);
66 for (size_t i = 0; i < num_readers; i++)
67 readers[i] =
68 thread_spawn(("rr_%zu", i), rw_reader_worker, .joinable = true);
69 irql_lower(old_level: irql);
70
71 for (size_t i = 0; i < num_readers; i++) {
72 if (readers[i])
73 thread_join(t: readers[i]);
74 }
75
76 /* a failed spawn leaves the counter short, which this catches */
77 TEST_ASSERT_EQ(atomic_load(&rw_readers_left), 0);
78
79 return TEST_SUCCESS;
80}
81
82#define RWLOCK_MIXED_THREADS_MAX 64
83#define RWLOCK_MIXED_LOOPS 500
84struct thread *mixed_threads[RWLOCK_MIXED_THREADS_MAX];
85static struct rwlock rw_mixed = RWLOCK_INIT(THREAD_PRIO_CLASS_TIMESHARE);
86static atomic_uint32_t rw_mixed_left = 0;
87
88static void rw_mixed_worker(void *arg) {
89 cc_ignore(arg);
90 for (int i = 0; i < RWLOCK_MIXED_LOOPS; i++) {
91 if (prng_next() & 1) {
92 // Reader
93 rw_lock(&rw_mixed, RWLOCK_READ);
94 } else {
95 // Writer
96 rw_lock(&rw_mixed, RWLOCK_WRITE);
97 }
98
99 for (volatile size_t j = 0; j < (prng_next() & 0x1f); j++)
100 cpu_pause();
101
102 rw_unlock(&rw_mixed);
103
104 if (prng_next() & 1)
105 scheduler_yield();
106 }
107
108 atomic_dec(&rw_mixed_left);
109}
110
111TEST_DEFINE_INTEGRATION(rwlock, mt_mixed_stress, TEST_INTENSITY(4, 24, 64)) {
112 size_t num_threads = ctx->intensity_val ? ctx->intensity_val : 24;
113 if (num_threads > RWLOCK_MIXED_THREADS_MAX)
114 num_threads = RWLOCK_MIXED_THREADS_MAX;
115
116 atomic_store(&rw_mixed_left, (uint32_t) num_threads);
117
118 for (size_t i = 0; i < num_threads; i++)
119 mixed_threads[i] =
120 thread_spawn("rm", rw_mixed_worker, .joinable = true);
121
122 for (size_t i = 0; i < num_threads; i++) {
123 if (mixed_threads[i])
124 thread_join(t: mixed_threads[i]);
125 }
126
127 TEST_ASSERT_EQ(atomic_load(&rw_mixed_left), 0);
128
129 return TEST_SUCCESS;
130}
131
132#define RWLOCK_CHAOS_THREADS_MAX 64
133#define RWLOCK_CHAOS_LOOPS 500
134
135static struct rwlock rw_chaos = RWLOCK_INIT(THREAD_PRIO_CLASS_TIMESHARE);
136static atomic_uint32_t rw_chaos_left = 0;
137
138static void rw_chaos_worker(void *arg) {
139 cc_ignore(arg);
140 for (int i = 0; i < RWLOCK_CHAOS_LOOPS; i++) {
141 if (prng_next() & 1)
142 rw_lock(&rw_chaos, RWLOCK_READ);
143 else
144 rw_lock(&rw_chaos, RWLOCK_WRITE);
145
146 for (volatile size_t j = 0; j < (prng_next() & 0x1F); j++)
147 cpu_pause();
148
149 rw_unlock(&rw_chaos);
150
151 if (prng_next() & 1)
152 scheduler_yield();
153 }
154
155 test_info("%u threads left", atomic_fetch_sub(&rw_chaos_left, 1) - 1);
156}
157
158TEST_DEFINE_INTEGRATION(rwlock, mt_chaos, TEST_INTENSITY(4, 24, 64)) {
159 size_t num_threads = ctx->intensity_val ? ctx->intensity_val : 24;
160 if (num_threads > RWLOCK_CHAOS_THREADS_MAX)
161 num_threads = RWLOCK_CHAOS_THREADS_MAX;
162
163 atomic_store(&rw_chaos_left, (uint32_t) num_threads);
164 struct thread *workers[RWLOCK_CHAOS_THREADS_MAX];
165
166 enum irql irql = irql_raise(new_level: IRQL_DISPATCH_LEVEL);
167 for (size_t i = 0; i < num_threads; i++)
168 workers[i] = thread_spawn("rch", rw_chaos_worker, .joinable = true);
169 irql_lower(old_level: irql);
170
171 for (size_t i = 0; i < num_threads; i++) {
172 if (workers[i])
173 thread_join(t: workers[i]);
174 }
175
176 TEST_ASSERT_EQ(atomic_load(&rw_chaos_left), 0);
177
178 return TEST_SUCCESS;
179}
180
181static struct rwlock rw_correct = RWLOCK_INIT(THREAD_PRIO_CLASS_TIMESHARE);
182static atomic_uint32_t active_readers = 0;
183
184static atomic_uint32_t active_writers = 0;
185static atomic_bool correctness_ok = true;
186
187#define RWLOCK_CORRECT_LOOPS 5000
188#define RWLOCK_CORRECT_THREADS_MAX 64
189
190static atomic_uint correctness_left = 0;
191
192static void rw_correct_worker(void *arg) {
193 cc_ignore(arg);
194 for (int i = 0; i < RWLOCK_CORRECT_LOOPS; i++) {
195 if (prng_next() & 1) {
196 // Reader
197 rw_lock(&rw_correct, RWLOCK_READ);
198 atomic_inc(&active_readers);
199
200 if (atomic_load(&active_writers) > 0)
201 atomic_store(&correctness_ok, false);
202
203 for (volatile size_t j = 0; j < (prng_next() & 0xF); j++)
204 cpu_pause();
205
206 atomic_dec(&active_readers);
207 rw_unlock(&rw_correct);
208 } else {
209 // Writer
210 rw_lock(&rw_correct, RWLOCK_WRITE);
211 atomic_inc(&active_writers);
212
213 if (atomic_load(&active_readers) > 0 ||
214 atomic_load(&active_writers) > 1)
215 atomic_store(&correctness_ok, false);
216
217 for (volatile size_t j = 0; j < (prng_next() & 0xF); j++)
218 cpu_pause();
219
220 atomic_dec(&active_writers);
221 rw_unlock(&rw_correct);
222 }
223
224 if (prng_next() & 1)
225 scheduler_yield();
226 }
227
228 atomic_dec(&correctness_left);
229}
230
231TEST_DEFINE_INTEGRATION(rwlock, mutual_exclusion, TEST_INTENSITY(4, 16, 64)) {
232 size_t num_threads = ctx->intensity_val ? ctx->intensity_val : 16;
233 if (num_threads > RWLOCK_CORRECT_THREADS_MAX)
234 num_threads = RWLOCK_CORRECT_THREADS_MAX;
235
236 atomic_store(&active_readers, 0);
237 atomic_store(&active_writers, 0);
238 atomic_store(&correctness_ok, true);
239 atomic_store(&correctness_left, (unsigned) num_threads);
240
241 struct thread *workers[RWLOCK_CORRECT_THREADS_MAX];
242
243 enum irql irql = irql_raise(new_level: IRQL_DISPATCH_LEVEL);
244 for (size_t i = 0; i < num_threads; i++)
245 workers[i] = thread_spawn("rcorr", rw_correct_worker, .joinable = true);
246 irql_lower(old_level: irql);
247
248 for (size_t i = 0; i < num_threads; i++) {
249 if (workers[i])
250 thread_join(t: workers[i]);
251 }
252
253 TEST_ASSERT_EQ(atomic_load(&correctness_left), 0);
254 TEST_ASSERT(atomic_load(&correctness_ok));
255
256 return TEST_SUCCESS;
257}
258