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