Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

doc: add Triagers section to table of contents in GOVERNANCE.md #34504

Merged
merged 2 commits into from Jul 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions GOVERNANCE.md
Expand Up @@ -2,6 +2,7 @@

<!-- TOC -->

* [Triagers](#triagers)
* [Collaborators](#collaborators)
* [Collaborator Activities](#collaborator-activities)
* [Technical Steering Committee](#technical-steering-committee)
Expand Down
10 changes: 6 additions & 4 deletions benchmark/napi/ref/index.js
Expand Up @@ -10,8 +10,10 @@ function callNewWeak() {
function main({ n }) {
addon.count = 0;
bench.start();
while (addon.count < n) {
callNewWeak();
}
bench.end(n);
new Promise((resolve) => {
(function oneIteration() {
callNewWeak();
setImmediate(() => ((addon.count < n) ? oneIteration() : resolve()));
})();
}).then(() => bench.end(n));
}
8 changes: 1 addition & 7 deletions src/js_native_api_v8.cc
Expand Up @@ -267,13 +267,7 @@ class RefBase : protected Finalizer, RefTracker {
protected:
inline void Finalize(bool is_env_teardown = false) override {
if (_finalize_callback != nullptr) {
v8::HandleScope handle_scope(_env->isolate);
_env->CallIntoModule([&](napi_env env) {
_finalize_callback(
env,
_finalize_data,
_finalize_hint);
});
_env->CallFinalizer(_finalize_callback, _finalize_data, _finalize_hint);
}

// this is safe because if a request to delete the reference
Expand Down
7 changes: 7 additions & 0 deletions src/js_native_api_v8.h
Expand Up @@ -101,6 +101,13 @@ struct napi_env__ {
}
}

virtual void CallFinalizer(napi_finalize cb, void* data, void* hint) {
v8::HandleScope handle_scope(isolate);
CallIntoModule([&](napi_env env) {
cb(env, data, hint);
});
}

v8impl::Persistent<v8::Value> last_exception;

// We store references in two different lists, depending on whether they have
Expand Down
11 changes: 11 additions & 0 deletions src/node_api.cc
Expand Up @@ -32,6 +32,17 @@ struct node_napi_env__ : public napi_env__ {
node_env()->untransferable_object_private_symbol(),
v8::True(isolate));
}

void CallFinalizer(napi_finalize cb, void* data, void* hint) override {
napi_env env = static_cast<napi_env>(this);
node_env()->SetImmediate([=](node::Environment* node_env) {
v8::HandleScope handle_scope(env->isolate);
v8::Context::Scope context_scope(env->context());
env->CallIntoModule([&](napi_env env) {
cb(env, data, hint);
});
});
}
};

typedef node_napi_env__* node_napi_env;
Expand Down
25 changes: 25 additions & 0 deletions test/common/index.js
Expand Up @@ -664,6 +664,30 @@ 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();
});
}

const common = {
allowGlobals,
buildType,
Expand All @@ -673,6 +697,7 @@ const common = {
disableCrashOnUnhandledRejection,
expectsError,
expectWarning,
gcUntil,
getArrayBufferViews,
getBufferSources,
getCallSite,
Expand Down
33 changes: 17 additions & 16 deletions test/js-native-api/7_factory_wrap/test.js
Expand Up @@ -6,20 +6,21 @@ const assert = require('assert');
const test = require(`./build/${common.buildType}/binding`);

assert.strictEqual(test.finalizeCount, 0);
(() => {
const obj = test.createObject(10);
assert.strictEqual(obj.plusOne(), 11);
assert.strictEqual(obj.plusOne(), 12);
assert.strictEqual(obj.plusOne(), 13);
})();
global.gc();
assert.strictEqual(test.finalizeCount, 1);
async function runGCTests() {
(() => {
const obj = test.createObject(10);
assert.strictEqual(obj.plusOne(), 11);
assert.strictEqual(obj.plusOne(), 12);
assert.strictEqual(obj.plusOne(), 13);
})();
await common.gcUntil('test 1', () => (test.finalizeCount === 1));

(() => {
const obj2 = test.createObject(20);
assert.strictEqual(obj2.plusOne(), 21);
assert.strictEqual(obj2.plusOne(), 22);
assert.strictEqual(obj2.plusOne(), 23);
})();
global.gc();
assert.strictEqual(test.finalizeCount, 2);
(() => {
const obj2 = test.createObject(20);
assert.strictEqual(obj2.plusOne(), 21);
assert.strictEqual(obj2.plusOne(), 22);
assert.strictEqual(obj2.plusOne(), 23);
})();
await common.gcUntil('test 2', () => (test.finalizeCount === 2));
}
runGCTests();
21 changes: 12 additions & 9 deletions test/js-native-api/8_passing_wrapped/test.js
Expand Up @@ -5,13 +5,16 @@ const common = require('../../common');
const assert = require('assert');
const addon = require(`./build/${common.buildType}/binding`);

let obj1 = addon.createObject(10);
let obj2 = addon.createObject(20);
const result = addon.add(obj1, obj2);
assert.strictEqual(result, 30);
async function runTest() {
let obj1 = addon.createObject(10);
let obj2 = addon.createObject(20);
const result = addon.add(obj1, obj2);
assert.strictEqual(result, 30);

// Make sure the native destructor gets called.
obj1 = null;
obj2 = null;
global.gc();
assert.strictEqual(addon.finalizeCount(), 2);
// Make sure the native destructor gets called.
obj1 = null;
obj2 = null;
await common.gcUntil('8_passing_wrapped',
() => (addon.finalizeCount() === 2));
}
runTest();
11 changes: 0 additions & 11 deletions test/js-native-api/test_exception/test.js
Expand Up @@ -66,14 +66,3 @@ const test_exception = (function() {
'Exception state did not remain clear as expected,' +
` .wasPending() returned ${exception_pending}`);
}

// Make sure that exceptions that occur during finalization are propagated.
function testFinalize(binding) {
let x = test_exception[binding]();
x = null;
assert.throws(() => { global.gc(); }, /Error during Finalize/);

// To assuage the linter's concerns.
(function() {})(x);
}
testFinalize('createExternal');
32 changes: 32 additions & 0 deletions test/js-native-api/test_exception/testFinalizerException.js
@@ -0,0 +1,32 @@
'use strict';
if (process.argv[2] === 'child') {
const common = require('../../common');
// Trying, catching the exception, and finding the bindings at the `Error`'s
// `binding` property is done intentionally, because we're also testing what
// happens when the add-on entry point throws. See test.js.
try {
require(`./build/${common.buildType}/test_exception`);
} catch (anException) {
anException.binding.createExternal();
}

// Collect garbage 10 times. At least one of those should throw the exception
// and cause the whole process to bail with it, its text printed to stderr and
// asserted by the parent process to match expectations.
let gcCount = 10;
(function gcLoop() {
global.gc();
if (--gcCount > 0) {
setImmediate(() => gcLoop());
}
})();
return;
}

const assert = require('assert');
const { spawnSync } = require('child_process');
const child = spawnSync(process.execPath, [
'--expose-gc', __filename, 'child'
]);
assert.strictEqual(child.signal, null);
assert.match(child.stderr.toString(), /Error during Finalize/m);
52 changes: 26 additions & 26 deletions test/js-native-api/test_general/test.js
Expand Up @@ -51,46 +51,46 @@ assert.strictEqual(test_general.testGetVersion(), 6);
// for null
assert.strictEqual(test_general.testNapiTypeof(null), 'null');

// Ensure that garbage collecting an object with a wrapped native item results
// in the finalize callback being called.
let w = {};
test_general.wrap(w);
w = null;
global.gc();
const derefItemWasCalled = test_general.derefItemWasCalled();
assert.strictEqual(derefItemWasCalled, true,
'deref_item() was called upon garbage collecting a ' +
'wrapped object. test_general.derefItemWasCalled() ' +
`returned ${derefItemWasCalled}`);


// Assert that wrapping twice fails.
const x = {};
test_general.wrap(x);
assert.throws(() => test_general.wrap(x),
{ name: 'Error', message: 'Invalid argument' });
// Clean up here, otherwise derefItemWasCalled() will be polluted.
test_general.removeWrap(x);

// Ensure that wrapping, removing the wrap, and then wrapping again works.
const y = {};
test_general.wrap(y);
test_general.removeWrap(y);
// Wrapping twice succeeds if a remove_wrap() separates the instances
test_general.wrap(y);

// Ensure that removing a wrap and garbage collecting does not fire the
// finalize callback.
let z = {};
test_general.testFinalizeWrap(z);
test_general.removeWrap(z);
z = null;
global.gc();
const finalizeWasCalled = test_general.finalizeWasCalled();
assert.strictEqual(finalizeWasCalled, false,
'finalize callback was not called upon garbage collection.' +
' test_general.finalizeWasCalled() ' +
`returned ${finalizeWasCalled}`);
// Clean up here, otherwise derefItemWasCalled() will be polluted.
test_general.removeWrap(y);

// Test napi_adjust_external_memory
const adjustedValue = test_general.testAdjustExternalMemory();
assert.strictEqual(typeof adjustedValue, 'number');
assert(adjustedValue > 0);

async function runGCTests() {
// Ensure that garbage collecting an object with a wrapped native item results
// in the finalize callback being called.
assert.strictEqual(test_general.derefItemWasCalled(), false);

(() => test_general.wrap({}))();
await common.gcUntil('deref_item() was called upon garbage collecting a ' +
'wrapped object.',
() => test_general.derefItemWasCalled());

// Ensure that removing a wrap and garbage collecting does not fire the
// finalize callback.
let z = {};
test_general.testFinalizeWrap(z);
test_general.removeWrap(z);
z = null;
await common.gcUntil(
'finalize callback was not called upon garbage collection.',
() => (!test_general.finalizeWasCalled()));
}
runGCTests();
16 changes: 10 additions & 6 deletions test/js-native-api/test_general/testFinalizer.js
Expand Up @@ -24,9 +24,13 @@ global.gc();

// Add an item to an object that is already wrapped, and ensure that its
// finalizer as well as the wrap finalizer gets called.
let finalizeAndWrap = {};
test_general.wrap(finalizeAndWrap);
test_general.addFinalizerOnly(finalizeAndWrap, common.mustCall());
finalizeAndWrap = null;
global.gc();
assert.strictEqual(test_general.derefItemWasCalled(), true);
async function testFinalizeAndWrap() {
assert.strictEqual(test_general.derefItemWasCalled(), false);
let finalizeAndWrap = {};
test_general.wrap(finalizeAndWrap);
test_general.addFinalizerOnly(finalizeAndWrap, common.mustCall());
finalizeAndWrap = null;
await common.gcUntil('test finalize and wrap',
() => test_general.derefItemWasCalled());
}
testFinalizeAndWrap();