[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,6 +4,8 @@
// Flags: --expose-gc --noincremental-marking
(async function () {
let r = Realm.create();
let cleanup = Realm.eval(r, "var stored_global; function cleanup() { stored_global = globalThis; } cleanup");
@ -11,18 +13,19 @@ let realm_global_this = Realm.eval(r, "globalThis");
let fg = new FinalizationRegistry(cleanup);
// Create an object and a register it in the FinalizationRegistry. The object needs
// 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, {});
// object goes out of scope.
// Object goes out of scope.
})();
gc();
// 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 () {
@ -32,3 +35,5 @@ let timeout_func = function() {
}
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 microtaskInvoked = false;
const microtask = () => {
assertFalse(cleanedUp);
assertFalse(microtaskInvoked);
microtaskInvoked = true;
};
let cleanedUp = false;
function scheduleMicrotask(func) {
Promise.resolve().then(func);
}
let log = [];
let cleanup = (holdings) => {
const cleanup = (holdings) => {
assertFalse(cleanedUp);
assertTrue(microtaskInvoked);
cleanedUp = true;
}
};
let fg = new FinalizationRegistry(cleanup);
let o = null;
const fg = new FinalizationRegistry(cleanup);
(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, {});
// 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 microtask = function() {
log.push("first_microtask");
// cause GC during a microtask
o = null;
gc();
}
// 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);
// enqueue microtask that triggers GC
// Schedule the microtask.
Promise.resolve().then(microtask);
// but cleanup callback hasn't been called yet, as we're still in
// synchronous execution
// Nothing else hasn't been called yet, as we're still in synchronous
// execution.
assertFalse(microtaskInvoked);
assertFalse(cleanedUp);
// flush the microtask queue to run the microtask that triggers GC
%PerformMicrotaskCheckpoint();
// The microtask and the cleanup callbacks will verify that these two are
// invoked in the right order: microtask -> cleanup.
setTimeout(() => { assertTrue(cleanedUp); }, 0);
// 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");
(async function () {
const r = Realm.create();
const FG = Realm.eval(r, "FinalizationRegistry");
Realm.detachGlobal(r);
let fg_not_run = new FG(() => {
const cleanup_not_run = function (holdings) {
assertUnreachable();
});
(() => {
fg_not_run.register({});
}
let fg_not_run = new FG(cleanup_not_run);
(function () {
const object = {};
fg_not_run.register(object, "first");
// Object becomes unreachable.
})();
gc();
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);
let fg = new FG(()=> {
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.
(() => {
let object = {};
fg.register(object, {});
// object becomes unreachable.
(function () {
const object = {};
fg_run.register(object, "second");
// Object becomes unreachable.
})();
})();
gc();
// 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);
})();

View File

@ -4,6 +4,8 @@
// Flags: --expose-gc --noincremental-marking
(async function () {
let r = Realm.create();
let cleanup = Realm.eval(r, "var stored_global; let cleanup = new Proxy(function() { stored_global = globalThis;}, {}); cleanup");
@ -22,7 +24,11 @@ let weak_cell;
// object goes out of scope.
})();
gc();
// 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 () {
@ -32,3 +38,5 @@ let timeout_func = function() {
}
setTimeout(timeout_func, 0);
})();

View File

@ -4,6 +4,8 @@
// Flags: --expose-gc --noincremental-marking
(async function () {
let cleanup_called = 0;
let holdings_list = [];
let cleanup = function (holdings) {
@ -23,7 +25,11 @@ let o2 = {};
fg.register(o2, 2);
})();
gc();
// 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.
@ -34,7 +40,7 @@ assertEquals(cleanup_called, 0);
// GC will reclaim the target objects; the cleanup function will be called the
// next time we enter the event loop.
gc();
await gc({ type: 'major', execution: 'async' });
assertEquals(cleanup_called, 0);
let timeout_func = function () {
@ -49,3 +55,5 @@ let timeout_func = function() {
}
setTimeout(timeout_func, 0);
})();

View File

@ -4,6 +4,8 @@
// Flags: --harmony-weak-refs-with-cleanup-some --expose-gc --noincremental-marking --allow-natives-syntax
(async function () {
let cleanup_count = 0;
let cleanup_holdings = [];
let cleanup = function (holdings) {
@ -20,10 +22,16 @@ let fg = new FinalizationRegistry(cleanup);
})();
// GC will detect o as dead.
gc();
// 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]);
})();

View File

@ -4,6 +4,8 @@
// Flags: --harmony-weak-refs-with-cleanup-some --expose-gc --noincremental-marking
(async function () {
let cleanup_count = 0;
let cleanup_holdings = [];
let cleanup = function (holdings) {
@ -24,7 +26,11 @@ let key = {"k": "this is the key"};
})();
// GC will detect the WeakCell as dirty.
gc();
// 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);
@ -32,3 +38,5 @@ fg.unregister(key);
fg.cleanupSome();
assertEquals(0, cleanup_count);
})();

View File

@ -4,6 +4,8 @@
// Flags: --harmony-weak-refs-with-cleanup-some --expose-gc --noincremental-marking --allow-natives-syntax
(async function () {
let cleanup_count = 0;
let cleanup_holdings = [];
let cleanup = function (holdings) {
@ -26,9 +28,15 @@ let fg = new FinalizationRegistry(cleanup);
})();
// GC will detect o as dead.
gc();
// 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' });
fg.cleanupSome(cleanup2);
assertEquals(1, cleanup_count);
assertEquals(1, cleanup_holdings.length);
assertEquals("holdings", cleanup_holdings[0]);
})();

View File

@ -4,20 +4,19 @@
// Flags: --expose-gc --noincremental-marking
(async function () {
let cleanup_called = false;
let cleanup = function(holdings) {
const cleanup = function(holdings) {
assertFalse(cleanup_called);
let holdings_list = [];
holdings_list.push(holdings);
assertEquals(1, holdings_list.length);
assertEquals("holdings", holdings_list[0]);
assertEquals("holdings", holdings);
cleanup_called = true;
}
let fg = new FinalizationRegistry(cleanup);
const fg = new FinalizationRegistry(cleanup);
let weak_ref;
(function() {
let o = {};
const o = {};
weak_ref = new WeakRef(o);
fg.register(o, "holdings");
})();
@ -25,18 +24,26 @@ let weak_ref;
// 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();
(function() {
assertNotEquals(undefined, weak_ref.deref());
})();
assertFalse(cleanup_called);
// Trigger gc in next task
setTimeout(() => {
gc();
// 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' });
// Check that cleanup callback was called in a follow up task
setTimeout(() => {
assertTrue(cleanup_called);
assertEquals(undefined, weak_ref.deref());
}, 0);
}, 0);
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
(async function () {
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);
const fg = new FinalizationRegistry(cleanup);
let task_1_gc = (async function () {
const fg2 = new FinalizationRegistry(cleanup2);
(function () {
let fg2 = new FinalizationRegistry(cleanup2);
(function() {
fg.register({}, {});
fg2.register({}, {});
fg.register({}, "holdings1");
fg2.register({}, "holdings2");
})();
// Schedule fg and fg2 for cleanup.
gc();
await gc({ type: 'major', execution: 'async' });
assertFalse(cleanup_called);
assertFalse(cleanup_called_2);
})();
// Collect fg2, but fg is still alive.
gc();
// 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);
})();
// 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);
})();

View File

@ -4,22 +4,38 @@
// Flags: --expose-gc --noincremental-marking --no-concurrent-recompilation
(async function () {
let cleanup_called = false;
function cleanup(holdings) {
cleanup_called = true;
};
(function() {
let fg = new FinalizationRegistry(cleanup);
let task_1_gc = (async function () {
const fg = new FinalizationRegistry(cleanup);
(function () {
let x = {};
fg.register(x, {});
fg.register(x, "holdings");
x = null;
})();
// Schedule fg for cleanup.
gc();
await gc({ type: 'major', execution: 'async' });
assertFalse(cleanup_called);
})();
// Collect fg, which should result in cleanup not called.
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);
})();

View File

@ -4,6 +4,8 @@
// Flags: --expose-gc --noincremental-marking
(async function () {
let cleanup_called = false;
let holdings_list = [];
let cleanup = function (holdings) {
@ -22,7 +24,11 @@ let holdings = {'a': 'this is the holdings object'};
// triggered.
(() => { fg.register(o1, holdings); })()
gc();
// 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);
// Drop the last references to o1.
@ -31,7 +37,7 @@ assertFalse(cleanup_called);
// Drop the last reference to the holdings. The FinalizationRegistry keeps it
// alive, so the cleanup function will be called as normal.
holdings = null;
gc();
await gc({ type: 'major', execution: 'async' });
assertFalse(cleanup_called);
let timeout_func = function () {
@ -41,3 +47,5 @@ let timeout_func = function() {
}
setTimeout(timeout_func, 0);
})();

View File

@ -5,46 +5,48 @@
// Flags: --expose-gc --noincremental-marking
// Flags: --no-stress-flush-code
let cleanup0_call_count = 0;
let cleanup0_holdings_count = 0;
(async function () {
let cleanup0_call_count = 0;
let cleanup1_call_count = 0;
let cleanup1_holdings_count = 0;
let cleanup0 = function (holdings) {
++cleanup0_holdings_count;
++cleanup0_call_count;
}
let cleanup1 = function (holdings) {
++cleanup1_holdings_count;
++cleanup1_call_count;
}
let fg0 = new FinalizationRegistry(cleanup0);
let fg1 = new FinalizationRegistry(cleanup1);
// Register 1 weak reference for each FinalizationRegistry and kill the objects they point to.
// 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.
// 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 = [];
})();
// Will schedule both fg0 and fg1 for cleanup.
gc();
// 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.
// 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] = {};
@ -53,15 +55,28 @@ gc();
fg1.register(objects[1], "holdings1-1");
objects = [];
})();
})();
gc();
// 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, cleanup0_holdings_count);
assertEquals(2, cleanup1_call_count);
assertEquals(2, cleanup1_holdings_count);
}
// Give the cleanup task a chance to run.
// Give the cleanup task a chance to run and check it worked correctly.
setTimeout(timeout_func, 0);
})();

View File

@ -4,10 +4,10 @@
// Flags: --expose-gc --noincremental-marking
(async function () {
let cleanup_call_count = 0;
let cleanup_holdings_count = 0;
let cleanup = function (holdings) {
++cleanup_holdings_count;
++cleanup_call_count;
}
@ -28,13 +28,18 @@ let fg2 = new FinalizationRegistry(cleanup);
})();
// This GC will discover dirty WeakCells and schedule cleanup.
gc();
// 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);
assertEquals(2, cleanup_holdings_count);
}
setTimeout(timeout_func, 0);
})();

View File

@ -4,20 +4,27 @@
// Flags: --expose-gc --noincremental-marking
(async function () {
let call_count = 0;
let reentrant_gc = function(holdings) {
const reentrant_gc = function (holdings) {
gc();
call_count++;
}
let fg = new FinalizationRegistry(reentrant_gc);
const fg = new FinalizationRegistry(reentrant_gc);
(function () {
fg.register({}, 42);
})();
gc();
// 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);
setTimeout(function () { assertEquals(1, call_count); }, 0);
})();

View File

@ -4,6 +4,8 @@
// Flags: --stress-compaction --expose-gc
(async function () {
// Test that the dirty FinalizationRegistries that are enqueued during GC have
// their slots correctly recorded by the GC.
@ -15,8 +17,12 @@ for (let i = 0; i < 1024 * 8; i++) {
}
// 2) Force two GCs to ensure that JSFinalizatonRegistry objects are tenured.
gc();
gc();
// 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.
@ -30,7 +36,9 @@ gc();
// 4) Outside the function where the target is unreachable: force GC to collect
// the object.
gc();
await gc({ type: 'major', execution: 'async' });
// 5) Force another GC to test that the slot was correctly updated.
gc();
await gc({ type: 'major', execution: 'async' });
})();

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() {
(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, wr1.deref());
gc();
// New task
setTimeout(function() {
assertEquals(undefined, wr2.deref());
}, 0);
}, 0);
})();
}, 0);
}, 0);

View File

@ -4,11 +4,11 @@
// Flags: --expose-gc --noincremental-marking
(async function () {
let cleanup_call_count = 0;
let cleanup_holdings_count = 0;
let cleanup = function (holdings) {
assertEquals(holdings, undefined);
++cleanup_holdings_count;
++cleanup_call_count;
}
@ -25,13 +25,18 @@ let fg = new FinalizationRegistry(cleanup);
})();
// This GC will reclaim the target object and schedule cleanup.
gc();
// 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);
assertEquals(1, cleanup_holdings_count);
}
setTimeout(timeout_func, 0);
})();

View File

@ -4,11 +4,11 @@
// Flags: --expose-gc --noincremental-marking
(async function () {
let cleanup_call_count = 0;
let cleanup_holdings_count = 0;
let cleanup = function (holdings) {
assertEquals("holdings", holdings);
++cleanup_holdings_count;
++cleanup_call_count;
}
@ -25,13 +25,16 @@ let key = {"k": "this is the key"};
})();
// This GC will reclaim the target object and schedule cleanup.
gc();
// 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);
assertEquals(1, cleanup_holdings_count);
// Unregister an already cleaned-up weak reference.
let success = fg.unregister(key);
@ -39,7 +42,8 @@ let timeout_func = function() {
// 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,6 +4,8 @@
// Flags: --expose-gc --noincremental-marking --noincremental-marking
(async function () {
let cleanup_call_count = 0;
let cleanup = function (holdings) {
++cleanup_call_count;
@ -26,7 +28,11 @@ let key = {"k": "this is the key"};
})();
// This GC will reclaim the target object.
gc();
// 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 won't be called, since we called unregister.
@ -35,3 +41,5 @@ let timeout_func = function() {
}
setTimeout(timeout_func, 0);
})();

View File

@ -4,6 +4,8 @@
// Flags: --expose-gc --noincremental-marking
(async function () {
let cleanup_call_count = 0;
let cleanup = function (holdings) {
++cleanup_call_count;
@ -30,7 +32,11 @@ let key = {"k": "this is the key"};
})();
// This GC will reclaim the target object.
gc();
// 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 won't be called, since the weak reference
@ -40,3 +46,5 @@ let timeout_func = function() {
}
setTimeout(timeout_func, 0);
})();

View File

@ -4,14 +4,14 @@
// Flags: --expose-gc --noincremental-marking
(async function () {
let cleanup_call_count = 0;
let cleanup_holdings_count = 0;
let cleanup = function (holdings) {
assertEquals(holdings, "holdings");
let success = fg.unregister(key);
assertFalse(success);
++cleanup_holdings_count;
++cleanup_call_count;
}
@ -28,13 +28,18 @@ let key = {"k": "this is the key"};
})();
// This GC will discover dirty WeakCells and schedule cleanup.
gc();
// 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);
assertEquals(1, cleanup_holdings_count);
}
setTimeout(timeout_func, 0);
})();

View File

@ -4,8 +4,9 @@
// Flags: --expose-gc --noincremental-marking
(async function () {
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) {
@ -16,7 +17,6 @@ let cleanup = function(holdings) {
let success = fg.unregister(key1);
assertTrue(success);
}
++cleanup_holdings_count;
++cleanup_call_count;
}
@ -36,13 +36,18 @@ let key2 = {"k": "second key"};
})();
// This GC will reclaim target objects and schedule cleanup.
gc();
// 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);
assertEquals(1, cleanup_holdings_count);
}
setTimeout(timeout_func, 0);
})();

View File

@ -4,8 +4,9 @@
// Flags: --expose-gc --noincremental-marking
(async function () {
let cleanup_call_count = 0;
let cleanup_holdings_count = 0;
let cleanup = function(holdings) {
assertEquals(holdings, "holdings");
@ -13,10 +14,8 @@ let cleanup = function(holdings) {
// 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;
}
@ -35,13 +34,18 @@ let key = {"k": "this is the key"};
})();
// This GC will discover dirty WeakCells and schedule cleanup.
gc();
// 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);
assertEquals(1, cleanup_holdings_count);
}
setTimeout(timeout_func, 0);
})();

View File

@ -4,11 +4,11 @@
// Flags: --expose-gc --noincremental-marking
(async function () {
let cleanup_call_count = 0;
let cleanup_holdings_count = 0;
let cleanup = function(holdings) {
assertEquals("holdings2", holdings);
++cleanup_holdings_count;
++cleanup_call_count;
}
@ -36,14 +36,19 @@ let key2 = {"k": "key2"};
})();
// This GC will reclaim the target objects.
gc();
// 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);
assertEquals(1, cleanup_holdings_count);
}
setTimeout(timeout_func, 0);
})();

View File

@ -4,6 +4,8 @@
// Flags: --expose-gc --noincremental-marking
(async function () {
let cleanup_call_count = 0;
let cleanup = function(holdings) {
++cleanup_call_count;
@ -22,7 +24,11 @@ let fg = new FinalizationRegistry(cleanup);
})();
// This GC will discover dirty WeakCells and schedule cleanup.
gc();
// 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);
// Unregister the object from the FinalizationRegistry before cleanup has ran.
@ -35,3 +41,5 @@ let timeout_func = function() {
}
setTimeout(timeout_func, 0);
})();

View File

@ -4,6 +4,8 @@
// Flags: --expose-gc --noincremental-marking
(async function () {
let cleanup_called = false;
let cleanup = function(holdings_arg) {
assertFalse(cleanup_called);
@ -21,7 +23,11 @@ let holdings = {'h': 55};
// triggered.
(() => { fg.register(o, holdings); })()
gc();
// 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);
// Drop the last reference to o.
@ -29,7 +35,7 @@ assertFalse(cleanup_called);
// GC will clear the WeakCell; the cleanup function will be called the next time
// we enter the event loop.
gc();
await gc({ type: 'major', execution: 'async' });
assertFalse(cleanup_called);
let timeout_func = function() {
@ -37,3 +43,5 @@ let timeout_func = function() {
}
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();
(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

@ -12,15 +12,22 @@ let wr;
// 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());
})();
// Next task.
setTimeout(() => {
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);

View File

@ -15,31 +15,34 @@ let wr_control; // control WeakRef for testing what happens without 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);