Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -255,3 +255,5 @@
- Name package-target resolution options explicitly to satisfy the Rust Mordant lint without changing resolution behavior.

- Destroy Duplex-backed TLS transports without calling `end()`, preserve wrapped-socket close ordering, and honor half-open shutdown like Node.js. Ports HTTP/2 teardown from [oven-sh/bun#38195](https://github.com/oven-sh/bun/pull/38195) and adapts coverage from [oven-sh/bun#38154](https://github.com/oven-sh/bun/pull/38154); thanks @robobun!

- Verify dead-realm FinalizationRegistry cancellation only after observing collection, while requiring a collected realm and preserving zero callbacks for every collected context. Adapts [oven-sh/bun#44544](https://github.com/oven-sh/bun/pull/44544); thanks @robobun!
22 changes: 17 additions & 5 deletions test/js/node/vm/vm.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2895,32 +2895,44 @@ test.concurrent("a FinalizationRegistry cleanup job is dropped when its context
const nextTurn = () => new Promise(resolve => setImmediate(resolve));
const liveContextCleanedUp = Promise.withResolvers();
let liveContext;
let deadContextCleanups = 0;
const cleanups = [0, 0, 0, 0];
const dropped = [];

function setup() {
// A collection sweeps the first 8 cells of a type itself and leaves the rest for later. ~JSGlobalObject
// cancels the job too, so these contexts are past the first 8 and their registries are not.
const swept = Array.from({ length: 8 }, () => vm.createContext({}));
const contexts = Array.from({ length: 4 }, () => vm.createContext({ onCleanup: () => deadContextCleanups++ }));
const contexts = Array.from(cleanups, (_, index) => vm.createContext({ onCleanup: () => cleanups[index]++ }));
// Each realm strongly holds its sandbox, so a cleared sandbox witness proves that realm was unmarked.
for (const context of contexts) dropped.push(new WeakRef(context));
liveContext = vm.createContext({ onCleanup: liveContextCleanedUp.resolve });
contexts.push(liveContext);
for (const context of contexts) vm.runInContext("globalThis.registry = new FinalizationRegistry(onCleanup)", context);
edenGC(); // Old generation now: the next eden collection leaves them marked.
for (const context of contexts) for (let i = 0; i < 5; i++) context.registry.register({ i }, i);
contexts.length = 0; // A stale reference to this array must not retain every dropped context.
}

await nextTurn().then(setup);
await nextTurn();
const cleanupsBeforeCollection = cleanups.slice();
edenGC(); // The registered objects are dead: every registry posts its cleanup job.
fullGC(); // All contexts but one are dead, and their registries are destroyed.
fullGC(); // Cancel jobs for realms this collection finds dead, before yielding to their cleanup jobs.
const collected = dropped.map(context => context.deref() === undefined);
await liveContextCleanedUp.promise;
await nextTurn(); // A job posted after the live context's has run by now too.
console.log({ deadContextCleanups });
console.log({
cleanupsBeforeCollection,
// One collected realm exercises cancellation; requiring all four assumes a particular stack layout.
someContextCollected: collected.includes(true),
collectedContextsHadNoCleanups: cleanups.every((count, index) => !collected[index] || count === 0),
});
`;
await using proc = Bun.spawn({ cmd: [bunExe(), "-e", fixture], env: bunEnv, stdout: "pipe", stderr: "pipe" });
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect({ stdout, stderr, exitCode }).toEqual({
stdout: "{\n deadContextCleanups: 0,\n}\n",
stdout:
"{\n cleanupsBeforeCollection: [ 0, 0, 0, 0 ],\n someContextCollected: true,\n collectedContextsHadNoCleanups: true,\n}\n",
stderr: "",
exitCode: 0,
});
Expand Down
Loading