threads(M11): RwLock, WaitGroup, and host-testable sync — Phase 2 done
runtime.Thread.RwLock (reader-preferring, lock/tryLock/unlock + lockShared/tryLockShared/unlockShared) and WaitGroup (start/finish/wait), both on the existing Mutex/Condition. A compile-time Futex seam gated on builtin.os.tag: the futex syscalls on danos, a spin+yield mock off-target (Zig 0.16 has no std.Thread.Futex; wake is a no-op since the state machines re-check). thread.zig is wired into zig build test, so Mutex/RwLock/WaitGroup run as host unit tests with real std.Thread threads (test blocks compile only under test, so std.Thread there is fine on freestanding). thread-rwlock QEMU case: 2 writers set both halves of a value under the exclusive lock while 3 readers check they match under the shared lock; zero half-write observations across ~150k reads. Marks Phase 2 (M7-M11) built. threading.md/threading-plan.md status updated. Gate: host zig build test covers the sync primitives; thread-rwlock PASS (3x); full Done gate 26/26 (whole thread-* suite + guardrail); build clean.
This commit is contained in:
@@ -157,6 +157,8 @@ pub fn run(case: []const u8, boot_information: *const BootInformation) void {
|
||||
taskReapTest(boot_information);
|
||||
} else if (eql(case, "thread-tls")) {
|
||||
threadTlsTest(boot_information);
|
||||
} else if (eql(case, "thread-rwlock")) {
|
||||
threadRwlockTest(boot_information);
|
||||
} else if (eql(case, "args")) {
|
||||
argsTest(boot_information);
|
||||
} else if (eql(case, "init")) {
|
||||
@@ -1785,6 +1787,48 @@ fn threadTlsTest(boot_information: *const BootInformation) void {
|
||||
result();
|
||||
}
|
||||
|
||||
/// RwLock (docs/threading-plan.md M11): `thread-test` in rwlock mode runs writers that set
|
||||
/// two halves of a value under the exclusive lock and readers that check the halves match
|
||||
/// under the shared lock. If the reader/writer lock were wrong, a reader would observe a
|
||||
/// half-written value; zero violations across many reads → the lock holds.
|
||||
fn threadRwlockTest(boot_information: *const BootInformation) void {
|
||||
log("DANOS-TEST-BEGIN: thread-rwlock\n", .{});
|
||||
if (boot_information.initial_ramdisk_len == 0) {
|
||||
check("bootloader handed over an initial_ramdisk", false);
|
||||
result();
|
||||
return;
|
||||
}
|
||||
const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
|
||||
const rd = initial_ramdisk.Reader.init(image) orelse {
|
||||
check("initial_ramdisk image is valid", false);
|
||||
result();
|
||||
return;
|
||||
};
|
||||
|
||||
var started = false;
|
||||
var i: u32 = 0;
|
||||
while (i < rd.count) : (i += 1) {
|
||||
const item = rd.entry(i) orelse continue;
|
||||
if (!eql(item.name, "thread-test")) continue;
|
||||
started = if (process.spawnProcess(item.blob, 4, &.{ "thread-test", "rwlock" })) true else |_| false;
|
||||
break;
|
||||
}
|
||||
check("thread-test (rwlock mode) spawned", started);
|
||||
|
||||
const ok_marker = "thread-rwlock: ok";
|
||||
const fail_marker = "thread-rwlock: FAIL";
|
||||
scheduler.setPriority(1);
|
||||
const deadline = architecture.millis() + 20000;
|
||||
while (architecture.millis() < deadline) {
|
||||
if (bufferHas(ok_marker) or bufferHas(fail_marker)) break;
|
||||
scheduler.yield();
|
||||
}
|
||||
scheduler.setPriority(4);
|
||||
|
||||
check("readers/writers over an RwLock never observed a half-written value", bufferHas(ok_marker) and !bufferHas(fail_marker));
|
||||
result();
|
||||
}
|
||||
|
||||
/// The task reaper (docs/threading-plan.md M8): a dead task's kernel stack used to be
|
||||
/// leaked ("no reaper yet"). Spawn and kill many ring-3 processes and confirm the total
|
||||
/// kernel-stack bytes return to baseline — every stack reclaimed, no leak. (Threads exit
|
||||
|
||||
Reference in New Issue
Block a user