code wiki / (root) / nx_thread_spawn_test.nx

nx_thread_spawn_test.nx source

↩ module page · 116 lines · 4208 B

1// nx_thread_spawn_test.nx -- end-to-end test for __thread_clone: 2// real OS thread spawn + atomic counter bumping + spin-join. 3// 4// Strategy: parent allocates a shared counter (mmap'd page so child 5// sees the same memory), spawns a worker thread that bumps the 6// counter N times via atomic FAA, then spin-waits until the counter 7// hits N. No futex/condvar needed for this smoke -- just proves the 8// child trampoline works and that atomics propagate cross-thread. 9// 10// Per cardinal end-to-end-or-dont-ship: this test EXERCISES the 11// trampoline rather than just compiling it. Runs under 12// qemu-riscv64-static which emulates clone() correctly. 13 14import "nx_kernel_v2.nx" 15import "nx_log.nx" 16import "nx_atom.nx" 17import "nx_thread.nx" 18import "nx_hw.nx" 19 20const ITERATIONS_PER_THREAD: i64 = 10000 21 22// Contention oversubscription multiplier: spawn STRESS_FACTOR x cpu_count 23// threads so the race test produces real contention even on single-CPU 24// emulators (qemu-user reports 1 CPU regardless of host) -- per 25// [[feedback-dynamic-hw-sizing-no-hardcoded-thread-counts]] never 26// hardcode thread counts; always derive from hw + a factor. 27const STRESS_FACTOR: i64 = 4 28 29// Worker function -- each spawned thread calls this with a pointer 30// to the shared counter. Bumps it ITERATIONS_PER_THREAD times via 31// atomic fetch-and-add at SEQ_CST ordering. 32func worker_bump(arg: *u8) -> i64 { 33 let counter: *i64 = arg as *i64 34 var i: i64 = 0 35 while i < ITERATIONS_PER_THREAD { 36 nx_atom_faa_i64(counter, 1, NX_MO_SEQ_CST) 37 i = i + 1 38 } 39 return 0 40} 41 42func main() -> nx_exit { 43 println("=== nx_thread_spawn end-to-end smoke ===" as *u8) 44 45 // Shared counter -- mmap'd so it survives address-space sharing. 46 let raw: *u8 = sys_mmap(64) 47 let counter: *i64 = raw as *i64 48 *counter = 0 49 50 println("Spawning 1 worker thread that bumps counter 10000 times..." as *u8) 51 52 let tid: i64 = nx_thread_spawn_fn(worker_bump, raw, 65536) 53 if tid <= 0 { 54 println("FAIL: nx_thread_spawn returned non-positive TID" as *u8) 55 return 1 56 } 57 println("Thread spawned (positive TID). Spin-joining on counter..." as *u8) 58 59 // Spin-wait until counter reaches the expected value. A real 60 // join would use SYS_wait4 + CLONE_CHILD_CLEARTID/futex, but 61 // this is sufficient for the smoke and proves the child runs. 62 var spins: i64 = 0 63 while nx_atom_load_i64(counter, NX_MO_SEQ_CST) < ITERATIONS_PER_THREAD { 64 spins = spins + 1 65 if spins > 100000000 { 66 println("FAIL: spin timeout (counter < 10000 after 100M spins)" as *u8) 67 return 2 68 } 69 } 70 71 let final: i64 = nx_atom_load_i64(counter, NX_MO_SEQ_CST) 72 if final != ITERATIONS_PER_THREAD { 73 println("FAIL: counter overshot ITERATIONS_PER_THREAD" as *u8) 74 return 3 75 } 76 77 println("PASS T1: 1 thread, counter == 10000." as *u8) 78 79 // ===== T2: hw-derived thread count racing the same counter ===== 80 let n_threads: i64 = nx_hw_worker_count() * STRESS_FACTOR 81 println("" as *u8) 82 println("Spawning hw-derived workers (cpu_count * STRESS_FACTOR) racing same counter:" as *u8) 83 print_i64(n_threads); println(" threads" as *u8) 84 *counter = 0 85 86 var spawned: i64 = 0 87 var t: i64 = 0 88 while t < n_threads { 89 let tid_n: i64 = nx_thread_spawn_fn(worker_bump, raw, 65536) 90 if tid_n <= 0 { 91 println("FAIL: spawn N failed" as *u8) 92 return 4 93 } 94 spawned = spawned + 1 95 t = t + 1 96 } 97 98 let expected: i64 = ITERATIONS_PER_THREAD * n_threads 99 var spins2: i64 = 0 100 while nx_atom_load_i64(counter, NX_MO_SEQ_CST) < expected { 101 spins2 = spins2 + 1 102 if spins2 > 200000000 { 103 println("FAIL: T2 spin timeout" as *u8) 104 return 5 105 } 106 } 107 let final2: i64 = nx_atom_load_i64(counter, NX_MO_SEQ_CST) 108 if final2 != expected { 109 println("FAIL: T2 counter overshot" as *u8) 110 return 6 111 } 112 println("PASS T2: hw-sized racers, counter == ITERATIONS * n_threads (no lost updates)." as *u8) 113 println("" as *u8) 114 println("Real concurrency confirmed: atomic FAA arbitrates dynamic racers." as *u8) 115 return 0 116}