danos/system/services/thread-test/thread-test.zig

48 lines
2.0 KiB
Zig

//! thread-test — the first multi-threaded danos binary (docs/threading-plan.md M2).
//!
//! Proves `runtime.Thread.spawn` starts a task in the **same address space**: the main
//! thread spawns a worker, the worker writes a shared global and signals `done`, and the
//! main thread — polling that shared memory — observes the write. Seeing the write proves
//! the two tasks share one address space (a separate process could not touch this memory).
//! The `thread-test: child ran in shared aspace ok` line is the case's marker.
//!
//! Built multi-threaded (`addThreadedUserBinary`), so the poll below is a real atomic
//! load the compiler must re-read — under a single-threaded build it could be hoisted.
const std = @import("std");
const runtime = @import("runtime");
/// Written by the worker thread, read by main — the shared-address-space evidence.
var shared_value: u32 = 0;
/// Release/acquire handshake: publishes the `shared_value` write to the reader.
var done = std.atomic.Value(u32).init(0);
const sentinel: u32 = 0xA5A5;
fn worker() void {
shared_value = sentinel; // a plain write to a global we share with main
done.store(1, .release); // ...published by this release store
}
pub fn main() void {
_ = runtime.system.write("thread-test: starting\n");
_ = runtime.Thread.spawn(.{}, worker, .{}) catch {
_ = runtime.system.write("thread-test: FAIL spawn refused\n");
return;
};
// Bounded wait for the worker to run and publish. yield() keeps the core useful;
// the acquire load pairs with the worker's release store.
var spins: usize = 0;
while (done.load(.acquire) == 0 and spins < 50_000_000) : (spins += 1) {
runtime.system.yield();
}
if (done.load(.acquire) == 1 and shared_value == sentinel) {
_ = runtime.system.write("thread-test: child ran in shared aspace ok\n");
} else {
_ = runtime.system.write("thread-test: FAIL worker did not update shared memory\n");
}
}