| 1 | #include <crypto/prng.h> |
| 2 | #include <mem/alloc.h> |
| 3 | #include <sch/sched.h> |
| 4 | #include <smp/core.h> |
| 5 | #include <stdatomic.h> |
| 6 | #include <test.h> |
| 7 | #include <thread/apc.h> |
| 8 | #include <thread/thread.h> |
| 9 | |
| 10 | #define CHAOS_THREADS 16 |
| 11 | #define CHAOS_ITERS 50000 |
| 12 | |
| 13 | struct chaos_thread_state { |
| 14 | struct thread *t; |
| 15 | atomic_bool alive; |
| 16 | atomic_bool ready; |
| 17 | _Atomic uintptr_t last_cookie; |
| 18 | enum thread_wake_reason last_reason; |
| 19 | }; |
| 20 | |
| 21 | static struct chaos_thread_state states[CHAOS_THREADS]; |
| 22 | static atomic_bool chaos_stop = false; |
| 23 | static atomic_bool starter_ok = false; |
| 24 | static atomic_uint sync_chaos_left = CHAOS_THREADS; |
| 25 | |
| 26 | /* ------------------------------------ |
| 27 | * Rate-limited logging helpers |
| 28 | * ------------------------------------ */ |
| 29 | |
| 30 | #define CHAOS_LOG_INTERVAL_NS (50ULL * 1000 * 1000) /* 50ms */ |
| 31 | #define CHAOS_LOG_BURST 3 |
| 32 | |
| 33 | static inline bool chaos_log_allow(uint64_t *last_ns, uint32_t *burst) { |
| 34 | uint64_t now = time_get_ns(); |
| 35 | |
| 36 | if (now - *last_ns > CHAOS_LOG_INTERVAL_NS) { |
| 37 | *last_ns = now; |
| 38 | *burst = 0; |
| 39 | return true; |
| 40 | } |
| 41 | |
| 42 | if (*burst < CHAOS_LOG_BURST) { |
| 43 | (*burst)++; |
| 44 | return true; |
| 45 | } |
| 46 | |
| 47 | return false; |
| 48 | } |
| 49 | |
| 50 | #define CHAOS_LOG(fmt, ...) \ |
| 51 | do { \ |
| 52 | static uint64_t _last_ns; \ |
| 53 | static uint32_t _burst; \ |
| 54 | if (chaos_log_allow(&_last_ns, &_burst)) \ |
| 55 | printf("[chaos %llu ms] " fmt "\n", time_get_ms(), ##__VA_ARGS__); \ |
| 56 | } while (0) |
| 57 | |
| 58 | /* ------------------------------------ |
| 59 | * APC spammer callback |
| 60 | * ------------------------------------ */ |
| 61 | static void chaos_apc_fn(void *apc) { |
| 62 | (void) apc; |
| 63 | /* No signal needed; the wake logic handles APC ordering. */ |
| 64 | } |
| 65 | |
| 66 | /* ------------------------------------ |
| 67 | * Thread: Sleeper |
| 68 | * Random interruptible sleeps |
| 69 | * ------------------------------------ */ |
| 70 | static void chaos_sleeper(void *arg) { |
| 71 | size_t id = (size_t) arg; |
| 72 | struct chaos_thread_state *s = &states[id]; |
| 73 | |
| 74 | CHAOS_LOG("sleeper[%zu] start on core %u" , id, smp_core_id()); |
| 75 | |
| 76 | while (!atomic_load(&starter_ok)) |
| 77 | cpu_relax(); |
| 78 | |
| 79 | for (int i = 0; i < CHAOS_ITERS && !atomic_load(&chaos_stop); i++) { |
| 80 | uintptr_t cookie = prng_next(); |
| 81 | atomic_store(&s->last_cookie, cookie); |
| 82 | atomic_store(&s->ready, false); |
| 83 | |
| 84 | CHAOS_LOG("sleeper[%zu] sleep iter=%d cookie=%p" , id, i, |
| 85 | (void *) cookie); |
| 86 | thread_prepare_to_sleep(t: thread_get_current(), |
| 87 | r: THREAD_SLEEP_REASON_MANUAL, |
| 88 | wait_type: THREAD_WAIT_INTERRUPTIBLE, expect_wake_src: (void *) cookie); |
| 89 | |
| 90 | thread_yield_until_wake_match(); |
| 91 | |
| 92 | atomic_store(&s->ready, true); |
| 93 | |
| 94 | if (prng_next() % 5 == 0) |
| 95 | scheduler_yield(); |
| 96 | } |
| 97 | |
| 98 | CHAOS_LOG("sleeper[%zu] exit" , id); |
| 99 | |
| 100 | atomic_fetch_sub(&sync_chaos_left, 1); |
| 101 | atomic_store(&s->alive, false); |
| 102 | } |
| 103 | |
| 104 | /* ------------------------------------ |
| 105 | * Thread: Waker |
| 106 | * Randomly wakes sleeper threads |
| 107 | * ------------------------------------ */ |
| 108 | static void chaos_waker(void *a) { |
| 109 | (void) a; |
| 110 | CHAOS_LOG("waker start" ); |
| 111 | |
| 112 | while (!atomic_load(&chaos_stop)) { |
| 113 | int id = prng_next() % CHAOS_THREADS; |
| 114 | struct chaos_thread_state *s = &states[id]; |
| 115 | |
| 116 | if (!atomic_load(&s->alive)) |
| 117 | continue; |
| 118 | |
| 119 | if (!thread_get(obj: s->t)) |
| 120 | continue; |
| 121 | |
| 122 | bool correct = (prng_next() % 3 == 0); |
| 123 | uintptr_t cookie = correct ? atomic_load(&s->last_cookie) : prng_next(); |
| 124 | |
| 125 | CHAOS_LOG("waker wake %p" , s->t); |
| 126 | thread_wake(t: s->t, reason: THREAD_WAKE_REASON_SLEEP_MANUAL, |
| 127 | prio: s->t->perceived_prio_class, wake_src: (void *) cookie); |
| 128 | CHAOS_LOG("waker wake done" ); |
| 129 | |
| 130 | thread_put(t: s->t); |
| 131 | |
| 132 | if (prng_next() % 2) |
| 133 | scheduler_yield(); |
| 134 | } |
| 135 | |
| 136 | CHAOS_LOG("waker exit" ); |
| 137 | } |
| 138 | |
| 139 | /* ------------------------------------ |
| 140 | * Thread: APC Spammer |
| 141 | * ------------------------------------ */ |
| 142 | static void chaos_apc_spammer(void *arg) { |
| 143 | (void) arg; |
| 144 | static atomic_uint apc_count = 0; |
| 145 | |
| 146 | CHAOS_LOG("apc spammer start" ); |
| 147 | |
| 148 | while (!atomic_load(&chaos_stop)) { |
| 149 | |
| 150 | int id = prng_next() % CHAOS_THREADS; |
| 151 | struct chaos_thread_state *s = &states[id]; |
| 152 | |
| 153 | if (!atomic_load(&s->alive)) |
| 154 | continue; |
| 155 | |
| 156 | if (!thread_get(obj: s->t)) |
| 157 | continue; |
| 158 | |
| 159 | struct apc *apc = apc_create(); |
| 160 | apc_init(a: apc, fn: chaos_apc_fn, NULL); |
| 161 | apc_enqueue(t: s->t, a: apc, type: APC_TYPE_KERNEL); |
| 162 | |
| 163 | uint32_t n = atomic_fetch_add(&apc_count, 1) + 1; |
| 164 | if ((n & 0xfff) == 0) { |
| 165 | CHAOS_LOG("apc spammer queued %u APCs" , n); |
| 166 | } |
| 167 | |
| 168 | thread_put(t: s->t); |
| 169 | scheduler_yield(); |
| 170 | } |
| 171 | |
| 172 | CHAOS_LOG("apc spammer exit" ); |
| 173 | } |
| 174 | |
| 175 | /* ------------------------------------ |
| 176 | * Thread: Migrator |
| 177 | * Moves threads across cores |
| 178 | * ------------------------------------ */ |
| 179 | static void chaos_migrator() { |
| 180 | uint32_t cores = global.core_count; |
| 181 | |
| 182 | CHAOS_LOG("migrator start (%u cores)" , cores); |
| 183 | |
| 184 | while (!atomic_load(&chaos_stop)) { |
| 185 | int id = prng_next() % CHAOS_THREADS; |
| 186 | uint32_t core = prng_next() % cores; |
| 187 | |
| 188 | if (!atomic_load(&states[id].alive)) |
| 189 | continue; |
| 190 | |
| 191 | if (!thread_get(obj: states[id].t)) |
| 192 | continue; |
| 193 | |
| 194 | CHAOS_LOG("migrate %p to %u" , states[id].t, core); |
| 195 | thread_migrate(t: states[id].t, dest_core: core); |
| 196 | CHAOS_LOG("migrate %p ok" , states[id].t); |
| 197 | thread_put(t: states[id].t); |
| 198 | |
| 199 | scheduler_yield(); |
| 200 | } |
| 201 | |
| 202 | CHAOS_LOG("migrator exit" ); |
| 203 | } |
| 204 | |
| 205 | /* ------------------------------------ |
| 206 | * Main Test |
| 207 | * ------------------------------------ */ |
| 208 | TEST_DECLARE(thread_interruptible_chaos_fuzz, .tier = TEST_TIER_INTEGRATION) { |
| 209 | test_info("this test is long. comment me out to run it." ); |
| 210 | return TEST_SKIP(TEST_SKIP_NONE); |
| 211 | |
| 212 | CHAOS_LOG("chaos test start" ); |
| 213 | |
| 214 | if (global.core_count < 6) { |
| 215 | test_info("needs 6+ cores for chaos fuzz" ); |
| 216 | return TEST_SKIP(TEST_SKIP_NONE); |
| 217 | } |
| 218 | |
| 219 | enum irql irql = irql_raise(new_level: IRQL_DISPATCH_LEVEL); |
| 220 | for (size_t i = 0; i < CHAOS_THREADS; i++) { |
| 221 | atomic_store(&states[i].alive, true); |
| 222 | states[i].t = thread_spawn_on_core(name: "chaos_sleeper" , entry: chaos_sleeper, |
| 223 | arg: (void *) i, core_id: i % global.core_count); |
| 224 | } |
| 225 | |
| 226 | atomic_store(&starter_ok, true); |
| 227 | |
| 228 | thread_spawn(name: "chaos_wake" , entry: chaos_waker, NULL); |
| 229 | thread_spawn(name: "chaos_migrate" , entry: chaos_migrator, NULL); |
| 230 | thread_spawn(name: "chaos_apc" , entry: chaos_apc_spammer, NULL); |
| 231 | irql_lower(old_level: irql); |
| 232 | |
| 233 | uint64_t last_report = time_get_ms(); |
| 234 | |
| 235 | while (atomic_load(&sync_chaos_left)) { |
| 236 | if (time_get_ms() - last_report > 1000) { |
| 237 | last_report = time_get_ms(); |
| 238 | CHAOS_LOG("waiting: %u sleepers left" , |
| 239 | atomic_load(&sync_chaos_left)); |
| 240 | } |
| 241 | scheduler_yield(); |
| 242 | } |
| 243 | |
| 244 | atomic_store(&chaos_stop, true); |
| 245 | |
| 246 | CHAOS_LOG("chaos test complete" ); |
| 247 | return TEST_SUCCESS; |
| 248 | } |
| 249 | |