[heap][test] Fix weakrefs tests for conservative stack scanning

31 out of the 36 JS tests in test/mjsunit/harmony/weakrefs/ rely on
precise GC with the following general pattern: they allocate some
objects, clear all references to them, invoke a GC, then perform
some test that assumes that the GC has reclaimed the objects.
When conservative stack scanning is used, this may fail.

This CL fixes the tests, ensuring that a precise GC will be invoked
when necessary, without scanning the stack. To achieve this, the GC
has to be invoked in asynchronous execution mode, which ensures that
it will be invoked from the event loop without a stack. In some
cases, this change requires a non-trivial change in the tests.

In 5 tests, part of the test's objective was to verify that a weak
reference is not cleared before the end of the turn. In those, it
was not possible to invoke GC asynchronously, as this would
immediately start a new turn. These tests still use synchronous GC
and they have been modified, if necessary, to allow for CSS (i.e.,
to not test that all possible garbage is reclaimed after a
sequential GC). Because of CSS, these tests may not always test
everything that they were intended to.

Tests with trivial fix:

- cleanup-from-different-realm
- cleanup
- cleanup-proxy-from-different-realm
- cleanupsome-2
- cleanupsome-after-unregister
- cleanupsome
- finalizationregistry-keeps-holdings-alive
- multiple-dirty-finalization-groups
- stress-finalizationregistry-dirty-enqueue
- undefined-holdings
- unregister-after-cleanup
- unregister-before-cleanup
- unregister-called-twice
- unregister-inside-cleanup2
- unregister-inside-cleanup3
- unregister-inside-cleanup
- unregister-many
- unregister-when-cleanup-already-scheduled
- weak-cell-basics

Tests with non-trivial fixes; same logic but very restructured:

- cleanup-is-not-a-microtask:
- cleanup-on-detached-realm
- finalizationregistry-scheduled-for-cleanup-multiple-times
- finalizationregistry-independent-lifetime
- finalizationregistry-independent-lifetime-multiple
- reentrant-gc-from-cleanup
- symbol-in-finalizationregistry
  (was 2nd part of former symbol-as-weakref-target-gc)
- weak-unregistertoken

Tests with non-trivial fixes; same logic, restructured, using
synchronous GC:

- finalizationregistry-and-weakref
- symbol-as-weakref-target-gc
  (was 1st part of former symbol-as-weakref-target-gc)
- two-weakrefs
- weakref-creation-keeps-alive
- weakref-deref-keeps-alive

Bug: v8:13257
Bug: v8:13662
Change-Id: I53586bd16cdb98fa976e1fa798ef498bdf286238
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/4191774
Reviewed-by: Marja Hölttä <marja@chromium.org>
Reviewed-by: Shu-yu Guo <syg@chromium.org>
Commit-Queue: Nikolaos Papaspyrou <nikolaos@chromium.org>
Cr-Commit-Position: refs/heads/main@{#85477}
This commit is contained in:
Nikolaos Papaspyrou 2023-01-25 10:47:51 +01:00 committed by V8 LUCI CQ
parent d7816e41da
commit 20a954f4bc
32 changed files with 1085 additions and 817 deletions

View File

@ -4,31 +4,36 @@
// Flags: --expose-gc --noincremental-marking
let r = Realm.create();
(async function () {
let cleanup = Realm.eval(r, "var stored_global; function cleanup() { stored_global = globalThis; } cleanup");
let realm_global_this = Realm.eval(r, "globalThis");
let r = Realm.create();
let fg = new FinalizationRegistry(cleanup);
let cleanup = Realm.eval(r, "var stored_global; function cleanup() { stored_global = globalThis; } cleanup");
let realm_global_this = Realm.eval(r, "globalThis");
// Create an object and a register it in the FinalizationRegistry. The object needs
// to be inside a closure so that we can reliably kill them!
let weak_cell;
let fg = new FinalizationRegistry(cleanup);
(function() {
let object = {};
fg.register(object, {});
// Create an object and register it in the FinalizationRegistry. The object needs
// to be inside a closure so that we can reliably kill them!
(function () {
let object = {};
fg.register(object, {});
// Object goes out of scope.
})();
// We need to invoke GC asynchronously and wait for it to finish, so that
// it doesn't need to scan the stack. Otherwise, the objects may not be
// reclaimed because of conservative stack scanning and the test may not
// work as intended.
await gc({ type: 'major', execution: 'async' });
// Assert that the cleanup function was called in its Realm.
let timeout_func = function () {
let stored_global = Realm.eval(r, "stored_global;");
assertNotEquals(stored_global, globalThis);
assertEquals(stored_global, realm_global_this);
}
setTimeout(timeout_func, 0);
// object goes out of scope.
})();
gc();
// Assert that the cleanup function was called in its Realm.
let timeout_func = function() {
let stored_global = Realm.eval(r, "stored_global;");
assertNotEquals(stored_global, globalThis);
assertEquals(stored_global, realm_global_this);
}
setTimeout(timeout_func, 0);

View File

@ -2,60 +2,55 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --expose-gc --noincremental-marking --allow-natives-syntax
// Flags: --expose-gc --noincremental-marking
// This test asserts that the cleanup function call, scheduled by GC, is a
// microtask and not a normal task.
// This test asserts that the cleanup function call, scheduled by GC, is
// not a microtask but a normal task.
// Inside a microtask, cause GC (which should schedule the cleanup as
// microtask). lso schedule another microtask. Assert that the cleanup
// function ran before the other microtask.
(async function () {
let cleanedUp = false;
let microtaskInvoked = false;
const microtask = () => {
assertFalse(cleanedUp);
assertFalse(microtaskInvoked);
microtaskInvoked = true;
};
function scheduleMicrotask(func) {
Promise.resolve().then(func);
}
let cleanedUp = false;
const cleanup = (holdings) => {
assertFalse(cleanedUp);
assertTrue(microtaskInvoked);
cleanedUp = true;
};
let log = [];
const fg = new FinalizationRegistry(cleanup);
let cleanup = (holdings) => {
cleanedUp = true;
}
(function() {
// Use a closure here to avoid other references to object which might keep
// it alive (e.g., stack frames pointing to it).
const object = {};
fg.register(object, {});
})();
let fg = new FinalizationRegistry(cleanup);
let o = null;
// The GC will schedule the cleanup as a regular task.
// We need to invoke GC asynchronously and wait for it to finish, so that
// it doesn't need to scan the stack. Otherwise, the objects may not be
// reclaimed because of conservative stack scanning and the test may not
// work as intended.
await gc({ type: 'major', execution: 'async' });
assertFalse(cleanedUp);
// Schedule the microtask.
Promise.resolve().then(microtask);
// Nothing else hasn't been called yet, as we're still in synchronous
// execution.
assertFalse(microtaskInvoked);
assertFalse(cleanedUp);
// The microtask and the cleanup callbacks will verify that these two are
// invoked in the right order: microtask -> cleanup.
setTimeout(() => { assertTrue(cleanedUp); }, 0);
(function() {
// Use a closure here to avoid other references to o which might keep it alive
// (e.g., stack frames pointing to it).
o = {};
fg.register(o, {});
})();
let microtask = function() {
log.push("first_microtask");
// cause GC during a microtask
o = null;
gc();
}
assertFalse(cleanedUp);
// enqueue microtask that triggers GC
Promise.resolve().then(microtask);
// but cleanup callback hasn't been called yet, as we're still in
// synchronous execution
assertFalse(cleanedUp);
// flush the microtask queue to run the microtask that triggers GC
%PerformMicrotaskCheckpoint();
// still no cleanup callback, because it runs after as a separate task
assertFalse(cleanedUp);
setTimeout(() => {
assertTrue(cleanedUp);
}, 0);

View File

@ -4,35 +4,66 @@
// Flags: --expose-gc --noincremental-marking
let cleanedUp = false;
let r = Realm.create();
let FG = Realm.eval(r, "FinalizationRegistry");
Realm.detachGlobal(r);
(async function () {
const r = Realm.create();
const FG = Realm.eval(r, "FinalizationRegistry");
Realm.detachGlobal(r);
const cleanup_not_run = function (holdings) {
assertUnreachable();
}
let fg_not_run = new FG(cleanup_not_run);
(function () {
const object = {};
fg_not_run.register(object, "first");
// Object becomes unreachable.
})();
let cleanedUp = false;
let fg_run;
// Schedule a GC, which will schedule fg_not_run for cleanup.
// Here and below, we need to invoke GC asynchronously and wait for it to
// finish, so that it doesn't need to scan the stack. Otherwise, the objects
// may not be reclaimed because of conservative stack scanning and the test
// may not work as intended.
let task_1_gc = (async function () {
await gc({ type: 'major', execution: 'async' });
// Disposing the realm cancels the already scheduled fg_not_run's finalizer.
Realm.dispose(r);
const cleanup = function (holdings) {
assertEquals(holdings, "second");
assertFalse(cleanedUp);
cleanedUp = true;
}
fg_run = new FG(cleanup);
// FGs that are alive after disposal can still schedule tasks.
(function () {
const object = {};
fg_run.register(object, "second");
// Object becomes unreachable.
})();
})();
// Schedule a second GC for execution after that, which will now schedule
// fg_run for cleanup.
let task_2_gc = (async function () {
await gc({ type: 'major', execution: 'async' });
// Check that the cleanup task has had the chance to run yet.
assertFalse(cleanedUp);
})();
// Wait for the two GCs to be executed.
await task_1_gc;
await task_2_gc;
// Give the cleanup task a chance to run and check it worked correctly.
setTimeout(function () { assertTrue(cleanedUp); }, 0);
let fg_not_run = new FG(() => {
assertUnreachable();
});
(() => {
fg_not_run.register({});
})();
gc();
// Disposing the realm cancels the already scheduled fg_not_run's finalizer.
Realm.dispose(r);
let fg = new FG(()=> {
cleanedUp = true;
});
// FGs that are alive after disposal can still schedule tasks.
(() => {
let object = {};
fg.register(object, {});
// object becomes unreachable.
})();
gc();
setTimeout(function() { assertTrue(cleanedUp); }, 0);

View File

@ -4,31 +4,39 @@
// Flags: --expose-gc --noincremental-marking
let r = Realm.create();
(async function () {
let cleanup = Realm.eval(r, "var stored_global; let cleanup = new Proxy(function() { stored_global = globalThis;}, {}); cleanup");
let realm_global_this = Realm.eval(r, "globalThis");
let r = Realm.create();
let fg = new FinalizationRegistry(cleanup);
let cleanup = Realm.eval(r, "var stored_global; let cleanup = new Proxy(function() { stored_global = globalThis;}, {}); cleanup");
let realm_global_this = Realm.eval(r, "globalThis");
// Create an object and register it in the FinalizationRegistry. The object needs
// to be inside a closure so that we can reliably kill them!
let weak_cell;
let fg = new FinalizationRegistry(cleanup);
(function() {
let object = {};
fg.register(object, "holdings");
// Create an object and register it in the FinalizationRegistry. The object needs
// to be inside a closure so that we can reliably kill them!
let weak_cell;
(function () {
let object = {};
fg.register(object, "holdings");
// object goes out of scope.
})();
// We need to invoke GC asynchronously and wait for it to finish, so that
// it doesn't need to scan the stack. Otherwise, the objects may not be
// reclaimed because of conservative stack scanning and the test may not
// work as intended.
await gc({ type: 'major', execution: 'async' });
// Assert that the cleanup function was called in its Realm.
let timeout_func = function () {
let stored_global = Realm.eval(r, "stored_global;");
assertNotEquals(stored_global, globalThis);
assertEquals(stored_global, realm_global_this);
}
setTimeout(timeout_func, 0);
// object goes out of scope.
})();
gc();
// Assert that the cleanup function was called in its Realm.
let timeout_func = function() {
let stored_global = Realm.eval(r, "stored_global;");
assertNotEquals(stored_global, globalThis);
assertEquals(stored_global, realm_global_this);
}
setTimeout(timeout_func, 0);

View File

@ -4,48 +4,56 @@
// Flags: --expose-gc --noincremental-marking
let cleanup_called = 0;
let holdings_list = [];
let cleanup = function(holdings) {
holdings_list.push(holdings);
cleanup_called++;
}
(async function () {
let fg = new FinalizationRegistry(cleanup);
let o1 = {};
let o2 = {};
// Ignition holds references to objects in temporary registers. These will be
// released when the function exits. So only access o inside a function to
// prevent any references to objects in temporary registers when a gc is
(function() {
fg.register(o1, 1);
fg.register(o2, 2);
})();
gc();
assertEquals(cleanup_called, 0);
// Drop the last references to o1 and o2.
(function() {
o1 = null;
o2 = null;
})();
// GC will reclaim the target objects; the cleanup function will be called the
// next time we enter the event loop.
gc();
assertEquals(cleanup_called, 0);
let timeout_func = function() {
assertEquals(cleanup_called, 2);
assertEquals(holdings_list.length, 2);
if (holdings_list[0] == 1) {
assertEquals(holdings_list[1], 2);
} else {
assertEquals(holdings_list[0], 2);
assertEquals(holdings_list[1], 1);
let cleanup_called = 0;
let holdings_list = [];
let cleanup = function (holdings) {
holdings_list.push(holdings);
cleanup_called++;
}
}
setTimeout(timeout_func, 0);
let fg = new FinalizationRegistry(cleanup);
let o1 = {};
let o2 = {};
// Ignition holds references to objects in temporary registers. These will be
// released when the function exits. So only access o inside a function to
// prevent any references to objects in temporary registers when a gc is
(function () {
fg.register(o1, 1);
fg.register(o2, 2);
})();
// Here and below, we need to invoke GC asynchronously and wait for it to
// finish, so that it doesn't need to scan the stack. Otherwise, the objects
// may not be reclaimed because of conservative stack scanning and the test
// may not work as intended.
await gc({ type: 'major', execution: 'async' });
assertEquals(cleanup_called, 0);
// Drop the last references to o1 and o2.
(function () {
o1 = null;
o2 = null;
})();
// GC will reclaim the target objects; the cleanup function will be called the
// next time we enter the event loop.
await gc({ type: 'major', execution: 'async' });
assertEquals(cleanup_called, 0);
let timeout_func = function () {
assertEquals(cleanup_called, 2);
assertEquals(holdings_list.length, 2);
if (holdings_list[0] == 1) {
assertEquals(holdings_list[1], 2);
} else {
assertEquals(holdings_list[0], 2);
assertEquals(holdings_list[1], 1);
}
}
setTimeout(timeout_func, 0);
})();

View File

@ -4,26 +4,34 @@
// Flags: --harmony-weak-refs-with-cleanup-some --expose-gc --noincremental-marking --allow-natives-syntax
let cleanup_count = 0;
let cleanup_holdings = [];
let cleanup = function(holdings) {
cleanup_holdings.push(holdings);
++cleanup_count;
}
(async function () {
let fg = new FinalizationRegistry(cleanup);
(function() {
let o = {};
fg.register(o, "holdings");
let cleanup_count = 0;
let cleanup_holdings = [];
let cleanup = function (holdings) {
cleanup_holdings.push(holdings);
++cleanup_count;
}
let fg = new FinalizationRegistry(cleanup);
(function () {
let o = {};
fg.register(o, "holdings");
assertEquals(0, cleanup_count);
})();
// GC will detect o as dead.
// We need to invoke GC asynchronously and wait for it to finish, so that
// it doesn't need to scan the stack. Otherwise, the objects may not be
// reclaimed because of conservative stack scanning and the test may not
// work as intended.
await gc({ type: 'major', execution: 'async' });
// passing no callback, should trigger cleanup function
fg.cleanupSome();
assertEquals(1, cleanup_count);
assertEquals(1, cleanup_holdings.length);
assertEquals("holdings", cleanup_holdings[0]);
assertEquals(0, cleanup_count);
})();
// GC will detect o as dead.
gc();
// passing no callback, should trigger cleanup function
fg.cleanupSome();
assertEquals(1, cleanup_count);
assertEquals(1, cleanup_holdings.length);
assertEquals("holdings", cleanup_holdings[0]);

View File

@ -4,31 +4,39 @@
// Flags: --harmony-weak-refs-with-cleanup-some --expose-gc --noincremental-marking
let cleanup_count = 0;
let cleanup_holdings = [];
let cleanup = function(holdings) {
cleanup_holdings.push(holdings);
++cleanup_count;
}
(async function () {
let fg = new FinalizationRegistry(cleanup);
let key = {"k": "this is the key"};
(function() {
let o = {};
weak_cell = fg.register(o, "holdings", key);
let cleanup_count = 0;
let cleanup_holdings = [];
let cleanup = function (holdings) {
cleanup_holdings.push(holdings);
++cleanup_count;
}
let fg = new FinalizationRegistry(cleanup);
let key = { "k": "this is the key" };
(function () {
let o = {};
weak_cell = fg.register(o, "holdings", key);
// cleanupSome won't do anything since there are no reclaimed targets.
fg.cleanupSome();
assertEquals(0, cleanup_count);
return o;
})();
// GC will detect the WeakCell as dirty.
// We need to invoke GC asynchronously and wait for it to finish, so that
// it doesn't need to scan the stack. Otherwise, the objects may not be
// reclaimed because of conservative stack scanning and the test may not
// work as intended.
await gc({ type: 'major', execution: 'async' });
// Unregister the tracked object just before calling cleanupSome.
fg.unregister(key);
// cleanupSome won't do anything since there are no reclaimed targets.
fg.cleanupSome();
assertEquals(0, cleanup_count);
return o;
})();
// GC will detect the WeakCell as dirty.
gc();
// Unregister the tracked object just before calling cleanupSome.
fg.unregister(key);
fg.cleanupSome();
assertEquals(0, cleanup_count);

View File

@ -4,31 +4,39 @@
// Flags: --harmony-weak-refs-with-cleanup-some --expose-gc --noincremental-marking --allow-natives-syntax
let cleanup_count = 0;
let cleanup_holdings = [];
let cleanup = function(holdings) {
%AbortJS("shouldn't be called");
}
(async function () {
let cleanup2 = function(holdings) {
cleanup_holdings.push(holdings);
++cleanup_count;
}
let cleanup_count = 0;
let cleanup_holdings = [];
let cleanup = function (holdings) {
%AbortJS("shouldn't be called");
}
let fg = new FinalizationRegistry(cleanup);
(function() {
let o = {};
fg.register(o, "holdings");
let cleanup2 = function (holdings) {
cleanup_holdings.push(holdings);
++cleanup_count;
}
let fg = new FinalizationRegistry(cleanup);
(function () {
let o = {};
fg.register(o, "holdings");
// cleanupSome won't do anything since there are no reclaimed targets.
fg.cleanupSome(cleanup2);
assertEquals(0, cleanup_count);
})();
// GC will detect o as dead.
// We need to invoke GC asynchronously and wait for it to finish, so that
// it doesn't need to scan the stack. Otherwise, the objects may not be
// reclaimed because of conservative stack scanning and the test may not
// work as intended.
await gc({ type: 'major', execution: 'async' });
// cleanupSome won't do anything since there are no reclaimed targets.
fg.cleanupSome(cleanup2);
assertEquals(0, cleanup_count);
assertEquals(1, cleanup_count);
assertEquals(1, cleanup_holdings.length);
assertEquals("holdings", cleanup_holdings[0]);
})();
// GC will detect o as dead.
gc();
fg.cleanupSome(cleanup2);
assertEquals(1, cleanup_count);
assertEquals(1, cleanup_holdings.length);
assertEquals("holdings", cleanup_holdings[0]);

View File

@ -4,39 +4,46 @@
// Flags: --expose-gc --noincremental-marking
let cleanup_called = false;
let cleanup = function(holdings) {
assertFalse(cleanup_called);
let holdings_list = [];
holdings_list.push(holdings);
assertEquals(1, holdings_list.length);
assertEquals("holdings", holdings_list[0]);
cleanup_called = true;
}
(async function () {
let fg = new FinalizationRegistry(cleanup);
let weak_ref;
(function() {
let o = {};
weak_ref = new WeakRef(o);
fg.register(o, "holdings");
})();
let cleanup_called = false;
const cleanup = function(holdings) {
assertFalse(cleanup_called);
assertEquals("holdings", holdings);
cleanup_called = true;
}
// Since the WeakRef was created during this turn, it is not cleared by GC. The
// pointer inside the FinalizationRegistry is not cleared either, since the WeakRef
// keeps the target object alive.
gc();
(function() {
assertNotEquals(undefined, weak_ref.deref());
})();
const fg = new FinalizationRegistry(cleanup);
let weak_ref;
(function() {
const o = {};
weak_ref = new WeakRef(o);
fg.register(o, "holdings");
})();
// Trigger gc in next task
setTimeout(() => {
// Since the WeakRef was created during this turn, it is not cleared by GC. The
// pointer inside the FinalizationRegistry is not cleared either, since the WeakRef
// keeps the target object alive.
// Here we invoke GC synchronously and, with conservative stack scanning, there is
// a chance that the object is not reclaimed now. In any case, the WeakRef should
// not be cleared.
gc();
// Check that cleanup callback was called in a follow up task
setTimeout(() => {
assertTrue(cleanup_called);
assertEquals(undefined, weak_ref.deref());
}, 0);
}, 0);
assertNotEquals(undefined, weak_ref.deref());
assertFalse(cleanup_called);
// Trigger GC in next task. Now the WeakRef is cleared but the cleanup has
// not been called yet.
// We need to invoke GC asynchronously and wait for it to finish, so that
// it doesn't need to scan the stack. Otherwise, the objects may not be
// reclaimed because of conservative stack scanning and the test may not
// work as intended.
await gc({ type: 'major', execution: 'async' });
assertEquals(undefined, weak_ref.deref());
assertFalse(cleanup_called);
// Check that the cleanup callback was called in a follow up task.
setTimeout(() => { assertTrue(cleanup_called); }, 0);
})();

View File

@ -4,29 +4,49 @@
// Flags: --expose-gc --noincremental-marking --no-concurrent-inlining
let cleanup_called = false;
function cleanup(holdings) {
cleanup_called = true;
};
let cleanup_called_2 = false;
function cleanup2(holdings) {
cleanup_called_2 = true;
};
let fg = new FinalizationRegistry(cleanup);
(function() {
let fg2 = new FinalizationRegistry(cleanup2);
(function() {
fg.register({}, {});
fg2.register({}, {});
(async function () {
let cleanup_called = false;
function cleanup(holdings) {
cleanup_called = true;
};
let cleanup_called_2 = false;
function cleanup2(holdings) {
cleanup_called_2 = true;
};
const fg = new FinalizationRegistry(cleanup);
let task_1_gc = (async function () {
const fg2 = new FinalizationRegistry(cleanup2);
(function () {
fg.register({}, "holdings1");
fg2.register({}, "holdings2");
})();
// Schedule fg and fg2 for cleanup.
await gc({ type: 'major', execution: 'async' });
assertFalse(cleanup_called);
assertFalse(cleanup_called_2);
})();
// Schedule a task to collect fg2, but fg is still alive.
let task_2_gc = (async function () {
await gc({ type: 'major', execution: 'async' });
assertFalse(cleanup_called);
assertFalse(cleanup_called_2);
})();
// Schedule fg and fg2 for cleanup.
gc();
// Wait for the two GC tasks to be executed.
await task_1_gc;
await task_2_gc;
// Check that only the cleanup for fg will be called.
setTimeout(function() {
assertTrue(cleanup_called);
assertFalse(cleanup_called_2);
}, 0);
})();
// Collect fg2, but fg is still alive.
gc();
setTimeout(function() {
assertTrue(cleanup_called);
assertFalse(cleanup_called_2);
}, 0);

View File

@ -4,22 +4,38 @@
// Flags: --expose-gc --noincremental-marking --no-concurrent-recompilation
let cleanup_called = false;
function cleanup(holdings) {
cleanup_called = true;
};
(function() {
let fg = new FinalizationRegistry(cleanup);
(function() {
let x = {};
fg.register(x, {});
x = null;
(async function () {
let cleanup_called = false;
function cleanup(holdings) {
cleanup_called = true;
};
let task_1_gc = (async function () {
const fg = new FinalizationRegistry(cleanup);
(function () {
let x = {};
fg.register(x, "holdings");
x = null;
})();
// Schedule fg for cleanup.
await gc({ type: 'major', execution: 'async' });
assertFalse(cleanup_called);
})();
// Schedule fg for cleanup.
gc();
// Schedule a task to collect fg, which should result in cleanup not called.
let task_2_gc = (async function () {
await gc({ type: 'major', execution: 'async' });
assertFalse(cleanup_called);
})();
// Wait for the two GC tasks to be executed.
await task_1_gc;
await task_2_gc;
// Check that the cleanup will not be called.
setTimeout(function () { assertFalse(cleanup_called); }, 0);
})();
// Collect fg, which should result in cleanup not called.
gc();
setTimeout(function() { assertFalse(cleanup_called); }, 0);

View File

@ -4,40 +4,48 @@
// Flags: --expose-gc --noincremental-marking
let cleanup_called = false;
let holdings_list = [];
let cleanup = function(holdings) {
(async function () {
let cleanup_called = false;
let holdings_list = [];
let cleanup = function (holdings) {
assertFalse(cleanup_called);
holdings_list.push(holdings);
cleanup_called = true;
}
let fg = new FinalizationRegistry(cleanup);
let o1 = {};
let holdings = { 'a': 'this is the holdings object' };
// Ignition holds references to objects in temporary registers. These will be
// released when the function exits. So only access o inside a function to
// prevent any references to objects in temporary registers when a gc is
// triggered.
(() => { fg.register(o1, holdings); })()
// Here and below, we need to invoke GC asynchronously and wait for it to
// finish, so that it doesn't need to scan the stack. Otherwise, the objects
// may not be reclaimed because of conservative stack scanning and the test
// may not work as intended.
await gc({ type: 'major', execution: 'async' });
assertFalse(cleanup_called);
holdings_list.push(holdings);
cleanup_called = true;
}
let fg = new FinalizationRegistry(cleanup);
let o1 = {};
let holdings = {'a': 'this is the holdings object'};
// Drop the last references to o1.
(() => { o1 = null; })()
// Ignition holds references to objects in temporary registers. These will be
// released when the function exits. So only access o inside a function to
// prevent any references to objects in temporary registers when a gc is
// triggered.
(() => {fg.register(o1, holdings);})()
// Drop the last reference to the holdings. The FinalizationRegistry keeps it
// alive, so the cleanup function will be called as normal.
holdings = null;
await gc({ type: 'major', execution: 'async' });
assertFalse(cleanup_called);
gc();
assertFalse(cleanup_called);
let timeout_func = function () {
assertTrue(cleanup_called);
assertEquals(holdings_list.length, 1);
assertEquals(holdings_list[0].a, "this is the holdings object");
}
// Drop the last references to o1.
(() => {o1 = null;})()
setTimeout(timeout_func, 0);
// Drop the last reference to the holdings. The FinalizationRegistry keeps it
// alive, so the cleanup function will be called as normal.
holdings = null;
gc();
assertFalse(cleanup_called);
let timeout_func = function() {
assertTrue(cleanup_called);
assertEquals(holdings_list.length, 1);
assertEquals(holdings_list[0].a, "this is the holdings object");
}
setTimeout(timeout_func, 0);
})();

View File

@ -5,63 +5,78 @@
// Flags: --expose-gc --noincremental-marking
// Flags: --no-stress-flush-code
let cleanup0_call_count = 0;
let cleanup0_holdings_count = 0;
(async function () {
let cleanup1_call_count = 0;
let cleanup1_holdings_count = 0;
let cleanup0_call_count = 0;
let cleanup1_call_count = 0;
let cleanup0 = function(holdings) {
++cleanup0_holdings_count;
++cleanup0_call_count;
}
let cleanup0 = function (holdings) {
++cleanup0_call_count;
}
let cleanup1 = function(holdings) {
++cleanup1_holdings_count;
++cleanup1_call_count;
}
let cleanup1 = function (holdings) {
++cleanup1_call_count;
}
let fg0 = new FinalizationRegistry(cleanup0);
let fg1 = new FinalizationRegistry(cleanup1);
let fg0 = new FinalizationRegistry(cleanup0);
let fg1 = new FinalizationRegistry(cleanup1);
// Register 1 weak reference for each FinalizationRegistry and kill the objects they point to.
(function() {
// The objects need to be inside a closure so that we can reliably kill them.
let objects = [];
objects[0] = {};
objects[1] = {};
// Register 1 weak reference for each FinalizationRegistry and kill the
// objects they point to.
(function () {
// The objects need to be inside a closure so that we can reliably kill
// them.
let objects = [];
objects[0] = {};
objects[1] = {};
fg0.register(objects[0], "holdings0-0");
fg1.register(objects[1], "holdings1-0");
// Drop the references to the objects.
objects = [];
})();
fg0.register(objects[0], "holdings0-0");
fg1.register(objects[1], "holdings1-0");
// Schedule a GC, which will schedule both fg0 and fg1 for cleanup.
// Here and below, we need to invoke GC asynchronously and wait for it to
// finish, so that it doesn't need to scan the stack. Otherwise, the objects
// may not be reclaimed because of conservative stack scanning and the test
// may not work as intended.
let task_1_gc = (async function () {
await gc({ type: 'major', execution: 'async' });
// Before the cleanup task has a chance to run, do the same thing again, so
// both FinalizationRegistries are (again) scheduled for cleanup. This has to
// be a IIFE function (so that we can reliably kill the objects) so we cannot
// use the same function as before.
(function () {
let objects = [];
objects[0] = {};
objects[1] = {};
fg0.register(objects[0], "holdings0-1");
fg1.register(objects[1], "holdings1-1");
objects = [];
})();
})();
// Schedule a second GC for execution after that, which will again schedule
// both fg0 and fg1 for cleanup.
let task_2_gc = (async function () {
await gc({ type: 'major', execution: 'async' });
// Check that no cleanup task has had the chance to run yet.
assertEquals(0, cleanup0_call_count);
assertEquals(0, cleanup1_call_count);
})();
// Wait for the two GCs to be executed.
await task_1_gc;
await task_2_gc;
let timeout_func = function () {
assertEquals(2, cleanup0_call_count);
assertEquals(2, cleanup1_call_count);
}
// Give the cleanup task a chance to run and check it worked correctly.
setTimeout(timeout_func, 0);
// Drop the references to the objects.
objects = [];
})();
// Will schedule both fg0 and fg1 for cleanup.
gc();
// Before the cleanup task has a chance to run, do the same thing again, so both
// FinalizationRegistries are (again) scheduled for cleanup. This has to be a IIFE function
// (so that we can reliably kill the objects) so we cannot use the same function
// as before.
(function() {
let objects = [];
objects[0] = {};
objects[1] = {};
fg0.register(objects[0], "holdings0-1");
fg1.register(objects[1], "holdings1-1");
objects = [];
})();
gc();
let timeout_func = function() {
assertEquals(2, cleanup0_call_count);
assertEquals(2, cleanup0_holdings_count);
assertEquals(2, cleanup1_call_count);
assertEquals(2, cleanup1_holdings_count);
}
// Give the cleanup task a chance to run.
setTimeout(timeout_func, 0);

View File

@ -4,37 +4,42 @@
// Flags: --expose-gc --noincremental-marking
let cleanup_call_count = 0;
let cleanup_holdings_count = 0;
let cleanup = function(holdings) {
++cleanup_holdings_count;
++cleanup_call_count;
}
(async function () {
let fg1 = new FinalizationRegistry(cleanup);
let fg2 = new FinalizationRegistry(cleanup);
let cleanup_call_count = 0;
let cleanup = function (holdings) {
++cleanup_call_count;
}
// Create two objects and register them in FinalizationRegistries. The objects need
// to be inside a closure so that we can reliably kill them!
let fg1 = new FinalizationRegistry(cleanup);
let fg2 = new FinalizationRegistry(cleanup);
(function() {
let object1 = {};
fg1.register(object1, "holdings1");
// Create two objects and register them in FinalizationRegistries. The objects need
// to be inside a closure so that we can reliably kill them!
let object2 = {};
fg2.register(object2, "holdings2");
(function () {
let object1 = {};
fg1.register(object1, "holdings1");
let object2 = {};
fg2.register(object2, "holdings2");
// object1 and object2 go out of scope.
})();
// This GC will discover dirty WeakCells and schedule cleanup.
// We need to invoke GC asynchronously and wait for it to finish, so that
// it doesn't need to scan the stack. Otherwise, the objects may not be
// reclaimed because of conservative stack scanning and the test may not
// work as intended.
await gc({ type: 'major', execution: 'async' });
assertEquals(0, cleanup_call_count);
// Assert that the cleanup function was called.
let timeout_func = function () {
assertEquals(2, cleanup_call_count);
}
setTimeout(timeout_func, 0);
// object1 and object2 go out of scope.
})();
// This GC will discover dirty WeakCells and schedule cleanup.
gc();
assertEquals(0, cleanup_call_count);
// Assert that the cleanup function was called.
let timeout_func = function() {
assertEquals(2, cleanup_call_count);
assertEquals(2, cleanup_holdings_count);
}
setTimeout(timeout_func, 0);

View File

@ -4,20 +4,27 @@
// Flags: --expose-gc --noincremental-marking
let call_count = 0;
let reentrant_gc = function(holdings) {
gc();
call_count++;
}
(async function () {
let fg = new FinalizationRegistry(reentrant_gc);
let call_count = 0;
const reentrant_gc = function (holdings) {
gc();
call_count++;
}
(function() {
fg.register({}, 42);
})();
const fg = new FinalizationRegistry(reentrant_gc);
gc();
(function () {
fg.register({}, 42);
})();
setTimeout(function() {
assertEquals(1, call_count);
}, 0);
// We need to invoke GC asynchronously and wait for it to finish, so that
// it doesn't need to scan the stack. Otherwise, the objects may not be
// reclaimed because of conservative stack scanning and the test may not
// work as intended.
await gc({ type: 'major', execution: 'async' });
assertEquals(0, call_count);
setTimeout(function () { assertEquals(1, call_count); }, 0);
})();

View File

@ -4,33 +4,41 @@
// Flags: --stress-compaction --expose-gc
// Test that the dirty FinalizationRegistries that are enqueued during GC have
// their slots correctly recorded by the GC.
(async function () {
// 1) Create many JSFinalizationRegistry objects so that they span several pages
// (page size is 256kb).
let registries = [];
for (let i = 0; i < 1024 * 8; i++) {
registries.push(new FinalizationRegistry(() => {}));
}
// Test that the dirty FinalizationRegistries that are enqueued during GC have
// their slots correctly recorded by the GC.
// 2) Force two GCs to ensure that JSFinalizatonRegistry objects are tenured.
gc();
gc();
// 1) Create many JSFinalizationRegistry objects so that they span several pages
// (page size is 256kb).
let registries = [];
for (let i = 0; i < 1024 * 8; i++) {
registries.push(new FinalizationRegistry(() => { }));
}
// 2) Force two GCs to ensure that JSFinalizatonRegistry objects are tenured.
// Here and below, we need to invoke GC asynchronously and wait for it to
// finish, so that it doesn't need to scan the stack. Otherwise, the objects
// may not be reclaimed because of conservative stack scanning and the test
// may not work as intended.
await gc({ type: 'major', execution: 'async' });
await gc({ type: 'major', execution: 'async' });
// 3) In a function: create a dummy target and register it in all
// JSFinalizatonRegistry objects.
(function () {
let garbage = {};
registries.forEach((fr) => {
fr.register(garbage, 42);
});
garbage = null;
})();
// 4) Outside the function where the target is unreachable: force GC to collect
// the object.
await gc({ type: 'major', execution: 'async' });
// 5) Force another GC to test that the slot was correctly updated.
await gc({ type: 'major', execution: 'async' });
// 3) In a function: create a dummy target and register it in all
// JSFinalizatonRegistry objects.
(function() {
let garbage = {};
registries.forEach((fr) => {
fr.register(garbage, 42);
});
garbage = null;
})();
// 4) Outside the function where the target is unreachable: force GC to collect
// the object.
gc();
// 5) Force another GC to test that the slot was correctly updated.
gc();

View File

@ -4,36 +4,29 @@
// Flags: --harmony-symbol-as-weakmap-key --expose-gc --noincremental-marking
(function TestWeakRefWithSymbolGC() {
let weakRef;
{
const innerKey = Symbol('123');
weakRef = new WeakRef(innerKey);
}
// Since the WeakRef was created during this turn, it is not cleared by GC.
gc();
assertNotEquals(undefined, weakRef.deref());
// Next task.
setTimeout(() => {
gc();
assertEquals(undefined, weakRef.deref());
}, 0);
})();
(async function () {
(function TestFinalizationRegistryWithSymbolGC() {
let cleanUpCalled = false;
const fg = new FinalizationRegistry((target) => {
assertEquals('123', target);
cleanUpCalled = true;
});
let weakRef;
(function () {
const innerKey = Symbol('123');
fg.register(innerKey, '123');
weakRef = new WeakRef(innerKey);
})();
// Since the WeakRef was created during this turn, it is not cleared by GC.
// Here we invoke GC synchronously and, with conservative stack scanning, there is
// a chance that the object is not reclaimed now. In any case, the WeakRef should
// not be cleared.
gc();
assertFalse(cleanUpCalled);
// Check that cleanup callback was called in a follow up task.
setTimeout(() => {
assertTrue(cleanUpCalled);
}, 0);
assertNotEquals(undefined, weakRef.deref());
// Trigger GC again in next task. Now the WeakRef is cleared.
// We need to invoke GC asynchronously and wait for it to finish, so that
// it doesn't need to scan the stack. Otherwise, the objects may not be
// reclaimed because of conservative stack scanning and the test may not
// work as intended.
await gc({ type: 'major', execution: 'async' });
assertEquals(undefined, weakRef.deref());
})();

View File

@ -0,0 +1,30 @@
// Copyright 2023 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --harmony-symbol-as-weakmap-key --expose-gc --noincremental-marking
(async function () {
let cleanUpCalled = false;
const fg = new FinalizationRegistry((target) => {
assertEquals('123', target);
cleanUpCalled = true;
});
(function () {
const innerKey = Symbol('123');
fg.register(innerKey, '123');
})();
// We need to invoke GC asynchronously and wait for it to finish, so that
// it doesn't need to scan the stack. Otherwise, the objects may not be
// reclaimed because of conservative stack scanning and the test may not
// work as intended.
await gc({ type: 'major', execution: 'async' });
assertFalse(cleanUpCalled);
// Check that cleanup callback was called in a follow up task.
setTimeout(() => { assertTrue(cleanUpCalled); }, 0);
})();

View File

@ -14,6 +14,9 @@ let wr2;
})();
// Since the WeakRefs were created during this turn, they're not cleared by GC.
// Here and below, we invoke GC synchronously and, with conservative stack
// scanning, there is a chance that the object is not reclaimed now. In any
// case, the WeakRef should not be cleared.
gc();
(function() {
@ -23,25 +26,29 @@ gc();
// New task
setTimeout(function() {
wr1.deref();
(function () { wr1.deref(); })();
o1 = null;
gc(); // deref makes sure we don't clean up wr1
(function () { assertNotEquals(undefined, wr1.deref()); })();
// New task
setTimeout(function() {
wr2.deref();
(function () { wr2.deref(); })();
o2 = null;
gc(); // deref makes sure we don't clean up wr2
(function () { assertNotEquals(undefined, wr2.deref()); })();
// New task
setTimeout(function() {
assertEquals(undefined, wr1.deref());
gc();
(async function () {
// Trigger GC again to make sure the two WeakRefs are cleared.
// We need to invoke GC asynchronously and wait for it to finish, so that
// it doesn't need to scan the stack. Otherwise, the objects may not be
// reclaimed because of conservative stack scanning and the test may not
// work as intended.
await gc({ type: 'major', execution: 'async' });
// New task
setTimeout(function() {
assertEquals(undefined, wr2.deref());
}, 0);
}, 0);
assertEquals(undefined, wr1.deref());
assertEquals(undefined, wr2.deref());
})();
}, 0);
}, 0);

View File

@ -4,34 +4,39 @@
// Flags: --expose-gc --noincremental-marking
let cleanup_call_count = 0;
let cleanup_holdings_count = 0;
let cleanup = function(holdings) {
assertEquals(holdings, undefined);
++cleanup_holdings_count;
++cleanup_call_count;
}
(async function () {
let fg = new FinalizationRegistry(cleanup);
let cleanup_call_count = 0;
let cleanup = function (holdings) {
assertEquals(holdings, undefined);
++cleanup_call_count;
}
// Create an object and register it in the FinalizationRegistry. The object needs to be inside
// a closure so that we can reliably kill them!
let fg = new FinalizationRegistry(cleanup);
(function() {
let object = {};
fg.register(object);
// Create an object and register it in the FinalizationRegistry. The object needs to be inside
// a closure so that we can reliably kill them!
(function () {
let object = {};
fg.register(object);
// object goes out of scope.
})();
// This GC will reclaim the target object and schedule cleanup.
// We need to invoke GC asynchronously and wait for it to finish, so that
// it doesn't need to scan the stack. Otherwise, the objects may not be
// reclaimed because of conservative stack scanning and the test may not
// work as intended.
await gc({ type: 'major', execution: 'async' });
assertEquals(0, cleanup_call_count);
// Assert that the cleanup function was called.
let timeout_func = function () {
assertEquals(1, cleanup_call_count);
}
setTimeout(timeout_func, 0);
// object goes out of scope.
})();
// This GC will reclaim the target object and schedule cleanup.
gc();
assertEquals(0, cleanup_call_count);
// Assert that the cleanup function was called.
let timeout_func = function() {
assertEquals(1, cleanup_call_count);
assertEquals(1, cleanup_holdings_count);
}
setTimeout(timeout_func, 0);

View File

@ -4,42 +4,46 @@
// Flags: --expose-gc --noincremental-marking
let cleanup_call_count = 0;
let cleanup_holdings_count = 0;
let cleanup = function(holdings) {
assertEquals("holdings", holdings);
++cleanup_holdings_count;
++cleanup_call_count;
}
(async function () {
let fg = new FinalizationRegistry(cleanup);
let key = {"k": "this is the key"};
// Create an object and register it in the FinalizationRegistry. The object needs
// to be inside a closure so that we can reliably kill them!
let cleanup_call_count = 0;
let cleanup = function (holdings) {
assertEquals("holdings", holdings);
++cleanup_call_count;
}
(function() {
let object = {};
fg.register(object, "holdings", key);
let fg = new FinalizationRegistry(cleanup);
let key = { "k": "this is the key" };
// Create an object and register it in the FinalizationRegistry. The object needs
// to be inside a closure so that we can reliably kill them!
(function () {
let object = {};
fg.register(object, "holdings", key);
// object goes out of scope.
})();
// This GC will reclaim the target object and schedule cleanup.
// We need to invoke GC asynchronously and wait for it to finish, so that
// it doesn't need to scan the stack. Otherwise, the objects may not be
// reclaimed because of conservative stack scanning and the test may not
// work as intended.
await gc({ type: 'major', execution: 'async' });
assertEquals(0, cleanup_call_count);
// Assert that the cleanup function was called.
let timeout_func = function () {
assertEquals(1, cleanup_call_count);
// Unregister an already cleaned-up weak reference.
let success = fg.unregister(key);
assertFalse(success);
// Assert that it didn't do anything.
setTimeout(() => { assertEquals(1, cleanup_call_count); }, 0);
}
setTimeout(timeout_func, 0);
// object goes out of scope.
})();
// This GC will reclaim the target object and schedule cleanup.
gc();
assertEquals(0, cleanup_call_count);
// Assert that the cleanup function was called.
let timeout_func = function() {
assertEquals(1, cleanup_call_count);
assertEquals(1, cleanup_holdings_count);
// Unregister an already cleaned-up weak reference.
let success = fg.unregister(key);
assertFalse(success);
// Assert that it didn't do anything.
setTimeout(() => { assertEquals(1, cleanup_call_count); }, 0);
setTimeout(() => { assertEquals(1, cleanup_holdings_count); }, 0);
}
setTimeout(timeout_func, 0);

View File

@ -4,34 +4,42 @@
// Flags: --expose-gc --noincremental-marking --noincremental-marking
let cleanup_call_count = 0;
let cleanup = function(holdings) {
++cleanup_call_count;
}
(async function () {
let fg = new FinalizationRegistry(cleanup);
let key = {"k": "this is the key"};
// Create an object and register it in the FinalizationRegistry. The object needs
// to be inside a closure so that we can reliably kill them!
let cleanup_call_count = 0;
let cleanup = function (holdings) {
++cleanup_call_count;
}
(function() {
let object = {};
fg.register(object, "my holdings", key);
let fg = new FinalizationRegistry(cleanup);
let key = { "k": "this is the key" };
// Create an object and register it in the FinalizationRegistry. The object needs
// to be inside a closure so that we can reliably kill them!
// Clear the WeakCell before the GC has a chance to discover it.
let success = fg.unregister(key);
assertTrue(success);
(function () {
let object = {};
fg.register(object, "my holdings", key);
// object goes out of scope.
})();
// Clear the WeakCell before the GC has a chance to discover it.
let success = fg.unregister(key);
assertTrue(success);
// This GC will reclaim the target object.
gc();
assertEquals(0, cleanup_call_count);
// object goes out of scope.
})();
// Assert that the cleanup function won't be called, since we called unregister.
let timeout_func = function() {
// This GC will reclaim the target object.
// We need to invoke GC asynchronously and wait for it to finish, so that
// it doesn't need to scan the stack. Otherwise, the objects may not be
// reclaimed because of conservative stack scanning and the test may not
// work as intended.
await gc({ type: 'major', execution: 'async' });
assertEquals(0, cleanup_call_count);
}
setTimeout(timeout_func, 0);
// Assert that the cleanup function won't be called, since we called unregister.
let timeout_func = function () {
assertEquals(0, cleanup_call_count);
}
setTimeout(timeout_func, 0);
})();

View File

@ -4,39 +4,47 @@
// Flags: --expose-gc --noincremental-marking
let cleanup_call_count = 0;
let cleanup = function(holdings) {
++cleanup_call_count;
}
(async function () {
let fg = new FinalizationRegistry(cleanup);
let key = {"k": "this is the key"};
// Create an object and register it in the FinalizationRegistry. The object needs
// to be inside a closure so that we can reliably kill them!
let cleanup_call_count = 0;
let cleanup = function (holdings) {
++cleanup_call_count;
}
(function() {
let object = {};
fg.register(object, "holdings", key);
let fg = new FinalizationRegistry(cleanup);
let key = { "k": "this is the key" };
// Create an object and register it in the FinalizationRegistry. The object needs
// to be inside a closure so that we can reliably kill them!
// Unregister before the GC has a chance to discover the object.
let success = fg.unregister(key);
assertTrue(success);
(function () {
let object = {};
fg.register(object, "holdings", key);
// Call unregister again (just to assert we handle this gracefully).
success = fg.unregister(key);
assertFalse(success);
// Unregister before the GC has a chance to discover the object.
let success = fg.unregister(key);
assertTrue(success);
// object goes out of scope.
})();
// Call unregister again (just to assert we handle this gracefully).
success = fg.unregister(key);
assertFalse(success);
// This GC will reclaim the target object.
gc();
assertEquals(0, cleanup_call_count);
// object goes out of scope.
})();
// Assert that the cleanup function won't be called, since the weak reference
// was unregistered.
let timeout_func = function() {
// This GC will reclaim the target object.
// We need to invoke GC asynchronously and wait for it to finish, so that
// it doesn't need to scan the stack. Otherwise, the objects may not be
// reclaimed because of conservative stack scanning and the test may not
// work as intended.
await gc({ type: 'major', execution: 'async' });
assertEquals(0, cleanup_call_count);
}
setTimeout(timeout_func, 0);
// Assert that the cleanup function won't be called, since the weak reference
// was unregistered.
let timeout_func = function () {
assertEquals(0, cleanup_call_count);
}
setTimeout(timeout_func, 0);
})();

View File

@ -4,37 +4,42 @@
// Flags: --expose-gc --noincremental-marking
let cleanup_call_count = 0;
let cleanup_holdings_count = 0;
let cleanup = function(holdings) {
assertEquals(holdings, "holdings");
let success = fg.unregister(key);
assertFalse(success);
(async function () {
++cleanup_holdings_count;
++cleanup_call_count;
}
let cleanup_call_count = 0;
let cleanup = function (holdings) {
assertEquals(holdings, "holdings");
let success = fg.unregister(key);
assertFalse(success);
let fg = new FinalizationRegistry(cleanup);
// Create an object and register it in the FinalizationRegistry. The object needs to be inside
// a closure so that we can reliably kill them!
let key = {"k": "this is the key"};
++cleanup_call_count;
}
(function() {
let object = {};
fg.register(object, "holdings", key);
let fg = new FinalizationRegistry(cleanup);
// Create an object and register it in the FinalizationRegistry. The object needs to be inside
// a closure so that we can reliably kill them!
let key = { "k": "this is the key" };
(function () {
let object = {};
fg.register(object, "holdings", key);
// object goes out of scope.
})();
// This GC will discover dirty WeakCells and schedule cleanup.
// We need to invoke GC asynchronously and wait for it to finish, so that
// it doesn't need to scan the stack. Otherwise, the objects may not be
// reclaimed because of conservative stack scanning and the test may not
// work as intended.
await gc({ type: 'major', execution: 'async' });
assertEquals(0, cleanup_call_count);
// Assert that the cleanup function was called.
let timeout_func = function () {
assertEquals(1, cleanup_call_count);
}
setTimeout(timeout_func, 0);
// object goes out of scope.
})();
// This GC will discover dirty WeakCells and schedule cleanup.
gc();
assertEquals(0, cleanup_call_count);
// Assert that the cleanup function was called.
let timeout_func = function() {
assertEquals(1, cleanup_call_count);
assertEquals(1, cleanup_holdings_count);
}
setTimeout(timeout_func, 0);

View File

@ -4,45 +4,50 @@
// Flags: --expose-gc --noincremental-marking
let cleanup_call_count = 0;
let cleanup_holdings_count = 0;
let cleanup = function(holdings) {
// See which target we're cleaning up and unregister the other one.
if (holdings == 1) {
let success = fg.unregister(key2);
assertTrue(success);
} else {
assertSame(holdings, 2);
let success = fg.unregister(key1);
assertTrue(success);
(async function () {
let cleanup_call_count = 0;
let cleanup = function(holdings) {
// See which target we're cleaning up and unregister the other one.
if (holdings == 1) {
let success = fg.unregister(key2);
assertTrue(success);
} else {
assertSame(holdings, 2);
let success = fg.unregister(key1);
assertTrue(success);
}
++cleanup_call_count;
}
++cleanup_holdings_count;
++cleanup_call_count;
}
let fg = new FinalizationRegistry(cleanup);
let key1 = {"k": "first key"};
let key2 = {"k": "second key"};
// Create two objects and register them in the FinalizationRegistry. The objects
// need to be inside a closure so that we can reliably kill them!
let fg = new FinalizationRegistry(cleanup);
let key1 = {"k": "first key"};
let key2 = {"k": "second key"};
// Create two objects and register them in the FinalizationRegistry. The objects
// need to be inside a closure so that we can reliably kill them!
(function() {
let object1 = {};
fg.register(object1, 1, key1);
let object2 = {};
fg.register(object2, 2, key2);
(function() {
let object1 = {};
fg.register(object1, 1, key1);
let object2 = {};
fg.register(object2, 2, key2);
// object1 and object2 go out of scope.
})();
// This GC will reclaim target objects and schedule cleanup.
// We need to invoke GC asynchronously and wait for it to finish, so that
// it doesn't need to scan the stack. Otherwise, the objects may not be
// reclaimed because of conservative stack scanning and the test may not
// work as intended.
await gc({ type: 'major', execution: 'async' });
assertEquals(0, cleanup_call_count);
// Assert that the cleanup function was called and cleaned up one holdings (but not the other one).
let timeout_func = function() {
assertEquals(1, cleanup_call_count);
}
setTimeout(timeout_func, 0);
// object1 and object2 go out of scope.
})();
// This GC will reclaim target objects and schedule cleanup.
gc();
assertEquals(0, cleanup_call_count);
// Assert that the cleanup function was called and cleaned up one holdings (but not the other one).
let timeout_func = function() {
assertEquals(1, cleanup_call_count);
assertEquals(1, cleanup_holdings_count);
}
setTimeout(timeout_func, 0);

View File

@ -4,44 +4,48 @@
// Flags: --expose-gc --noincremental-marking
let cleanup_call_count = 0;
let cleanup_holdings_count = 0;
let cleanup = function(holdings) {
assertEquals(holdings, "holdings");
(async function () {
// There's one more object with the same key that we haven't
// cleaned up yet so we should be able to unregister the
// callback for that one.
let success = fg.unregister(key);
let cleanup_call_count = 0;
let cleanup = function(holdings) {
assertEquals(holdings, "holdings");
assertTrue(success);
// There's one more object with the same key that we haven't
// cleaned up yet so we should be able to unregister the
// callback for that one.
let success = fg.unregister(key);
assertTrue(success);
++cleanup_holdings_count;
++cleanup_call_count;
}
++cleanup_call_count;
}
let fg = new FinalizationRegistry(cleanup);
// Create an object and register it in the FinalizationRegistry. The object needs to be inside
// a closure so that we can reliably kill them!
let key = {"k": "this is the key"};
let fg = new FinalizationRegistry(cleanup);
// Create an object and register it in the FinalizationRegistry. The object needs to be inside
// a closure so that we can reliably kill them!
let key = {"k": "this is the key"};
(function() {
let object = {};
let object2 = {};
fg.register(object, "holdings", key);
fg.register(object2, "holdings", key);
(function() {
let object = {};
let object2 = {};
fg.register(object, "holdings", key);
fg.register(object2, "holdings", key);
// object goes out of scope.
})();
// This GC will discover dirty WeakCells and schedule cleanup.
// We need to invoke GC asynchronously and wait for it to finish, so that
// it doesn't need to scan the stack. Otherwise, the objects may not be
// reclaimed because of conservative stack scanning and the test may not
// work as intended.
await gc({ type: 'major', execution: 'async' });
assertEquals(0, cleanup_call_count);
// Assert that the cleanup function was called.
let timeout_func = function() {
assertEquals(1, cleanup_call_count);
}
setTimeout(timeout_func, 0);
// object goes out of scope.
})();
// This GC will discover dirty WeakCells and schedule cleanup.
gc();
assertEquals(0, cleanup_call_count);
// Assert that the cleanup function was called.
let timeout_func = function() {
assertEquals(1, cleanup_call_count);
assertEquals(1, cleanup_holdings_count);
}
setTimeout(timeout_func, 0);

View File

@ -4,46 +4,51 @@
// Flags: --expose-gc --noincremental-marking
let cleanup_call_count = 0;
let cleanup_holdings_count = 0;
let cleanup = function(holdings) {
assertEquals("holdings2", holdings);
++cleanup_holdings_count;
++cleanup_call_count;
}
(async function () {
let fg = new FinalizationRegistry(cleanup);
let key1 = {"k": "key1"};
let key2 = {"k": "key2"};
// Create three objects and register them in the FinalizationRegistry. The objects
// need to be inside a closure so that we can reliably kill them!
let cleanup_call_count = 0;
let cleanup = function(holdings) {
assertEquals("holdings2", holdings);
++cleanup_call_count;
}
(function() {
let object1a = {};
fg.register(object1a, "holdings1a", key1);
let fg = new FinalizationRegistry(cleanup);
let key1 = {"k": "key1"};
let key2 = {"k": "key2"};
// Create three objects and register them in the FinalizationRegistry. The objects
// need to be inside a closure so that we can reliably kill them!
let object1b = {};
fg.register(object1b, "holdings1b", key1);
(function() {
let object1a = {};
fg.register(object1a, "holdings1a", key1);
let object2 = {};
fg.register(object2, "holdings2", key2);
let object1b = {};
fg.register(object1b, "holdings1b", key1);
// Unregister before the GC has a chance to discover the objects.
let success = fg.unregister(key1);
assertTrue(success);
let object2 = {};
fg.register(object2, "holdings2", key2);
// Unregister before the GC has a chance to discover the objects.
let success = fg.unregister(key1);
assertTrue(success);
// objects go out of scope.
})();
// This GC will reclaim the target objects.
// We need to invoke GC asynchronously and wait for it to finish, so that
// it doesn't need to scan the stack. Otherwise, the objects may not be
// reclaimed because of conservative stack scanning and the test may not
// work as intended.
await gc({ type: 'major', execution: 'async' });
assertEquals(0, cleanup_call_count);
// Assert that the cleanup function will be called only for the reference which
// was not unregistered.
let timeout_func = function() {
assertEquals(1, cleanup_call_count);
}
setTimeout(timeout_func, 0);
// objects go out of scope.
})();
// This GC will reclaim the target objects.
gc();
assertEquals(0, cleanup_call_count);
// Assert that the cleanup function will be called only for the reference which
// was not unregistered.
let timeout_func = function() {
assertEquals(1, cleanup_call_count);
assertEquals(1, cleanup_holdings_count);
}
setTimeout(timeout_func, 0);

View File

@ -4,34 +4,42 @@
// Flags: --expose-gc --noincremental-marking
let cleanup_call_count = 0;
let cleanup = function(holdings) {
++cleanup_call_count;
}
(async function () {
let key = {"k": "this is my key"};
let fg = new FinalizationRegistry(cleanup);
// Create an object and register it in the FinalizationRegistry. The object needs to be inside
// a closure so that we can reliably kill them!
let cleanup_call_count = 0;
let cleanup = function(holdings) {
++cleanup_call_count;
}
(function() {
let object = {};
fg.register(object, {}, key);
let key = {"k": "this is my key"};
let fg = new FinalizationRegistry(cleanup);
// Create an object and register it in the FinalizationRegistry. The object needs to be inside
// a closure so that we can reliably kill them!
// object goes out of scope.
})();
(function() {
let object = {};
fg.register(object, {}, key);
// This GC will discover dirty WeakCells and schedule cleanup.
gc();
assertEquals(0, cleanup_call_count);
// object goes out of scope.
})();
// Unregister the object from the FinalizationRegistry before cleanup has ran.
let success = fg.unregister(key);
assertTrue(success);
// Assert that the cleanup function won't be called.
let timeout_func = function() {
// This GC will discover dirty WeakCells and schedule cleanup.
// We need to invoke GC asynchronously and wait for it to finish, so that
// it doesn't need to scan the stack. Otherwise, the objects may not be
// reclaimed because of conservative stack scanning and the test may not
// work as intended.
await gc({ type: 'major', execution: 'async' });
assertEquals(0, cleanup_call_count);
}
setTimeout(timeout_func, 0);
// Unregister the object from the FinalizationRegistry before cleanup has ran.
let success = fg.unregister(key);
assertTrue(success);
// Assert that the cleanup function won't be called.
let timeout_func = function() {
assertEquals(0, cleanup_call_count);
}
setTimeout(timeout_func, 0);
})();

View File

@ -4,36 +4,44 @@
// Flags: --expose-gc --noincremental-marking
let cleanup_called = false;
let cleanup = function(holdings_arg) {
(async function () {
let cleanup_called = false;
let cleanup = function(holdings_arg) {
assertFalse(cleanup_called);
assertEquals(holdings_arg, holdings);
cleanup_called = true;
}
let fg = new FinalizationRegistry(cleanup);
let o = {};
let holdings = {'h': 55};
// Ignition holds references to objects in temporary registers. These will be
// released when the function exits. So only access o inside a function to
// prevent any references to objects in temporary registers when a gc is
// triggered.
(() => { fg.register(o, holdings); })()
// Here and below, we need to invoke GC asynchronously and wait for it to
// finish, so that it doesn't need to scan the stack. Otherwise, the objects
// may not be reclaimed because of conservative stack scanning and the test
// may not work as intended.
await gc({ type: 'major', execution: 'async' });
assertFalse(cleanup_called);
assertEquals(holdings_arg, holdings);
cleanup_called = true;
}
let fg = new FinalizationRegistry(cleanup);
let o = {};
let holdings = {'h': 55};
// Drop the last reference to o.
(() => { o = null; })()
// Ignition holds references to objects in temporary registers. These will be
// released when the function exits. So only access o inside a function to
// prevent any references to objects in temporary registers when a gc is
// triggered.
(() => { fg.register(o, holdings); })()
// GC will clear the WeakCell; the cleanup function will be called the next time
// we enter the event loop.
await gc({ type: 'major', execution: 'async' });
assertFalse(cleanup_called);
gc();
assertFalse(cleanup_called);
let timeout_func = function() {
assertTrue(cleanup_called);
}
// Drop the last reference to o.
(() => { o = null; })()
setTimeout(timeout_func, 0);
// GC will clear the WeakCell; the cleanup function will be called the next time
// we enter the event loop.
gc();
assertFalse(cleanup_called);
let timeout_func = function() {
assertTrue(cleanup_called);
}
setTimeout(timeout_func, 0);
})();

View File

@ -4,19 +4,30 @@
// Flags: --harmony-weak-refs-with-cleanup-some --expose-gc --noincremental-marking
var FR = new FinalizationRegistry (function (holdings) { globalThis.FRRan = true; });
{
const cleanup = function (holdings) { globalThis.FRRan = true; };
const FR = new FinalizationRegistry(cleanup);
(function () {
let obj = {};
// obj is its own unregister token and becomes unreachable after this
// block. If the unregister token is held strongly this test will not
// terminate.
FR.register(obj, 42, obj);
}
})();
function tryAgain() {
gc();
if (globalThis.FRRan || FR.cleanupSome()) {
return;
}
setTimeout(tryAgain, 0);
(async function () {
// We need to invoke GC asynchronously and wait for it to finish, so that
// it doesn't need to scan the stack. Otherwise, the objects may not be
// reclaimed because of conservative stack scanning and the test may not
// work as intended.
await gc({ type: 'major', execution: 'async' });
if (globalThis.FRRan || FR.cleanupSome()) {
return;
}
setTimeout(tryAgain, 0);
})();
}
tryAgain();

View File

@ -5,22 +5,29 @@
// Flags: --expose-gc --noincremental-marking
let wr;
(function() {
(function () {
let o = {};
wr = new WeakRef(o);
// Don't deref here, we want to test that the creation is enough to keep the
// WeakRef alive until the end of the turn.
})();
// Here we invoke GC synchronously and, with conservative stack scanning,
// there is a chance that the object is not reclaimed now. In any case,
// the WeakRef should not be cleared.
gc();
// Since the WeakRef was created during this turn, it is not cleared by GC.
(function() {
assertNotEquals(undefined, wr.deref());
})();
assertNotEquals(undefined, wr.deref());
// Next task.
setTimeout(() => {
gc();
assertEquals(undefined, wr.deref());
(async function () {
// Trigger GC again to make sure the two WeakRefs are cleared.
// We need to invoke GC asynchronously and wait for it to finish, so that
// it doesn't need to scan the stack. Otherwise, the objects may not be
// reclaimed because of conservative stack scanning and the test may not
// work as intended.
await gc({ type: 'major', execution: 'async' });
assertEquals(undefined, wr.deref());
})();
}, 0);

View File

@ -6,40 +6,43 @@
let wr;
let wr_control; // control WeakRef for testing what happens without deref
(function() {
(function () {
let o1 = {};
wr = new WeakRef(o1);
let o2 = {};
wr_control = new WeakRef(o2);
})();
let strong = {a: wr.deref(), b: wr_control.deref()};
let strong = { a: wr.deref(), b: wr_control.deref() };
// Here and below, we invoke GC synchronously and, with conservative stack
// scanning, there is a chance that the object is not reclaimed now. In any
// case, the WeakRefs should not be cleared.
gc();
// Next task.
setTimeout(function() {
// Call deref inside a closure, trying to avoid accidentally storing a strong
// reference into the object in the stack frame.
(function() {
wr.deref();
})();
(function () { wr.deref(); })();
strong = null;
// This GC will clear wr_control.
// This GC should clear wr_control (modulo CSS), since nothing was keeping it
// alive, but it should not clear wr.
gc();
(function() {
assertNotEquals(undefined, wr.deref());
// Now the control WeakRef got cleared, since nothing was keeping it alive.
assertEquals(undefined, wr_control.deref());
})();
(function () { assertNotEquals(undefined, wr.deref()); })();
// Next task.
setTimeout(function() {
gc();
(async function () {
// Trigger GC again to make sure the two WeakRefs are cleared.
// We need to invoke GC asynchronously and wait for it to finish, so that
// it doesn't need to scan the stack. Otherwise, the objects may not be
// reclaimed because of conservative stack scanning and the test may not
// work as intended.
await gc({ type: 'major', execution: 'async' });
assertEquals(undefined, wr.deref());
}, 0);
assertEquals(undefined, wr_control.deref());
})();
}, 0);