| 1 | #include "sync/tests/test_internal.h" |
| 2 | |
| 3 | struct chaos_state { |
| 4 | struct thread *t; |
| 5 | atomic_bool alive; |
| 6 | }; |
| 7 | |
| 8 | #define CHAOS_THREADS_MAX 64 |
| 9 | static 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 | |
| 18 | static size_t chaos_iters_count = 300; |
| 19 | static struct chaos_state states[CHAOS_THREADS_MAX]; |
| 20 | static atomic_bool chaos_stop = false; |
| 21 | static atomic_bool starter_ok = false; |
| 22 | static _Atomic uint32_t sync_chaos_left = 0; |
| 23 | |
| 24 | static struct mutex chaos_fuzz_mtx = MUTEX_INIT; |
| 25 | static struct rwlock chaos_fuzz_rw = RWLOCK_INIT(THREAD_PRIO_CLASS_TIMESHARE); |
| 26 | static struct spinlock chaos_fuzz_spin = SPINLOCK_INIT; |
| 27 | static struct qspinlock chaos_fuzz_qspin = QSPINLOCK_INIT; |
| 28 | |
| 29 | static _Atomic uint64_t chaos_apc_lock_taken = 0; |
| 30 | static _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 */ |
| 37 | static 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 | |
| 54 | static 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 | |
| 84 | static 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 | |
| 130 | static 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 | |
| 156 | TEST_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 | |