threads(M3): join, detach, and cross-core parallelism
thread_spawn takes a 4th arg, an exit-endpoint handle: spawnThreadSupervised resolves and refcounts it under the spawn lock (like spawnProcessSupervised), so a thread's death posts a child-exit notification carrying its tid. runtime Thread.join blocks in replyWait on that (private) endpoint for its tid, then munmaps the stack; detach relinquishes the join (stack reclaimed at process exit, for now). New current_core=39 syscall + Thread.currentCore() lets a worker observe which core it ran on. The closure now lives at the top of the thread's own (private) stack instead of the heap, so spawn/join never touch the not-yet-thread-safe runtime heap. thread-test gains a join mode: 4 workers x 100k atomic increments, joined, with counter == N*K and >1 core stamped (real parallelism), plus a detached worker. Gate thread-join PASS (4x, non-flaky); 17 guardrail/M1/M2 cases green; build + host tests clean.
This commit is contained in:
@@ -143,6 +143,8 @@ pub fn run(case: []const u8, boot_information: *const BootInformation) void {
|
||||
aspaceRefcountTest(boot_information);
|
||||
} else if (eql(case, "thread-spawn")) {
|
||||
threadSpawnTest(boot_information);
|
||||
} else if (eql(case, "thread-join")) {
|
||||
threadJoinTest(boot_information);
|
||||
} else if (eql(case, "args")) {
|
||||
argsTest(boot_information);
|
||||
} else if (eql(case, "init")) {
|
||||
@@ -1504,6 +1506,51 @@ fn threadSpawnTest(boot_information: *const BootInformation) void {
|
||||
result();
|
||||
}
|
||||
|
||||
/// Thread join + parallelism (docs/threading-plan.md M3): `thread-test` in join mode
|
||||
/// spawns N workers that each do K atomic increments on a shared counter and stamp the
|
||||
/// core they ran on; it `join`s all N and asserts the total is exactly N*K (every worker
|
||||
/// ran, join waited for each) and that >1 core was used (genuine parallelism), then a
|
||||
/// detached worker proves `detach`. Its single verdict marker is the case result.
|
||||
fn threadJoinTest(boot_information: *const BootInformation) void {
|
||||
log("DANOS-TEST-BEGIN: thread-join\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;
|
||||
};
|
||||
|
||||
// Spawn thread-test in join mode (argv selects the mode).
|
||||
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", "join" })) true else |_| false;
|
||||
break;
|
||||
}
|
||||
check("thread-test (join mode) spawned", started);
|
||||
|
||||
const ok_marker = "thread-test: join ok";
|
||||
const fail_marker = "thread-test: FAIL";
|
||||
scheduler.setPriority(1);
|
||||
const deadline = architecture.millis() + 15000;
|
||||
while (architecture.millis() < deadline) {
|
||||
if (bufferHas(ok_marker) or bufferHas(fail_marker)) break;
|
||||
scheduler.yield();
|
||||
}
|
||||
scheduler.setPriority(4);
|
||||
|
||||
check("N worker threads joined; counter exact (N*K) and >1 core used", bufferHas(ok_marker));
|
||||
check("no thread failure reported", !bufferHas(fail_marker));
|
||||
result();
|
||||
}
|
||||
|
||||
/// The full PID-1 path: the bootloader read /system/services/init off the boot volume and
|
||||
/// handed it over; load it as a user ELF and spawn it as a real ring-3 process
|
||||
/// — the same call the normal boot path makes — then confirm it beats. init
|
||||
|
||||
Reference in New Issue
Block a user