1#ifdef TEST_RWLOCK
2
3#include <crypto/prng.h>
4#include <sch/sched.h>
5#include <thread/thread.h>
6
7#include <stdatomic.h>
8#include <sync/rwlock.h>
9#include <test.h>
10
11#define RWLOCK_REPORT_PROBLEMS() \
12 test_info("rwlock tests are encountering problems and will be skipped"); \
13 return TEST_SKIP(TEST_SKIP_NONE);
14
15static struct rwlock rw_basic = RWLOCK_INIT(THREAD_PRIO_CLASS_TIMESHARE);
16
17TEST_DECLARE(rwlock_basic_read, .tier = TEST_TIER_UNIT) {
18 rwlock_lock(lock: &rw_basic, type: RWLOCK_ACQUIRE_READ);
19 scheduler_yield();
20 rwlock_unlock(lock: &rw_basic);
21
22 return TEST_SUCCESS;
23}
24
25static struct rwlock rw_basic_w = RWLOCK_INIT(THREAD_PRIO_CLASS_TIMESHARE);
26
27TEST_DECLARE(rwlock_basic_write, .tier = TEST_TIER_UNIT) {
28
29 rwlock_lock(lock: &rw_basic_w, type: RWLOCK_ACQUIRE_WRITE);
30 scheduler_yield();
31 rwlock_unlock(lock: &rw_basic_w);
32
33 return TEST_SUCCESS;
34}
35
36static struct rwlock rw_two_writers = RWLOCK_INIT(THREAD_PRIO_CLASS_TIMESHARE);
37static atomic_bool rw_two_done = false;
38
39static void rw_two_writer_thread(void *) {
40 rwlock_lock(lock: &rw_two_writers, type: RWLOCK_ACQUIRE_WRITE);
41 rwlock_unlock(lock: &rw_two_writers);
42
43 atomic_store(&rw_two_done, true);
44}
45
46TEST_DECLARE(rwlock_two_writer_basic, .tier = TEST_TIER_UNIT) {
47
48 rwlock_lock(lock: &rw_two_writers, type: RWLOCK_ACQUIRE_WRITE);
49
50 thread_spawn_on_core(name: "rw_two_writer", entry: rw_two_writer_thread, NULL, core_id: 0);
51
52 scheduler_yield(); // let second writer block
53
54 rwlock_unlock(lock: &rw_two_writers);
55
56 while (!atomic_load(&rw_two_done))
57 scheduler_yield();
58
59 return TEST_SUCCESS;
60}
61
62#define RWLOCK_READER_COUNT_TEST_N 20
63#define RWLOCK_READER_COUNT_LOOPS 500
64#define RWLOCK_READER_PRINT_INTERVAL 10000
65
66static struct rwlock rw_readers = RWLOCK_INIT(THREAD_PRIO_CLASS_TIMESHARE);
67static _Atomic uint32_t rw_readers_left = RWLOCK_READER_COUNT_TEST_N;
68
69static void rw_reader_worker(void *) {
70 time_t last_print = time_get_ms();
71 for (size_t i = 0; i < RWLOCK_READER_COUNT_LOOPS; i++) {
72 rwlock_lock(lock: &rw_readers, type: RWLOCK_ACQUIRE_READ);
73 scheduler_yield();
74 rwlock_unlock(lock: &rw_readers);
75 time_t now = time_get_ms();
76 if ((now - last_print) > RWLOCK_READER_PRINT_INTERVAL) {
77 test_info("RWlock reader %s on iteration %zu",
78 thread_get_current()->name, i);
79 }
80 last_print = now;
81 }
82
83 atomic_fetch_sub(&rw_readers_left, 1);
84}
85
86TEST_DECLARE(rwlock_many_readers, .tier = TEST_TIER_INTEGRATION,
87 .print_logs = true) {
88 enum irql irql = irql_raise(new_level: IRQL_DISPATCH_LEVEL);
89 for (int i = 0; i < RWLOCK_READER_COUNT_TEST_N; i++)
90 thread_spawn(name: "rr_%zu", entry: rw_reader_worker, NULL, i);
91 irql_lower(old_level: irql);
92
93 while (atomic_load(&rw_readers_left))
94 scheduler_yield();
95
96 return TEST_SUCCESS;
97}
98
99#define RWLOCK_MIXED_THREADS 24
100#define RWLOCK_MIXED_LOOPS 500
101volatile struct thread *mixed_threads[RWLOCK_MIXED_THREADS];
102static struct rwlock rw_mixed = RWLOCK_INIT(THREAD_PRIO_CLASS_TIMESHARE);
103static _Atomic uint32_t rw_mixed_left = RWLOCK_MIXED_THREADS;
104
105static void rw_mixed_worker(void *) {
106 for (int i = 0; i < RWLOCK_MIXED_LOOPS; i++) {
107 if (prng_next() & 1) {
108 // Reader
109 rwlock_lock(lock: &rw_mixed, type: RWLOCK_ACQUIRE_READ);
110 } else {
111 // Writer
112 rwlock_lock(lock: &rw_mixed, type: RWLOCK_ACQUIRE_WRITE);
113 }
114
115 for (volatile size_t j = 0; j < (prng_next() & 0x1f); j++)
116 cpu_relax();
117
118 rwlock_unlock(lock: &rw_mixed);
119
120 if (prng_next() & 1)
121 scheduler_yield();
122 }
123
124 atomic_fetch_sub(&rw_mixed_left, 1);
125}
126
127TEST_DECLARE(rwlock_mixed_stress, .tier = TEST_TIER_INTEGRATION) {
128
129 for (int i = 0; i < RWLOCK_MIXED_THREADS; i++)
130 mixed_threads[i] = thread_spawn(name: "rm", entry: rw_mixed_worker, NULL);
131
132 while (atomic_load(&rw_mixed_left))
133 scheduler_yield();
134
135 return TEST_SUCCESS;
136}
137
138#define RWLOCK_CHAOS_THREADS 24
139#define RWLOCK_CHAOS_LOOPS 500
140
141static struct rwlock rw_chaos = RWLOCK_INIT(THREAD_PRIO_CLASS_TIMESHARE);
142static _Atomic uint32_t rw_chaos_left = RWLOCK_CHAOS_THREADS;
143
144static void rw_chaos_worker(void *) {
145 for (int i = 0; i < RWLOCK_CHAOS_LOOPS; i++) {
146 if (prng_next() & 1)
147 rwlock_lock(lock: &rw_chaos, type: RWLOCK_ACQUIRE_READ);
148 else
149 rwlock_lock(lock: &rw_chaos, type: RWLOCK_ACQUIRE_WRITE);
150
151 for (volatile size_t j = 0; j < (prng_next() & 0x1F); j++)
152 cpu_relax();
153
154 rwlock_unlock(lock: &rw_chaos);
155
156 if (prng_next() & 1)
157 scheduler_yield();
158 }
159
160 test_info("%u threads left", atomic_fetch_sub(&rw_chaos_left, 1) - 1);
161}
162
163TEST_DECLARE(rwlock_chaos, .tier = TEST_TIER_INTEGRATION, .print_logs = true) {
164 enum irql irql = irql_raise(new_level: IRQL_DISPATCH_LEVEL);
165 for (int i = 0; i < RWLOCK_CHAOS_THREADS; i++)
166 thread_spawn(name: "rch", entry: rw_chaos_worker, NULL);
167 irql_lower(old_level: irql);
168
169 while (atomic_load(&rw_chaos_left)) {
170 thread_apply_cpu_penalty(t: thread_get_current());
171 scheduler_yield();
172 }
173
174 return TEST_SUCCESS;
175}
176
177static struct rwlock rw_correct = RWLOCK_INIT(THREAD_PRIO_CLASS_TIMESHARE);
178static _Atomic uint32_t active_readers = 0;
179
180static _Atomic uint32_t active_writers = 0;
181static atomic_bool correctness_ok = true;
182
183#define RWLOCK_CORRECT_LOOPS 5000
184#define RWLOCK_CORRECT_THREADS 16
185
186static atomic_uint correctness_left = RWLOCK_CORRECT_THREADS;
187
188static void rw_correct_worker(void *) {
189 for (int i = 0; i < RWLOCK_CORRECT_LOOPS; i++) {
190 if (prng_next() & 1) {
191 // Reader
192 rwlock_lock(lock: &rw_correct, type: RWLOCK_ACQUIRE_READ);
193
194 atomic_fetch_add(&active_readers, 1);
195 if (atomic_load(&active_writers) != 0)
196 atomic_store(&correctness_ok, false);
197
198 scheduler_yield();
199 atomic_fetch_sub(&active_readers, 1);
200
201 rwlock_unlock(lock: &rw_correct);
202 } else {
203 // Writer
204
205 rwlock_lock(lock: &rw_correct, type: RWLOCK_ACQUIRE_WRITE);
206
207 atomic_fetch_add(&active_writers, 1);
208 if (atomic_load(&active_readers) != 0 ||
209 atomic_load(&active_writers) > 1)
210 atomic_store(&correctness_ok, false);
211
212 scheduler_yield();
213 atomic_fetch_sub(&active_writers, 1);
214
215 rwlock_unlock(lock: &rw_correct);
216 }
217 }
218 atomic_fetch_sub(&correctness_left, 1);
219}
220
221TEST_DECLARE(rwlock_correctness, .tier = TEST_TIER_INTEGRATION) {
222
223 for (int i = 0; i < RWLOCK_CORRECT_THREADS; i++)
224 thread_spawn(name: "rwc", entry: rw_correct_worker, NULL);
225
226 while (!atomic_load(&correctness_left))
227 scheduler_yield();
228
229 while (!atomic_load(&correctness_ok))
230 scheduler_yield();
231
232 return TEST_SUCCESS;
233}
234
235#endif
236