FinalizationRegistry callbacks never run
Categories
(Core :: JavaScript: GC, defect)
Tracking
()
People
(Reporter: wingo, Unassigned)
Details
Consider the following JS test file:
let callLater = (()=> {
if (typeof setTimeout !== 'undefined')
return f=>setTimeout(f, 0);
if (typeof enqueueJob !== 'undefined')
return f=>enqueueJob(f);
return f=>f();
})();
let maybeGC = (()=> {
if (typeof gc !== 'undefined')
return gc;
return ()=>{};
})();
let nfinalized = 0;
let finalizers = new FinalizationRegistry(f => { print(`finalized ${f}`); nfinalized++ });
class B {};
class A {
constructor() {
let b = new B;
this.b = b;
finalizers.register(this, b, this);
}
installCallback(f) {
this.b.callback = f;
}
}
// JS shells have a setTimeout implementation that doesn't wait before
// calling the callback; instead it is just called on the next turn,
// ignoring the timeout argument. That's fine -- all we want to do is
// to be able to yield and let other tasks run, so that finalizers can
// run.
async function microsleep() {
await new Promise((resolve, reject) => callLater(resolve));
}
async function allowFinalizersToRun() {
//maybeGC();
await microsleep();
}
async function test() {
for (let j = 0; j < 1e3; j++) {
print(j);
for (let i = 0; i < 1e3; i++) {
let obj = new A(null);
obj.installCallback(() => console.log("hello"));
}
await allowFinalizersToRun();
}
}
async function main() {
await test();
print(`nfinalized: ${nfinalized}`);
}
main()
If you run it from the shell, it shows that no object was finalized. It's also quite slow.
In contrast, V8's d8 does finalize objects, for this test -- some of the objects, if you just run it as-is (passing --harmony-weak-refs
on the command line), or all but one of the objects, if you also pass --expose-gc
, and you uncomment the maybeGC
line.
JSC will also finalize objects. While never invoking finalizers is a legal behavior, I think here it's particularly egregious.
Comment 1•4 years ago
|
||
This is a dup of bug 1542660. All variables are marked as "aliased" in generators/async functions, which leads to keeping obj
alive through the () => console.log("hello")
function.
Reporter | ||
Comment 2•4 years ago
|
||
Hi André, thanks for the info!! That does make sense.
However -- if I split out the allocation and attach-callback business to an inner function, and I call drainJobQueue during the microsleep, then I do indeed get prompt-enough finalization. (The drainJobQueue is necessary it seems because it seems enqueueJob has a higher priority than finalizers. Explicitly calling gc is not necessary.)
Description
•