Skip to content

Commit 97c511c

Browse files
committed
test(oom): cover iterator helper recovery guard
1 parent 9a10bb7 commit 97c511c

2 files changed

Lines changed: 74 additions & 4 deletions

File tree

docs/threads/testing.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -135,10 +135,10 @@ heap-cap witnesses also cover no-GIL `ArrayBuffer` byte-slab recovery while a
135135
real peer thread is running and publishing roots to the abort-safe parallel
136136
collector, plus the trace-sensitive async-generator request lock guard that
137137
keeps allocation-failure recovery out of mutable generator side-store critical
138-
sections. A separate deferred-generator witness roots a suspended generator and
139-
checks that no-GIL allocation-failure recovery aborts instead of sweeping while
140-
parallel tracing has deferred the generator's mutable execution buffers to a
141-
world-stopped finish.
138+
sections. Separate deferred-generator and deferred-iterator-helper witnesses
139+
root suspended/mid-helper state and check that no-GIL allocation-failure
140+
recovery aborts instead of sweeping while parallel tracing has deferred mutable
141+
execution/helper buffers to a world-stopped finish.
142142

143143
`zig build threads-test` runs the green WebKit PR-249 allowlist from
144144
`reference/webkit-249/threads-tests`. CI shards the serialized/GIL leg with

src/context.zig

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12818,6 +12818,76 @@ test "parallel_js heap_limit_bytes fails closed with deferred generator tracing"
1281812818
try std.testing.expect(ctx.gc_par_deferred_rounds.load(.monotonic) > before_deferred_rounds);
1281912819
}
1282012820

12821+
test "parallel_js heap_limit_bytes fails closed with deferred iterator helper tracing" {
12822+
// #36 sibling policy witness for Iterator Helper cells. The parallel tracer
12823+
// defers helper state for the same reason as generators: mutable helper
12824+
// fields change around callback/source-iterator execution. No-GIL
12825+
// allocation-failure recovery must therefore abort rather than reporting a
12826+
// safe sweep while a rooted helper remains deferred.
12827+
if (builtin.single_threaded) return error.SkipZigTest;
12828+
const ctx = try Context.createWithTestingOptions(std.testing.allocator, .{
12829+
.enable_threads = true,
12830+
.enable_gc = true,
12831+
.parallel_gc = true,
12832+
.parallel_js = true,
12833+
.heap_limit_bytes = 8 * 1024 * 1024,
12834+
});
12835+
defer ctx.destroy();
12836+
12837+
const prepared = try ctx.evaluate(
12838+
\\(() => {
12839+
\\ const source = Iterator.from([1, 2, 3, 4, 5]);
12840+
\\ globalThis.__deferredRecoveryHelper =
12841+
\\ source.map(x => ({ value: x + 1 })).filter(o => o.value % 2 === 0);
12842+
\\ return globalThis.__deferredRecoveryHelper.next().value.value;
12843+
\\})();
12844+
);
12845+
try std.testing.expectEqual(@as(f64, 2), prepared.asNum());
12846+
12847+
var sibling: jsthread.ThreadRecord = .{ .id = 999, .gil = ctx.gil.?, .ctx = ctx };
12848+
{
12849+
ctx.gil.?.lockApi();
12850+
defer ctx.gil.?.unlockApi();
12851+
try ctx.reserveJsThreadsLocked(1);
12852+
ctx.js_threads.appendAssumeCapacity(&sibling);
12853+
}
12854+
defer {
12855+
ctx.gil.?.lockApi();
12856+
var i: usize = ctx.js_threads.items.len;
12857+
while (i > 0) {
12858+
i -= 1;
12859+
if (ctx.js_threads.items[i] == &sibling) {
12860+
_ = ctx.js_threads.swapRemove(i);
12861+
break;
12862+
}
12863+
}
12864+
ctx.gil.?.unlockApi();
12865+
}
12866+
12867+
const before_attempts = ctx.gc_par_attempts.load(.monotonic);
12868+
const before_collections = ctx.gc_par_collections.load(.monotonic);
12869+
const before_aborts = ctx.gc_par_aborts.load(.monotonic);
12870+
const before_round_aborts = ctx.gc_par_round_limit_aborts.load(.monotonic);
12871+
const before_deferred_rounds = ctx.gc_par_deferred_rounds.load(.monotonic);
12872+
12873+
const gc_saved = gc_mod.setActiveHeap(ctx.gc);
12874+
defer _ = gc_mod.setActiveHeap(gc_saved);
12875+
const ss_saved = stack_scan.enter(@frameAddress());
12876+
defer stack_scan.leave(ss_saved);
12877+
var machine = ctx.interpreter();
12878+
try ctx.pushActiveInterpreter(&machine);
12879+
defer ctx.popActiveInterpreter(&machine);
12880+
const ai_saved = gc_mod.setActiveInterpreter(&machine);
12881+
defer _ = gc_mod.setActiveInterpreter(ai_saved);
12882+
12883+
try std.testing.expect(!ctx.collectForAllocationFailure(&machine));
12884+
try std.testing.expect(ctx.gc_par_attempts.load(.monotonic) > before_attempts);
12885+
try std.testing.expectEqual(before_collections, ctx.gc_par_collections.load(.monotonic));
12886+
try std.testing.expect(ctx.gc_par_aborts.load(.monotonic) > before_aborts);
12887+
try std.testing.expect(ctx.gc_par_round_limit_aborts.load(.monotonic) > before_round_aborts);
12888+
try std.testing.expect(ctx.gc_par_deferred_rounds.load(.monotonic) > before_deferred_rounds);
12889+
}
12890+
1282112891
test "parallel_js (M3): sync wait peers publish roots for mid-script parallel GC" {
1282212892
// Sync property waits, Condition waits, and contended Lock acquisition are
1282312893
// not frozen `gc_parked` peers: they periodically pump tasks between short

0 commit comments

Comments
 (0)