add an SMP lock-stress test case
Four pairs push 400k sequenced messages through small channels; checks FIFO order and cross-core execution. Verified to fail with the lock disabled.
This commit is contained in:
parent
43afe6bf2e
commit
dba3939a0f
|
|
@ -70,6 +70,8 @@ pub fn run(case: []const u8, boot_info: *const BootInfo) void {
|
|||
ipcTest();
|
||||
} else if (eql(case, "smp")) {
|
||||
smpTest();
|
||||
} else if (eql(case, "smp-stress")) {
|
||||
stressTest();
|
||||
} else if (eql(case, "fault-ud")) {
|
||||
faultInvalidOpcode();
|
||||
} else if (eql(case, "fault-pf")) {
|
||||
|
|
@ -482,6 +484,92 @@ fn smpTest() void {
|
|||
result();
|
||||
}
|
||||
|
||||
// --- SMP stress: hammer the big kernel lock across cores ------------------
|
||||
|
||||
const stress_pairs = 4; // producer/consumer pairs (8 tasks; fits the 16-task pool)
|
||||
const stress_msgs = 100_000; // messages per pair
|
||||
const stress_cap = 4; // small channel -> constant block/wake, more lock churn
|
||||
|
||||
var stress_chan = [_]ipc.Channel(u64, stress_cap){.{}} ** stress_pairs;
|
||||
var stress_recv = [_]u64{0} ** stress_pairs; // messages received per pair
|
||||
var stress_order_ok = [_]bool{true} ** stress_pairs; // FIFO order held per pair
|
||||
var stress_cores = [_]bool{false} ** 8; // cores that ran a consumer
|
||||
var stress_prod_claim: usize = 0;
|
||||
var stress_cons_claim: usize = 0;
|
||||
|
||||
fn stressProducer() void {
|
||||
// Claim a unique pair index (atomic: producers start on different cores).
|
||||
const idx = @atomicRmw(usize, &stress_prod_claim, .Add, 1, .monotonic);
|
||||
var v: u64 = 1;
|
||||
while (v <= stress_msgs) : (v += 1) stress_chan[idx].send(v);
|
||||
sched.exit();
|
||||
}
|
||||
|
||||
fn stressConsumer() void {
|
||||
const idx = @atomicRmw(usize, &stress_cons_claim, .Add, 1, .monotonic);
|
||||
var expected: u64 = 1;
|
||||
while (expected <= stress_msgs) : (expected += 1) {
|
||||
const got = stress_chan[idx].recv();
|
||||
if (got != expected) stress_order_ok[idx] = false; // lost/reordered => lock broke
|
||||
const c = sched.currentCpuIndex();
|
||||
if (c < stress_cores.len) stress_cores[c] = true;
|
||||
stress_recv[idx] = expected;
|
||||
}
|
||||
sched.exit();
|
||||
}
|
||||
|
||||
/// Stress the big kernel lock under sustained cross-core contention. Each pair drives
|
||||
/// `stress_msgs` sequenced messages through a 4-slot channel — every send and recv
|
||||
/// takes the lock, and the small buffer forces constant block/wake (so the scheduler
|
||||
/// churns too). A single-producer/single-consumer channel must deliver in strict FIFO
|
||||
/// order; if the lock let two cores into a critical section at once, the ring buffer
|
||||
/// corrupts and the consumer sees a wrong or out-of-order value (or the run hangs /
|
||||
/// faults). Passing means ~320k lock acquisitions across the cores stayed consistent.
|
||||
fn stressTest() void {
|
||||
log("DANOS-TEST-BEGIN: smp-stress\n", .{});
|
||||
stress_chan = [_]ipc.Channel(u64, stress_cap){.{}} ** stress_pairs;
|
||||
stress_recv = [_]u64{0} ** stress_pairs;
|
||||
stress_order_ok = [_]bool{true} ** stress_pairs;
|
||||
stress_cores = [_]bool{false} ** 8;
|
||||
stress_prod_claim = 0;
|
||||
stress_cons_claim = 0;
|
||||
|
||||
var i: usize = 0;
|
||||
while (i < stress_pairs) : (i += 1) sched.spawn(stressConsumer, 4);
|
||||
i = 0;
|
||||
while (i < stress_pairs) : (i += 1) sched.spawn(stressProducer, 4);
|
||||
|
||||
// Drop below the workers so they get the cores; wake periodically to check for
|
||||
// completion. A broken lock instead hangs here (harness timeout) or faults.
|
||||
sched.setPriority(1);
|
||||
var spins: u64 = 0;
|
||||
while (spins < 40_000_000_000) : (spins += 1) {
|
||||
var done = true;
|
||||
for (stress_recv) |n| {
|
||||
if (n < stress_msgs) done = false;
|
||||
}
|
||||
if (done) break;
|
||||
}
|
||||
sched.setPriority(4);
|
||||
|
||||
var total: u64 = 0;
|
||||
for (stress_recv) |n| total += n;
|
||||
var order_ok = true;
|
||||
for (stress_order_ok) |ok| {
|
||||
if (!ok) order_ok = false;
|
||||
}
|
||||
var cores: u32 = 0;
|
||||
for (stress_cores) |s| {
|
||||
if (s) cores += 1;
|
||||
}
|
||||
|
||||
log("DANOS-STRESS: {d}/{d} pairs complete on {d} cores\n", .{ total, @as(u64, stress_pairs) * stress_msgs, cores });
|
||||
check("every message delivered", total == @as(u64, stress_pairs) * stress_msgs);
|
||||
check("strict FIFO order held (no lock corruption)", order_ok);
|
||||
check("contention was genuinely cross-core", cores >= 2);
|
||||
result();
|
||||
}
|
||||
|
||||
fn faultInvalidOpcode() void {
|
||||
log("DANOS-TEST-BEGIN: fault-ud\n", .{});
|
||||
asm volatile ("ud2");
|
||||
|
|
|
|||
|
|
@ -113,6 +113,12 @@ CASES = [
|
|||
"smp": 4,
|
||||
"expect": r"DANOS-TEST-RESULT: PASS",
|
||||
"fail": r"DANOS-TEST-RESULT: FAIL"},
|
||||
# Stress the big kernel lock across cores; heavier, so a longer timeout.
|
||||
{"name": "smp-stress",
|
||||
"smp": 4,
|
||||
"timeout": 90,
|
||||
"expect": r"DANOS-TEST-RESULT: PASS",
|
||||
"fail": r"DANOS-TEST-RESULT: FAIL"},
|
||||
{"name": "fault-ud", "expect": r"invalid opcode \(vector 6\)"},
|
||||
{"name": "fault-pf", "expect": r"page fault \(vector 14\)"},
|
||||
{"name": "fault-df", "expect": r"double fault \(vector 8\)"},
|
||||
|
|
@ -184,7 +190,8 @@ def run_case(arch, case):
|
|||
cmd += ["-smp", str(case["smp"])]
|
||||
qemu = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
try:
|
||||
deadline = time.monotonic() + TIMEOUT
|
||||
timeout = case.get("timeout", TIMEOUT)
|
||||
deadline = time.monotonic() + timeout
|
||||
while time.monotonic() < deadline:
|
||||
time.sleep(0.2)
|
||||
text = ""
|
||||
|
|
@ -199,7 +206,7 @@ def run_case(arch, case):
|
|||
if expect.search(text):
|
||||
return True, "matched " + repr(case["expect"])
|
||||
return False, "QEMU exited before matching (triple fault?)"
|
||||
return False, f"timed out after {TIMEOUT}s without matching {case['expect']!r}"
|
||||
return False, f"timed out after {timeout}s without matching {case['expect']!r}"
|
||||
finally:
|
||||
if qemu.poll() is None:
|
||||
qemu.terminate()
|
||||
|
|
|
|||
Loading…
Reference in New Issue