1#include "sync/tests/test_internal.h"
2
3struct chaos_state {
4 struct thread *t;
5 atomic_bool alive;
6};
7
8#define CHAOS_THREADS_MAX 64
9static size_t chaos_threads = 12;
10
11#if 0
12#define CHAOS_LOG(fmt, ...) \
13 test_info("[chaos %lu] " fmt, time_get_ms(), ##__VA_ARGS__)
14#else
15#define CHAOS_LOG(fmt, ...) ((void) 0)
16#endif
17
18static size_t chaos_iters_count = 300;
19static struct chaos_state states[CHAOS_THREADS_MAX];
20static atomic_bool chaos_stop = false;
21static atomic_bool starter_ok = false;
22static _Atomic uint32_t sync_chaos_left = 0;
23
24static struct mutex chaos_fuzz_mtx = MUTEX_INIT;
25static struct rwlock chaos_fuzz_rw = RWLOCK_INIT(THREAD_PRIO_CLASS_TIMESHARE);
26static struct spinlock chaos_fuzz_spin = SPINLOCK_INIT;
27static struct qspinlock chaos_fuzz_qspin = QSPINLOCK_INIT;
28
29static _Atomic uint64_t chaos_apc_lock_taken = 0;
30static _Atomic uint64_t chaos_apc_lock_skips = 0;
31
32/* Contend on a global lock from inside the APC,
33 * as the interleaving is what we fuzz.
34 *
35 * Blocking here means we spin irq disabled,
36 * so we trylock and record skips */
37static void chaos_apc_fn(void *arg) {
38 unused(arg);
39 CHAOS_LOG("apc executed on %p", thread_get_current());
40
41 enum irql irql;
42 if (!spin_trylock_irq_disable(&chaos_fuzz_spin, &irql)) {
43 atomic_fetch_add_explicit(&chaos_apc_lock_skips, 1,
44 memory_order_relaxed);
45 return;
46 }
47
48 atomic_fetch_add_explicit(&chaos_apc_lock_taken, 1, memory_order_relaxed);
49 for (volatile int j = 0; j < 10; j++)
50 cpu_relax();
51 spin_unlock(&chaos_fuzz_spin, irql);
52}
53
54static void chaos_apc_spammer(void *arg) {
55 unused(arg);
56 CHAOS_LOG("apc spammer start");
57
58 while (!atomic_load(&chaos_stop)) {
59 int id = prng_next() % chaos_threads;
60
61 if (!atomic_load(&states[id].alive)) {
62 scheduler_yield();
63 continue;
64 }
65
66 if (!thread_get(obj: states[id].t)) {
67 scheduler_yield();
68 continue;
69 }
70
71 struct apc *a = kmalloc(sizeof(struct apc), ALLOC_FLAGS_ZERO);
72 if (a) {
73 apc_init(a, fn: chaos_apc_fn, NULL, destroy: apc_destroy_free);
74 CHAOS_LOG("queue apc to %p", states[id].t);
75 apc_enqueue(t: states[id].t, a, type: APC_TYPE_KERNEL);
76 apc_put(a);
77 }
78 thread_put(t: states[id].t);
79
80 scheduler_yield();
81 }
82}
83
84static void chaos_sleeper(void *arg) {
85 size_t id = (size_t) arg;
86 struct thread *t = thread_get_current();
87 states[id].t = t;
88 atomic_store(&states[id].alive, true);
89
90 while (!atomic_load(&starter_ok))
91 cpu_relax();
92
93 for (size_t i = 0; i < chaos_iters_count; i++) {
94 /* Exercise mutex */
95 mutex_lock(&chaos_fuzz_mtx);
96 for (volatile int j = 0; j < (int) (prng_next() & 0xF); j++)
97 cpu_relax();
98 mutex_unlock(&chaos_fuzz_mtx);
99
100 /* Exercise rwlock */
101 if (prng_next() & 1) {
102 rw_lock(&chaos_fuzz_rw, RWLOCK_ACQUIRE_READ);
103 for (volatile int j = 0; j < (int) (prng_next() & 0xF); j++)
104 cpu_relax();
105 rw_unlock(&chaos_fuzz_rw);
106 } else {
107 rw_lock(&chaos_fuzz_rw, RWLOCK_ACQUIRE_WRITE);
108 for (volatile int j = 0; j < (int) (prng_next() & 0xF); j++)
109 cpu_relax();
110 rw_unlock(&chaos_fuzz_rw);
111 }
112
113 /* Exercise qspinlock */
114 enum irql irql = qspin_lock(&chaos_fuzz_qspin);
115 for (volatile int j = 0; j < (int) (prng_next() & 0xF); j++)
116 cpu_relax();
117 qspin_unlock(&chaos_fuzz_qspin, irql);
118
119 /* Sleep and wait for waker */
120 thread_prepare_to_sleep(t, r: THREAD_SLEEP_REASON_MANUAL,
121 wait_type: THREAD_WAIT_INTERRUPTIBLE, expect_wake_src: (void *) id);
122 thread_yield_until_wake_match();
123 CHAOS_LOG("sleeper %zu woke up, iter %zu", id, i);
124 }
125
126 atomic_store(&states[id].alive, false);
127 atomic_fetch_sub(&sync_chaos_left, 1);
128}
129
130static void chaos_waker(void *arg) {
131 unused(arg);
132 CHAOS_LOG("waker start");
133
134 while (!atomic_load(&chaos_stop)) {
135 int id = prng_next() % chaos_threads;
136
137 if (!atomic_load(&states[id].alive)) {
138 scheduler_yield();
139 continue;
140 }
141
142 if (!thread_get(obj: states[id].t)) {
143 scheduler_yield();
144 continue;
145 }
146
147 CHAOS_LOG("wake %p", states[id].t);
148 thread_wake(t: states[id].t, reason: THREAD_WAKE_REASON_SLEEP_MANUAL,
149 prio: THREAD_PRIO_CLASS_TIMESHARE, wake_src: (void *) (uintptr_t) id);
150 thread_put(t: states[id].t);
151
152 scheduler_yield();
153 }
154}
155
156TEST_DECLARE_INTEGRATION(mutex, interruptible_apc_fuzz,
157 TEST_INTENSITY(4, 12, CHAOS_THREADS_MAX)) {
158 if (global.core_count < 2) {
159 return TEST_SKIP(TEST_SKIP_NONE);
160 }
161
162 chaos_threads = ctx->intensity_val ? ctx->intensity_val : 12;
163 if (chaos_threads > CHAOS_THREADS_MAX)
164 chaos_threads = CHAOS_THREADS_MAX;
165
166 for (size_t i = 0; i < chaos_threads; i++) {
167 states[i].t = NULL;
168 atomic_store(&states[i].alive, false);
169 }
170
171 atomic_store(&sync_chaos_left, chaos_threads);
172 atomic_store(&chaos_stop, false);
173 atomic_store(&starter_ok, false);
174 atomic_store(&chaos_apc_lock_taken, 0);
175 atomic_store(&chaos_apc_lock_skips, 0);
176
177 struct thread *threads[CHAOS_THREADS_MAX];
178 for (size_t i = 0; i < chaos_threads; i++) {
179 threads[i] = thread_create(name: "cs", entry_point: chaos_sleeper, arg: (void *) i);
180 TEST_ASSERT_NONNULL(threads[i]);
181 thread_set_joinable(t: threads[i]);
182 thread_enqueue(t: threads[i]);
183 }
184
185 struct thread *spammer =
186 thread_spawn_joinable(name: "chaos_apc_spammer", entry: chaos_apc_spammer, NULL);
187 TEST_ASSERT_NONNULL(spammer);
188 struct thread *waker =
189 thread_spawn_joinable(name: "chaos_waker", entry: chaos_waker, NULL);
190 TEST_ASSERT_NONNULL(waker);
191
192 atomic_store(&starter_ok, true);
193
194 for (size_t i = 0; i < chaos_threads; i++)
195 thread_join(t: threads[i]);
196
197 atomic_store(&chaos_stop, true);
198
199 thread_join(t: spammer);
200 thread_join(t: waker);
201
202 TEST_ASSERT_EQ(atomic_load(&sync_chaos_left), 0);
203
204 test_info("apc lock: %llu taken, %llu skipped",
205 (unsigned long long) atomic_load(&chaos_apc_lock_taken),
206 (unsigned long long) atomic_load(&chaos_apc_lock_skips));
207
208 return TEST_SUCCESS;
209}
210