Skip to content

Commit eeeb142

Browse files
committed
test(gc): concurrent register/unregister churn stress test for the #3a registry (D-244 robustness)
Smell-audited: 1: pre-#4 hardening for the ThreadGcContext registry that Phase-B workers will register into. 4 threads x 200 register+unregister cycles through a threaded io_default; asserts the io_default-locked fixed array is race-free (back to count 0, no stranded slot). Additive (new test only). zig build test green.
1 parent 6ca347a commit eeeb142

1 file changed

Lines changed: 35 additions & 0 deletions

File tree

‎src/runtime/gc/root_set.zig‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -592,3 +592,38 @@ test "union walk: a registered context's frame + macro are walked alongside self
592592
try testing.expect(found_frame); // worker's binding-frame value (union source 1)
593593
try testing.expect(found_macro); // worker's macro slot (union source 1)
594594
}
595+
596+
test "registry: concurrent register/unregister churn is race-free (D-244 #3a robustness)" {
597+
// The registry's io_default-locked array must serialize register/unregister
598+
// across real OS threads (Phase-B workers register at spawn / join). Set a
599+
// threaded io so the Io.Mutex blocks for real, then restore it (the singleton
600+
// is process-wide; tests run serially).
601+
const saved_io = io_default.get();
602+
var threaded = std.Io.Threaded.init(testing.allocator, .{});
603+
defer threaded.deinit();
604+
defer io_default.set(saved_io); // runs before threaded.deinit (LIFO)
605+
io_default.set(threaded.io());
606+
607+
try testing.expectEqual(@as(usize, 0), registeredThreadCount());
608+
609+
const Worker = struct {
610+
fn run() void {
611+
// Each thread owns one context pointing at its own TLS; register +
612+
// immediately unregister in a tight loop (≤4 concurrent < cap 64).
613+
var ctx: ThreadGcContext = .{ .frame_slot = &env_mod.current_frame, .macro_slot = &macro_root_slot };
614+
var i: usize = 0;
615+
while (i < 200) : (i += 1) {
616+
registerThread(&ctx) catch return;
617+
unregisterThread(&ctx);
618+
}
619+
}
620+
};
621+
622+
var threads: [4]std.Thread = undefined;
623+
for (&threads) |*t| t.* = try std.Thread.spawn(.{}, Worker.run, .{});
624+
for (&threads) |t| t.join();
625+
626+
// Every register paired with an unregister → back to empty, no leaked slot,
627+
// no corruption (a torn array under contention would strand a registration).
628+
try testing.expectEqual(@as(usize, 0), registeredThreadCount());
629+
}

0 commit comments

Comments
 (0)