test: merge ongc and gcutil into gc.js

PR-URL: https://github.com/nodejs/node/pull/54355
Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
This commit is contained in:
tannal 2024-08-13 18:28:21 +08:00 committed by Chengzhong Wu
parent 7616855b2a
commit 0b6b2a4dc7
No known key found for this signature in database
32 changed files with 122 additions and 101 deletions

View File

@ -3,7 +3,7 @@
// Flags: --expose-gc
const common = require('../../common');
const { gcUntil } = require('../../common/gc');
// Verify that addons can create GarbageCollected objects and
// have them traced properly.
@ -35,7 +35,7 @@ setTimeout(async function() {
for (let i = 0; i < count; ++i) {
array[i] = new CppGCed();
}
await common.gcUntil(
await gcUntil(
'All old CppGCed are destroyed',
() => states[kDestructCount] === count,
);
@ -44,7 +44,7 @@ setTimeout(async function() {
array = null;
globalThis.gc();
await common.gcUntil(
await gcUntil(
'All old CppGCed are destroyed',
() => states[kDestructCount] === count * 2,
);

View File

@ -6,7 +6,7 @@
const common = require('../common');
const { AsyncLocalStorage } = require('async_hooks');
const onGC = require('../common/ongc');
const { onGC } = require('../common/gc');
let asyncLocalStorage = new AsyncLocalStorage();

View File

@ -982,7 +982,7 @@ module exports a single `onGC()` function.
```js
require('../common');
const onGC = require('../common/ongc');
const { onGC } = require('../common/gc');
onGC({}, { ongc() { console.log('collected'); } });
```

View File

@ -1,9 +1,72 @@
'use strict';
const wait = require('timers/promises').setTimeout;
const assert = require('assert');
const common = require('../common');
const gcTrackerMap = new WeakMap();
const gcTrackerTag = 'NODE_TEST_COMMON_GC_TRACKER';
// TODO(joyeecheung): merge ongc.js and gcUntil from common/index.js
// into this.
/**
* Installs a garbage collection listener for the specified object.
* Uses async_hooks for GC tracking, which may affect test functionality.
* A full setImmediate() invocation passes between a global.gc() call and the listener being invoked.
* @param {object} obj - The target object to track for garbage collection.
* @param {object} gcListener - The listener object containing the ongc callback.
* @param {Function} gcListener.ongc - The function to call when the target object is garbage collected.
*/
function onGC(obj, gcListener) {
const async_hooks = require('async_hooks');
const onGcAsyncHook = async_hooks.createHook({
init: common.mustCallAtLeast(function(id, type) {
if (this.trackedId === undefined) {
assert.strictEqual(type, gcTrackerTag);
this.trackedId = id;
}
}),
destroy(id) {
assert.notStrictEqual(this.trackedId, -1);
if (id === this.trackedId) {
this.gcListener.ongc();
onGcAsyncHook.disable();
}
},
}).enable();
onGcAsyncHook.gcListener = gcListener;
gcTrackerMap.set(obj, new async_hooks.AsyncResource(gcTrackerTag));
obj = null;
}
/**
* Repeatedly triggers garbage collection until a specified condition is met or a maximum number of attempts is reached.
* @param {string|Function} [name] - Optional name, used in the rejection message if the condition is not met.
* @param {Function} condition - A function that returns true when the desired condition is met.
* @returns {Promise} A promise that resolves when the condition is met, or rejects after 10 failed attempts.
*/
function gcUntil(name, condition) {
if (typeof name === 'function') {
condition = name;
name = undefined;
}
return new Promise((resolve, reject) => {
let count = 0;
function gcAndCheck() {
setImmediate(() => {
count++;
global.gc();
if (condition()) {
resolve();
} else if (count < 10) {
gcAndCheck();
} else {
reject(name === undefined ? undefined : 'Test ' + name + ' failed');
}
});
}
gcAndCheck();
});
}
// This function can be used to check if an object factor leaks or not,
// but it needs to be used with care:
@ -124,4 +187,6 @@ module.exports = {
checkIfCollectable,
runAndBreathe,
checkIfCollectableByCounting,
onGC,
gcUntil,
};

View File

@ -851,30 +851,6 @@ function skipIfDumbTerminal() {
}
}
function gcUntil(name, condition) {
if (typeof name === 'function') {
condition = name;
name = undefined;
}
return new Promise((resolve, reject) => {
let count = 0;
function gcAndCheck() {
setImmediate(() => {
count++;
global.gc();
if (condition()) {
resolve();
} else if (count < 10) {
gcAndCheck();
} else {
reject(name === undefined ? undefined : 'Test ' + name + ' failed');
}
});
}
gcAndCheck();
});
}
function requireNoPackageJSONAbove(dir = __dirname) {
let possiblePackage = path.join(dir, '..', 'package.json');
let lastPackage = null;
@ -985,7 +961,6 @@ const common = {
expectsError,
expectRequiredModule,
expectWarning,
gcUntil,
getArrayBufferViews,
getBufferSources,
getCallSite,

View File

@ -1,32 +0,0 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const gcTrackerMap = new WeakMap();
const gcTrackerTag = 'NODE_TEST_COMMON_GC_TRACKER';
function onGC(obj, gcListener) {
const async_hooks = require('async_hooks');
const onGcAsyncHook = async_hooks.createHook({
init: common.mustCallAtLeast(function(id, type) {
if (this.trackedId === undefined) {
assert.strictEqual(type, gcTrackerTag);
this.trackedId = id;
}
}),
destroy(id) {
assert.notStrictEqual(this.trackedId, -1);
if (id === this.trackedId) {
this.gcListener.ongc();
onGcAsyncHook.disable();
}
},
}).enable();
onGcAsyncHook.gcListener = gcListener;
gcTrackerMap.set(obj, new async_hooks.AsyncResource(gcTrackerTag));
obj = null;
}
module.exports = onGC;

View File

@ -3,11 +3,12 @@
'use strict';
const common = require('../../common');
const addon = require(`./build/${common.buildType}/6_object_wrap`);
const { gcUntil } = require('../../common/gc');
(function scope() {
addon.objectWrapDanglingReference({});
})();
common.gcUntil('object-wrap-ref', () => {
gcUntil('object-wrap-ref', () => {
return addon.objectWrapDanglingReferenceTest();
});

View File

@ -4,6 +4,7 @@
const common = require('../../common');
const assert = require('assert');
const test = require(`./build/${common.buildType}/7_factory_wrap`);
const { gcUntil } = require('../../common/gc');
assert.strictEqual(test.finalizeCount, 0);
async function runGCTests() {
@ -13,7 +14,7 @@ async function runGCTests() {
assert.strictEqual(obj.plusOne(), 12);
assert.strictEqual(obj.plusOne(), 13);
})();
await common.gcUntil('test 1', () => (test.finalizeCount === 1));
await gcUntil('test 1', () => (test.finalizeCount === 1));
(() => {
const obj2 = test.createObject(20);
@ -21,6 +22,6 @@ async function runGCTests() {
assert.strictEqual(obj2.plusOne(), 22);
assert.strictEqual(obj2.plusOne(), 23);
})();
await common.gcUntil('test 2', () => (test.finalizeCount === 2));
await gcUntil('test 2', () => (test.finalizeCount === 2));
}
runGCTests();

View File

@ -4,6 +4,7 @@
const common = require('../../common');
const assert = require('assert');
const addon = require(`./build/${common.buildType}/8_passing_wrapped`);
const { gcUntil } = require('../../common/gc');
async function runTest() {
let obj1 = addon.createObject(10);
@ -14,7 +15,7 @@ async function runTest() {
// Make sure the native destructor gets called.
obj1 = null;
obj2 = null;
await common.gcUntil('8_passing_wrapped',
() => (addon.finalizeCount() === 2));
await gcUntil('8_passing_wrapped',
() => (addon.finalizeCount() === 2));
}
runTest();

View File

@ -5,8 +5,10 @@ const common = require('../../common');
const test_finalizer = require(`./build/${common.buildType}/test_finalizer`);
const assert = require('assert');
const { gcUntil } = require('../../common/gc');
// The goal of this test is to show that we can run "pure" finalizers in the
// current JS loop tick. Thus, we do not use common.gcUntil function works
// current JS loop tick. Thus, we do not use gcUntil function works
// asynchronously using micro tasks.
// We use IIFE for the obj scope instead of {} to be compatible with
// non-V8 JS engines that do not support scoped variables.
@ -25,7 +27,7 @@ for (let i = 0; i < 10; ++i) {
assert.strictEqual(test_finalizer.getFinalizerCallCount(), 1);
// The finalizer that access JS cannot run synchronously. They are run in the
// next JS loop tick. Thus, we must use common.gcUntil.
// next JS loop tick. Thus, we must use gcUntil.
async function runAsyncTests() {
// We do not use common.mustCall() because we want to see the finalizer
// called in response to GC and not as a part of env destruction.
@ -36,8 +38,8 @@ async function runAsyncTests() {
const obj = {};
test_finalizer.addFinalizerWithJS(obj, () => { js_is_called = true; });
})();
await common.gcUntil('ensure JS finalizer called',
() => (test_finalizer.getFinalizerCallCount() === 2));
await gcUntil('ensure JS finalizer called',
() => (test_finalizer.getFinalizerCallCount() === 2));
assert(js_is_called);
}
runAsyncTests();

View File

@ -4,6 +4,7 @@
const common = require('../../common');
const test_general = require(`./build/${common.buildType}/test_general`);
const assert = require('assert');
const { gcUntil } = require('../../common/gc');
const val1 = '1';
const val2 = 1;
@ -79,9 +80,9 @@ async function runGCTests() {
assert.strictEqual(test_general.derefItemWasCalled(), false);
(() => test_general.wrap({}))();
await common.gcUntil('deref_item() was called upon garbage collecting a ' +
await gcUntil('deref_item() was called upon garbage collecting a ' +
'wrapped object.',
() => test_general.derefItemWasCalled());
() => test_general.derefItemWasCalled());
// Ensure that removing a wrap and garbage collecting does not fire the
// finalize callback.
@ -89,7 +90,7 @@ async function runGCTests() {
test_general.testFinalizeWrap(z);
test_general.removeWrap(z);
z = null;
await common.gcUntil(
await gcUntil(
'finalize callback was not called upon garbage collection.',
() => (!test_general.finalizeWasCalled()));
}

View File

@ -4,6 +4,7 @@
const common = require('../../common');
const test_general = require(`./build/${common.buildType}/test_general`);
const assert = require('assert');
const { gcUntil } = require('../../common/gc');
let finalized = {};
const callback = common.mustCall(2);
@ -30,7 +31,7 @@ async function testFinalizeAndWrap() {
test_general.wrap(finalizeAndWrap);
test_general.addFinalizerOnly(finalizeAndWrap, common.mustCall());
finalizeAndWrap = null;
await common.gcUntil('test finalize and wrap',
() => test_general.derefItemWasCalled());
await gcUntil('test finalize and wrap',
() => test_general.derefItemWasCalled());
}
testFinalizeAndWrap();

View File

@ -1,7 +1,8 @@
'use strict';
// Flags: --expose-gc
const { gcUntil, buildType } = require('../../common');
const { buildType } = require('../../common');
const { gcUntil } = require('../../common/gc');
const assert = require('assert');
const test_reference = require(`./build/${buildType}/test_reference`);

View File

@ -7,7 +7,8 @@
// and symbol types, while in newer versions they can be created for
// any value type.
//
const { gcUntil, buildType } = require('../../common');
const { buildType } = require('../../common');
const { gcUntil } = require('../../common/gc');
const assert = require('assert');
const addon_v8 = require(`./build/${buildType}/test_reference_obj_only`);
const addon_new = require(`./build/${buildType}/test_reference_all_types`);

View File

@ -1,7 +1,7 @@
'use strict';
// Flags: --expose-gc
const common = require('../common');
const onGC = require('../common/ongc');
const { onGC } = require('../common/gc');
{
onGC({}, { ongc: common.mustCall() });

View File

@ -1,7 +1,8 @@
// Flags: --expose-gc
'use strict';
const common = require('../common');
const onGC = require('../common/ongc');
const { onGC } = require('../common/gc');
const { gcUntil } = require('../common/gc');
const assert = require('assert');
const async_hooks = require('async_hooks');
const domain = require('domain');
@ -40,7 +41,7 @@ d.run(() => {
d = null;
async function main() {
await common.gcUntil(
await gcUntil(
'All objects garbage collected',
() => resourceGCed && domainGCed && emitterGCed);
}

View File

@ -4,7 +4,7 @@
// but aborting every connection that comes in.
const common = require('../common');
const onGC = require('../common/ongc');
const { onGC } = require('../common/gc');
const http = require('http');
const os = require('os');

View File

@ -4,7 +4,7 @@
// but using a net server/client instead
require('../common');
const onGC = require('../common/ongc');
const { onGC } = require('../common/gc');
const assert = require('assert');
const net = require('net');
const os = require('os');

View File

@ -9,7 +9,7 @@ if (!common.hasCrypto)
common.skip('missing crypto');
const { duplexPair } = require('stream');
const onGC = require('../common/ongc');
const { onGC } = require('../common/gc');
const assert = require('assert');
const tls = require('tls');

View File

@ -5,7 +5,7 @@
// Check that creating a server without listening does not leak resources.
require('../common');
const onGC = require('../common/ongc');
const { onGC } = require('../common/gc');
const Countdown = require('../common/countdown');
const http = require('http');

View File

@ -1,7 +1,7 @@
// Flags: --expose-gc
'use strict';
const common = require('../common');
const onGC = require('../common/ongc');
const { onGC } = require('../common/gc');
const { createServer } = require('http');
const { connect } = require('net');

View File

@ -10,7 +10,7 @@ if (!common.hasCrypto) {
common.skip('missing crypto');
}
const onGC = require('../common/ongc');
const { onGC } = require('../common/gc');
const Countdown = require('../common/countdown');
const https = require('https');

View File

@ -1,6 +1,7 @@
// Flags: --expose-internals --expose-gc
'use strict';
const common = require('../common');
require('../common');
const { gcUntil } = require('../common/gc');
const assert = require('assert');
const { WeakReference } = require('internal/util');
@ -10,7 +11,7 @@ assert.strictEqual(ref.get(), obj);
async function main() {
obj = null;
await common.gcUntil(
await gcUntil(
'Reference is garbage collected',
() => ref.get() === undefined);
}

View File

@ -23,7 +23,7 @@
// Flags: --expose-gc
const common = require('../common');
const onGC = require('../common/ongc');
const { onGC } = require('../common/gc');
const assert = require('assert');
const net = require('net');

View File

@ -1,7 +1,7 @@
'use strict';
// Flags: --expose-gc
require('../common');
const onGC = require('../common/ongc');
const { onGC } = require('../common/gc');
// See https://github.com/nodejs/node/issues/53335
const poller = setInterval(() => {

View File

@ -6,7 +6,8 @@
*/
'use strict';
const common = require('../common');
require('../common');
const { gcUntil } = require('../common/gc');
const assert = require('node:assert');
const { findSourceMap } = require('node:module');
@ -28,7 +29,7 @@ function run(moduleId) {
run(moduleId);
// Run until the source map is cleared by GC, or fail the test after determined iterations.
common.gcUntil('SourceMap of deleted CJS module is cleared', () => {
gcUntil('SourceMap of deleted CJS module is cleared', () => {
// Repetitively load a second module with --max-old-space-size=10 to make GC more aggressive.
run(moduleIdRepeat);
// Verify that the source map is cleared.

View File

@ -26,7 +26,7 @@ const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const onGC = require('../common/ongc');
const { onGC } = require('../common/gc');
const assert = require('assert');
const tls = require('tls');
const fixtures = require('../common/fixtures');

View File

@ -2,6 +2,7 @@
// Flags: --expose-gc
const common = require('../common');
const { gcUntil } = require('../common/gc');
// On IBMi, the rss memory always returns zero
if (common.isIBMi)
@ -16,7 +17,7 @@ for (let i = 0; i < 1000000; i++) {
}
async function main() {
await common.gcUntil('RSS should go down', () => {
await gcUntil('RSS should go down', () => {
const after = process.memoryUsage.rss();
if (common.isASan) {
console.log(`ASan: before=${before} after=${after}`);

View File

@ -1,7 +1,7 @@
// Flags: --expose-gc
'use strict';
const common = require('../common');
const onGC = require('../common/ongc');
const { onGC } = require('../common/gc');
const assert = require('assert');
const zlib = require('zlib');

View File

@ -4,7 +4,7 @@
// but with an on('error') handler that does nothing.
const common = require('../common');
const onGC = require('../common/ongc');
const { onGC } = require('../common/gc');
const cpus = require('os').availableParallelism();

View File

@ -3,7 +3,7 @@
// Like test-gc-http-client.js, but with a timeout set.
const common = require('../common');
const onGC = require('../common/ongc');
const { onGC } = require('../common/gc');
const http = require('http');
function serverHandler(req, res) {

View File

@ -3,7 +3,7 @@
// just a simple http server and client.
const common = require('../common');
const onGC = require('../common/ongc');
const { onGC } = require('../common/gc');
const cpus = require('os').availableParallelism();