node/test/js-native-api/test_general/testEnvCleanup.js
Gabriel Schulhof 53ca0b9ae1 src: render N-API weak callbacks as cleanup hooks
Since worker threads are complete Node.js environments, including the
ability to load native addons, and since those native addons can
allocate resources to be freed when objects go out of scope, and since,
upon worker thread exit, the engine does not invoke the weak callbacks
responsible for freeing resources which still have references, this
modification introduces tracking for weak references such that a list
of outstanding weak references is maintained. This list is traversed
during environment teardown. The callbacks for the remaining weak
references are called.

This change is also relevant for Node.js embedder scenarios, because in
those cases the process also outlives the `node::Environment` and
therefore weak callbacks should also be rendered as environment cleanup
hooks to ensure proper cleanup after native addons. This changes
introduces the means by which this can be accomplished.

A benchmark is included which measures the time it takes to execute the
weak reference callback for a given number of weak references.

Re: https://github.com/tc39/proposal-weakrefs/issues/125#issuecomment-535832130
PR-URL: https://github.com/nodejs/node/pull/28428
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
2019-10-13 00:07:43 -07:00

58 lines
2.3 KiB
JavaScript

'use strict';
if (process.argv[2] === 'child') {
const common = require('../../common');
const test_general = require(`./build/${common.buildType}/test_general`);
// The second argument to `envCleanupWrap()` is an index into the global
// static string array named `env_cleanup_finalizer_messages` on the native
// side. A reverse mapping is reproduced here for clarity.
const finalizerMessages = {
'simple wrap': 0,
'wrap, removeWrap': 1,
'first wrap': 2,
'second wrap': 3
};
// We attach the three objects we will test to `module.exports` to ensure they
// will not be garbage-collected before the process exits.
// Make sure the finalizer for a simple wrap will be called at env cleanup.
module.exports['simple wrap'] =
test_general.envCleanupWrap({}, finalizerMessages['simple wrap']);
// Make sure that a removed wrap does not result in a call to its finalizer at
// env cleanup.
module.exports['wrap, removeWrap'] =
test_general.envCleanupWrap({}, finalizerMessages['wrap, removeWrap']);
test_general.removeWrap(module.exports['wrap, removeWrap']);
// Make sure that only the latest attached version of a re-wrapped item's
// finalizer gets called at env cleanup.
module.exports['first wrap'] =
test_general.envCleanupWrap({}, finalizerMessages['first wrap']),
test_general.removeWrap(module.exports['first wrap']);
test_general.envCleanupWrap(module.exports['first wrap'],
finalizerMessages['second wrap']);
} else {
const assert = require('assert');
const { spawnSync } = require('child_process');
const child = spawnSync(process.execPath, [__filename, 'child'], {
stdio: [ process.stdin, 'pipe', process.stderr ]
});
// Grab the child's output and construct an object whose keys are the rows of
// the output and whose values are `true`, so we can compare the output while
// ignoring the order in which the lines of it were produced.
assert.deepStrictEqual(
child.stdout.toString().split(/\r\n|\r|\n/g).reduce((obj, item) =>
Object.assign(obj, item ? { [item]: true } : {}), {}), {
'finalize at env cleanup for simple wrap': true,
'finalize at env cleanup for second wrap': true
});
// Ensure that the child exited successfully.
assert.strictEqual(child.status, 0);
}