Upload Kmake

This commit is contained in:
Gorochu
2026-05-26 23:36:42 -07:00
parent ba051b2f74
commit 555ec72358
41615 changed files with 13344630 additions and 1 deletions

View File

@ -0,0 +1,23 @@
prefix async-hooks
# To mark a test as flaky, list the test name in the appropriate section
# below, without ".js", followed by ": PASS,FLAKY". Example:
# sample-test : PASS,FLAKY
[true] # This section applies to all platforms
[$system==win32]
[$system==linux]
[$system==macos]
[$arch==arm || $arch==arm64]
[$system==solaris] # Also applies to SmartOS
# https://github.com/nodejs/node/issues/43457
test-callback-error: PASS, FLAKY
[$system==freebsd]
[$system==aix]

View File

@ -0,0 +1,32 @@
# AsyncHooks Coverage Overview
Showing which kind of async resource is covered by which test:
| Resource Type | Test |
| ------------------- | --------------------------------------- |
| CONNECTION | test-connection.ssl.js |
| FSEVENTWRAP | test-fseventwrap.js |
| FSREQCALLBACK | test-fsreqcallback-{access,readFile}.js |
| GETADDRINFOREQWRAP | test-getaddrinforeqwrap.js |
| GETNAMEINFOREQWRAP | test-getnameinforeqwrap.js |
| HTTPINCOMINGMESSAGE | test-httpparser.request.js |
| HTTPCLIENTREQUEST | test-httpparser.response.js |
| Immediate | test-immediate.js |
| JSSTREAM | TODO (crashes when accessing directly) |
| PBKDF2REQUEST | test-crypto-pbkdf2.js |
| PIPECONNECTWRAP | test-pipeconnectwrap.js |
| PIPEWRAP | test-pipewrap.js |
| PROCESSWRAP | test-pipewrap.js |
| QUERYWRAP | test-querywrap.js |
| RANDOMBYTESREQUEST | test-crypto-randomBytes.js |
| SHUTDOWNWRAP | test-shutdownwrap.js |
| SIGNALWRAP | test-signalwrap.js |
| STATWATCHER | test-statwatcher.js |
| TCPCONNECTWRAP | test-tcpwrap.js |
| TCPWRAP | test-tcpwrap.js |
| TLSWRAP | test-tlswrap.js |
| TTYWRAP | test-ttywrap.{read,write}stream.js |
| UDPSENDWRAP | test-udpsendwrap.js |
| UDPWRAP | test-udpwrap.js |
| WRITEWRAP | test-writewrap.js |
| ZLIB | test-zlib.zlib-binding.deflate.js |

View File

@ -0,0 +1,53 @@
'use strict';
require('../common');
const assert = require('assert');
/**
* Checks the expected invocations against the invocations that actually
* occurred.
* @name checkInvocations
* @function
* @param {object} activity including timestamps for each life time event,
* i.e. init, before ...
* @param {object} hooks the expected life time event invocations with a count
* indicating how often they should have been invoked,
* i.e. `{ init: 1, before: 2, after: 2 }`
* @param {string} stage the name of the stage in the test at which we are
* checking the invocations
*/
exports.checkInvocations = function checkInvocations(activity, hooks, stage) {
const stageInfo = `Checking invocations at stage "${stage}":\n `;
assert.ok(activity != null,
`${stageInfo} Trying to check invocation for an activity, ` +
'but it was empty/undefined.',
);
// Check that actual invocations for all hooks match the expected invocations
[ 'init', 'before', 'after', 'destroy', 'promiseResolve' ].forEach(checkHook);
function checkHook(k) {
const val = hooks[k];
// Not expected ... all good
if (val == null) return;
if (val === 0) {
// Didn't expect any invocations, but it was actually invoked
const invocations = activity[k].length;
const msg = `${stageInfo} Called "${k}" ${invocations} time(s), ` +
'but expected no invocations.';
assert(activity[k] === null && activity[k] === undefined, msg);
} else {
// Expected some invocations, make sure that it was invoked at all
const msg1 = `${stageInfo} Never called "${k}", ` +
`but expected ${val} invocation(s).`;
assert(activity[k] !== null && activity[k] !== undefined, msg1);
// Now make sure that the expected count and
// the actual invocation count match
const msg2 = `${stageInfo} Called "${k}" ${activity[k].length} ` +
`time(s), but expected ${val} invocation(s).`;
assert.strictEqual(activity[k].length, val, msg2);
}
}
};

View File

@ -0,0 +1,249 @@
'use strict';
// Flags: --expose-gc
require('../common');
const assert = require('assert');
const async_hooks = require('async_hooks');
const { isMainThread } = require('worker_threads');
const util = require('util');
const print = process._rawDebug;
if (typeof global.gc === 'function') {
(function exity(cntr) {
process.once('beforeExit', () => {
global.gc();
if (cntr < 4) setImmediate(() => exity(cntr + 1));
});
})(0);
}
function noop() {}
class ActivityCollector {
constructor(start, {
allowNoInit = false,
oninit,
onbefore,
onafter,
ondestroy,
onpromiseResolve,
logid = null,
logtype = null,
} = {}) {
this._start = start;
this._allowNoInit = allowNoInit;
this._activities = new Map();
this._logid = logid;
this._logtype = logtype;
// Register event handlers if provided
this.oninit = typeof oninit === 'function' ? oninit : noop;
this.onbefore = typeof onbefore === 'function' ? onbefore : noop;
this.onafter = typeof onafter === 'function' ? onafter : noop;
this.ondestroy = typeof ondestroy === 'function' ? ondestroy : noop;
this.onpromiseResolve = typeof onpromiseResolve === 'function' ?
onpromiseResolve : noop;
// Create the hook with which we'll collect activity data
this._asyncHook = async_hooks.createHook({
init: this._init.bind(this),
before: this._before.bind(this),
after: this._after.bind(this),
destroy: this._destroy.bind(this),
promiseResolve: this._promiseResolve.bind(this),
});
}
enable() {
this._asyncHook.enable();
}
disable() {
this._asyncHook.disable();
}
sanityCheck(types) {
if (types != null && !Array.isArray(types)) types = [ types ];
function activityString(a) {
return util.inspect(a, false, 5, true);
}
const violations = [];
let tempActivityString;
function v(msg) { violations.push(msg); }
for (const a of this._activities.values()) {
tempActivityString = activityString(a);
if (types != null && !types.includes(a.type)) continue;
if (a.init && a.init.length > 1) {
v(`Activity inited twice\n${tempActivityString}` +
'\nExpected "init" to be called at most once');
}
if (a.destroy && a.destroy.length > 1) {
v(`Activity destroyed twice\n${tempActivityString}` +
'\nExpected "destroy" to be called at most once');
}
if (a.before && a.after) {
if (a.before.length < a.after.length) {
v('Activity called "after" without calling "before"\n' +
`${tempActivityString}` +
'\nExpected no "after" call without a "before"');
}
if (a.before.some((x, idx) => x > a.after[idx])) {
v('Activity had an instance where "after" ' +
'was invoked before "before"\n' +
`${tempActivityString}` +
'\nExpected "after" to be called after "before"');
}
}
if (a.before && a.destroy) {
if (a.before.some((x, idx) => x > a.destroy[idx])) {
v('Activity had an instance where "destroy" ' +
'was invoked before "before"\n' +
`${tempActivityString}` +
'\nExpected "destroy" to be called after "before"');
}
}
if (a.after && a.destroy) {
if (a.after.some((x, idx) => x > a.destroy[idx])) {
v('Activity had an instance where "destroy" ' +
'was invoked before "after"\n' +
`${tempActivityString}` +
'\nExpected "destroy" to be called after "after"');
}
}
if (!a.handleIsObject) {
v(`No resource object\n${tempActivityString}` +
'\nExpected "init" to be called with a resource object');
}
}
if (violations.length) {
console.error(violations.join('\n\n') + '\n');
assert.fail(`${violations.length} failed sanity checks`);
}
}
inspect(opts = {}) {
if (typeof opts === 'string') opts = { types: opts };
const { types = null, depth = 5, stage = null } = opts;
const activities = types == null ?
Array.from(this._activities.values()) :
this.activitiesOfTypes(types);
if (stage != null) console.log(`\n${stage}`);
console.log(util.inspect(activities, false, depth, true));
}
activitiesOfTypes(types) {
if (!Array.isArray(types)) types = [ types ];
return this.activities.filter((x) => types.includes(x.type));
}
get activities() {
return Array.from(this._activities.values());
}
_stamp(h, hook) {
if (h == null) return;
h[hook] ??= [];
const time = process.hrtime(this._start);
h[hook].push((time[0] * 1e9) + time[1]);
}
_getActivity(uid, hook) {
const h = this._activities.get(uid);
if (!h) {
// If we allowed handles without init we ignore any further life time
// events this makes sense for a few tests in which we enable some hooks
// later
if (this._allowNoInit) {
const stub = { uid, type: 'Unknown', handleIsObject: true, handle: {} };
this._activities.set(uid, stub);
return stub;
} else if (!isMainThread) {
// Worker threads start main script execution inside of an AsyncWrap
// callback, so we don't yield errors for these.
return null;
}
const err = new Error(`Found a handle whose ${hook}` +
' hook was invoked but not its init hook');
throw err;
}
return h;
}
_init(uid, type, triggerAsyncId, handle) {
const activity = {
uid,
type,
triggerAsyncId,
// In some cases (e.g. Timeout) the handle is a function, thus the usual
// `typeof handle === 'object' && handle !== null` check can't be used.
handleIsObject: handle instanceof Object,
handle,
};
this._stamp(activity, 'init');
this._activities.set(uid, activity);
this._maybeLog(uid, type, 'init');
this.oninit(uid, type, triggerAsyncId, handle);
}
_before(uid) {
const h = this._getActivity(uid, 'before');
this._stamp(h, 'before');
this._maybeLog(uid, h?.type, 'before');
this.onbefore(uid);
}
_after(uid) {
const h = this._getActivity(uid, 'after');
this._stamp(h, 'after');
this._maybeLog(uid, h?.type, 'after');
this.onafter(uid);
}
_destroy(uid) {
const h = this._getActivity(uid, 'destroy');
this._stamp(h, 'destroy');
this._maybeLog(uid, h?.type, 'destroy');
this.ondestroy(uid);
}
_promiseResolve(uid) {
const h = this._getActivity(uid, 'promiseResolve');
this._stamp(h, 'promiseResolve');
this._maybeLog(uid, h?.type, 'promiseResolve');
this.onpromiseResolve(uid);
}
_maybeLog(uid, type, name) {
if (this._logid &&
(type == null || this._logtype == null || this._logtype === type)) {
print(`${this._logid}.${name}.uid-${uid}`);
}
}
}
exports = module.exports = function initHooks({
oninit,
onbefore,
onafter,
ondestroy,
onpromiseResolve,
allowNoInit,
logid,
logtype,
} = {}) {
return new ActivityCollector(process.hrtime(), {
oninit,
onbefore,
onafter,
ondestroy,
onpromiseResolve,
allowNoInit,
logid,
logtype,
});
};

View File

@ -0,0 +1,91 @@
'use strict';
const common = require('../common');
// This test ensures async hooks are being properly called
// when using async-await mechanics. This involves:
// 1. Checking that all initialized promises are being resolved
// 2. Checking that for each 'before' corresponding hook 'after' hook is called
const assert = require('assert');
const initHooks = require('./init-hooks');
const util = require('util');
const sleep = util.promisify(setTimeout);
// Either 'inited' or 'resolved'
const promisesInitState = new Map();
// Either 'before' or 'after' AND asyncId must be present in the other map
const promisesExecutionState = new Map();
const hooks = initHooks({
oninit,
onbefore,
onafter,
ondestroy: null, // Intentionally not tested, since it will be removed soon
onpromiseResolve,
});
hooks.enable();
function oninit(asyncId, type) {
if (type === 'PROMISE') {
promisesInitState.set(asyncId, 'inited');
}
}
function onbefore(asyncId) {
if (!promisesInitState.has(asyncId)) {
return;
}
promisesExecutionState.set(asyncId, 'before');
}
function onafter(asyncId) {
if (!promisesInitState.has(asyncId)) {
return;
}
assert.strictEqual(promisesExecutionState.get(asyncId), 'before',
'after hook called for promise without prior call' +
'to before hook');
assert.strictEqual(promisesInitState.get(asyncId), 'resolved',
'after hook called for promise without prior call' +
'to resolve hook');
promisesExecutionState.set(asyncId, 'after');
}
function onpromiseResolve(asyncId) {
assert(promisesInitState.has(asyncId),
'resolve hook called for promise without prior call to init hook');
promisesInitState.set(asyncId, 'resolved');
}
const timeout = common.platformTimeout(10);
function checkPromisesInitState() {
for (const initState of promisesInitState.values()) {
// Promise should not be initialized without being resolved.
assert.strictEqual(initState, 'resolved');
}
}
function checkPromisesExecutionState() {
for (const executionState of promisesExecutionState.values()) {
// Check for mismatch between before and after hook calls.
assert.strictEqual(executionState, 'after');
}
}
process.on('beforeExit', common.mustCall(() => {
hooks.disable();
hooks.sanityCheck('PROMISE');
checkPromisesInitState();
checkPromisesExecutionState();
}));
async function asyncFunc() {
await sleep(timeout);
}
asyncFunc();

View File

@ -0,0 +1,37 @@
'use strict';
require('../common');
const assert = require('assert');
const {
executionAsyncResource,
executionAsyncId,
createHook,
} = require('async_hooks');
const http = require('http');
const hooked = {};
createHook({
init(asyncId, type, triggerAsyncId, resource) {
hooked[asyncId] = resource;
},
}).enable();
const server = http.createServer((req, res) => {
res.write('hello');
setTimeout(() => {
res.end(' world!');
}, 1000);
});
server.listen(0, () => {
assert.strictEqual(executionAsyncResource(), hooked[executionAsyncId()]);
http.get({ port: server.address().port }, (res) => {
assert.strictEqual(executionAsyncResource(), hooked[executionAsyncId()]);
res.on('data', () => {
assert.strictEqual(executionAsyncResource(), hooked[executionAsyncId()]);
});
res.on('end', () => {
assert.strictEqual(executionAsyncResource(), hooked[executionAsyncId()]);
server.close();
});
});
});

View File

@ -0,0 +1,36 @@
'use strict';
const common = require('../common');
const assert = require('node:assert');
const {
executionAsyncResource,
executionAsyncId,
createHook,
} = require('node:async_hooks');
const http = require('node:http');
const hooked = {};
createHook({
init(asyncId, type, triggerAsyncId, resource) {
hooked[asyncId] = resource;
},
}).enable();
const agent = new http.Agent({
maxSockets: 1,
});
const server = http.createServer((req, res) => {
res.end('ok');
});
server.listen(0, common.mustCall(() => {
assert.strictEqual(executionAsyncResource(), hooked[executionAsyncId()]);
http.get({ agent, port: server.address().port }, common.mustCall(() => {
assert.strictEqual(executionAsyncResource(), hooked[executionAsyncId()]);
server.close();
agent.destroy();
}));
}));

View File

@ -0,0 +1,30 @@
'use strict';
require('../common');
const assert = require('assert');
const {
executionAsyncResource,
executionAsyncId,
createHook,
} = require('async_hooks');
const http = require('http');
const hooked = {};
createHook({
init(asyncId, type, triggerAsyncId, resource) {
hooked[asyncId] = resource;
},
}).enable();
const server = http.createServer((req, res) => {
res.end('ok');
});
server.listen(0, () => {
assert.strictEqual(executionAsyncResource(), hooked[executionAsyncId()]);
http.get({ port: server.address().port }, () => {
assert.strictEqual(executionAsyncResource(), hooked[executionAsyncId()]);
server.close();
});
});

View File

@ -0,0 +1,62 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const { readFile } = require('fs');
const {
createHook,
executionAsyncResource,
AsyncResource,
} = require('async_hooks');
// Ignore any asyncIds created before our hook is active.
let firstSeenAsyncId = -1;
const idResMap = new Map();
const numExpectedCalls = 5;
createHook({
init: common.mustCallAtLeast(
(asyncId, type, triggerId, resource) => {
if (firstSeenAsyncId === -1) {
firstSeenAsyncId = asyncId;
}
assert.ok(idResMap.get(asyncId) === undefined);
idResMap.set(asyncId, resource);
}, numExpectedCalls),
before(asyncId) {
if (asyncId >= firstSeenAsyncId) {
beforeHook(asyncId);
}
},
after(asyncId) {
if (asyncId >= firstSeenAsyncId) {
afterHook(asyncId);
}
},
}).enable();
const beforeHook = common.mustCallAtLeast(
(asyncId) => {
const res = idResMap.get(asyncId);
assert.ok(res !== undefined);
const execRes = executionAsyncResource();
assert.ok(execRes === res, 'resource mismatch in before');
}, numExpectedCalls);
const afterHook = common.mustCallAtLeast(
(asyncId) => {
const res = idResMap.get(asyncId);
assert.ok(res !== undefined);
const execRes = executionAsyncResource();
assert.ok(execRes === res, 'resource mismatch in after');
}, numExpectedCalls);
const res = new AsyncResource('TheResource');
const initRes = idResMap.get(res.asyncId());
assert.ok(initRes === res, 'resource mismatch in init');
res.runInAsyncScope(common.mustCall(() => {
const execRes = executionAsyncResource();
assert.ok(execRes === res, 'resource mismatch in cb');
}));
readFile(__filename, common.mustCall());

View File

@ -0,0 +1,13 @@
'use strict';
require('../common');
const assert = require('assert');
const { AsyncLocalStorage } = require('async_hooks');
const asyncLocalStorage = new AsyncLocalStorage();
asyncLocalStorage.run({}, (runArg) => {
assert.strictEqual(runArg, 'foo');
asyncLocalStorage.exit((exitArg) => {
assert.strictEqual(exitArg, 'bar');
}, 'bar');
}, 'foo');

View File

@ -0,0 +1,19 @@
'use strict';
require('../common');
const assert = require('assert');
const { AsyncLocalStorage } = require('async_hooks');
const asyncLocalStorage = new AsyncLocalStorage();
async function test() {
asyncLocalStorage.getStore().set('foo', 'bar');
await Promise.resolve();
assert.strictEqual(asyncLocalStorage.getStore().get('foo'), 'bar');
}
async function main() {
await asyncLocalStorage.run(new Map(), test);
assert.strictEqual(asyncLocalStorage.getStore(), undefined);
}
main();

View File

@ -0,0 +1,27 @@
'use strict';
require('../common');
const assert = require('assert');
const { AsyncLocalStorage } = require('async_hooks');
async function foo() {}
const asyncLocalStorage = new AsyncLocalStorage();
async function testOut() {
await foo();
assert.strictEqual(asyncLocalStorage.getStore(), undefined);
}
async function testAwait() {
await foo();
assert.notStrictEqual(asyncLocalStorage.getStore(), undefined);
assert.strictEqual(asyncLocalStorage.getStore().get('key'), 'value');
await asyncLocalStorage.exit(testOut);
}
asyncLocalStorage.run(new Map(), () => {
const store = asyncLocalStorage.getStore();
store.set('key', 'value');
testAwait(); // should not reject
});
assert.strictEqual(asyncLocalStorage.getStore(), undefined);

View File

@ -0,0 +1,26 @@
'use strict';
require('../common');
// Regression tests for https://github.com/nodejs/node/issues/40693
const assert = require('assert');
const dgram = require('dgram');
const { AsyncLocalStorage } = require('async_hooks');
dgram.createSocket('udp4')
.on('message', function(msg, rinfo) { this.send(msg, rinfo.port); })
.on('listening', function() {
const asyncLocalStorage = new AsyncLocalStorage();
const store = { val: 'abcd' };
asyncLocalStorage.run(store, () => {
const client = dgram.createSocket('udp4');
client.on('message', (msg, rinfo) => {
assert.deepStrictEqual(asyncLocalStorage.getStore(), store);
client.close();
this.close();
});
client.send('Hello, world!', this.address().port);
});
})
.bind(0);

View File

@ -0,0 +1,32 @@
'use strict';
require('../common');
const assert = require('assert');
const { AsyncLocalStorage } = require('async_hooks');
const asyncLocalStorage = new AsyncLocalStorage();
asyncLocalStorage.run(new Map(), () => {
asyncLocalStorage.getStore().set('foo', 'bar');
process.nextTick(() => {
assert.strictEqual(asyncLocalStorage.getStore().get('foo'), 'bar');
process.nextTick(() => {
assert.strictEqual(asyncLocalStorage.getStore(), undefined);
});
asyncLocalStorage.disable();
assert.strictEqual(asyncLocalStorage.getStore(), undefined);
// Calls to exit() should not mess with enabled status
asyncLocalStorage.exit(() => {
assert.strictEqual(asyncLocalStorage.getStore(), undefined);
});
assert.strictEqual(asyncLocalStorage.getStore(), undefined);
process.nextTick(() => {
assert.strictEqual(asyncLocalStorage.getStore(), undefined);
asyncLocalStorage.run(new Map().set('bar', 'foo'), () => {
assert.strictEqual(asyncLocalStorage.getStore().get('bar'), 'foo');
});
});
});
});

View File

@ -0,0 +1,20 @@
'use strict';
require('../common');
const assert = require('assert');
const { AsyncLocalStorage } = require('async_hooks');
const asyncLocalStorage = new AsyncLocalStorage();
setImmediate(() => {
const store = { foo: 'bar' };
asyncLocalStorage.enterWith(store);
assert.strictEqual(asyncLocalStorage.getStore(), store);
setTimeout(() => {
assert.strictEqual(asyncLocalStorage.getStore(), store);
}, 10);
});
setTimeout(() => {
assert.strictEqual(asyncLocalStorage.getStore(), undefined);
}, 10);

View File

@ -0,0 +1,118 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const { AsyncLocalStorage } = require('async_hooks');
const vm = require('vm');
// err1 is emitted sync as a control - no events
// err2 is emitted after a timeout - uncaughtExceptionMonitor
// + uncaughtException
// err3 is emitted after some awaits - unhandledRejection
// err4 is emitted during handling err3 - uncaughtExceptionMonitor
// err5 is emitted after err4 from a VM lacking hooks - unhandledRejection
// + uncaughtException
const asyncLocalStorage = new AsyncLocalStorage();
const callbackToken = { callbackToken: true };
const awaitToken = { awaitToken: true };
let i = 0;
// Redefining the uncaughtExceptionHandler is a bit odd, so we just do this
// so we can track total invocations
let underlyingExceptionHandler;
const exceptionHandler = common.mustCall(function(...args) {
return underlyingExceptionHandler.call(this, ...args);
}, 2);
process.setUncaughtExceptionCaptureCallback(exceptionHandler);
const exceptionMonitor = common.mustCall((err, origin) => {
if (err.message === 'err2') {
assert.strictEqual(origin, 'uncaughtException');
assert.strictEqual(asyncLocalStorage.getStore(), callbackToken);
} else if (err.message === 'err4') {
assert.strictEqual(origin, 'unhandledRejection');
assert.strictEqual(asyncLocalStorage.getStore(), awaitToken);
} else {
assert.fail('unknown error ' + err);
}
}, 2);
process.on('uncaughtExceptionMonitor', exceptionMonitor);
function fireErr1() {
underlyingExceptionHandler = common.mustCall(function(err) {
++i;
assert.strictEqual(err.message, 'err2');
assert.strictEqual(asyncLocalStorage.getStore(), callbackToken);
}, 1);
try {
asyncLocalStorage.run(callbackToken, () => {
setTimeout(fireErr2, 0);
throw new Error('err1');
});
} catch (e) {
assert.strictEqual(e.message, 'err1');
assert.strictEqual(asyncLocalStorage.getStore(), undefined);
}
}
function fireErr2() {
process.nextTick(() => {
assert.strictEqual(i, 1);
fireErr3();
});
throw new Error('err2');
}
function fireErr3() {
assert.strictEqual(asyncLocalStorage.getStore(), callbackToken);
const rejectionHandler3 = common.mustCall((err) => {
assert.strictEqual(err.message, 'err3');
assert.strictEqual(asyncLocalStorage.getStore(), awaitToken);
process.off('unhandledRejection', rejectionHandler3);
fireErr4();
}, 1);
process.on('unhandledRejection', rejectionHandler3);
async function awaitTest() {
await null;
throw new Error('err3');
}
asyncLocalStorage.run(awaitToken, awaitTest);
}
const uncaughtExceptionHandler4 = common.mustCall(
function(err) {
assert.strictEqual(err.message, 'err4');
assert.strictEqual(asyncLocalStorage.getStore(), awaitToken);
fireErr5();
}, 1);
function fireErr4() {
assert.strictEqual(asyncLocalStorage.getStore(), awaitToken);
underlyingExceptionHandler = uncaughtExceptionHandler4;
// re-entrant check
Promise.reject(new Error('err4'));
}
function fireErr5() {
assert.strictEqual(asyncLocalStorage.getStore(), awaitToken);
underlyingExceptionHandler = () => {};
const rejectionHandler5 = common.mustCall((err) => {
assert.strictEqual(err.message, 'err5');
assert.strictEqual(asyncLocalStorage.getStore(), awaitToken);
process.off('unhandledRejection', rejectionHandler5);
}, 1);
process.on('unhandledRejection', rejectionHandler5);
const makeOrphan = vm.compileFunction(`(${String(() => {
async function main() {
await null;
Promise.resolve().then(() => {
throw new Error('err5');
});
}
main();
})})()`);
makeOrphan();
}
fireErr1();

View File

@ -0,0 +1,27 @@
'use strict';
// Flags: --expose_gc --expose-internals
// This test ensures that AsyncLocalStorage gets gced once it was disabled
// and no strong references remain in userland.
const common = require('../common');
const { AsyncLocalStorage } = require('async_hooks');
const AsyncContextFrame = require('internal/async_context_frame');
const { onGC } = require('../common/gc');
let asyncLocalStorage = new AsyncLocalStorage();
asyncLocalStorage.run({}, () => {
asyncLocalStorage.disable();
onGC(asyncLocalStorage, { ongc: common.mustCall() });
});
if (AsyncContextFrame.enabled) {
// This disable() is needed to remove reference form AsyncContextFrame
// created during exit of run() to the AsyncLocalStore instance.
asyncLocalStorage.disable();
}
asyncLocalStorage = null;
global.gc();

View File

@ -0,0 +1,35 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const { AsyncLocalStorage } = require('async_hooks');
const http = require('http');
const asyncLocalStorage = new AsyncLocalStorage();
const agent = new http.Agent({
maxSockets: 1,
});
const N = 3;
let responses = 0;
const server = http.createServer(common.mustCall((req, res) => {
res.end('ok');
}, N));
server.listen(0, common.mustCall(() => {
const port = server.address().port;
for (let i = 0; i < N; i++) {
asyncLocalStorage.run(i, () => {
http.get({ agent, port }, common.mustCall((res) => {
assert.strictEqual(asyncLocalStorage.getStore(), i);
if (++responses === N) {
server.close();
agent.destroy();
}
res.resume();
}));
});
}
}));

View File

@ -0,0 +1,21 @@
'use strict';
require('../common');
const assert = require('assert');
const { AsyncLocalStorage } = require('async_hooks');
const http = require('http');
const asyncLocalStorage = new AsyncLocalStorage();
const server = http.createServer((req, res) => {
res.end('ok');
});
server.listen(0, () => {
asyncLocalStorage.run(new Map(), () => {
const store = asyncLocalStorage.getStore();
store.set('hello', 'world');
http.get({ host: 'localhost', port: server.address().port }, () => {
assert.strictEqual(asyncLocalStorage.getStore().get('hello'), 'world');
server.close();
});
});
});

View File

@ -0,0 +1,15 @@
'use strict';
require('../common');
const assert = require('assert');
const { AsyncLocalStorage } = require('async_hooks');
const asyncLocalStorage = new AsyncLocalStorage();
asyncLocalStorage.run('hello node', () => {
assert.strictEqual(asyncLocalStorage.getStore(), 'hello node');
});
const runStore = { hello: 'node' };
asyncLocalStorage.run(runStore, () => {
assert.strictEqual(asyncLocalStorage.getStore(), runStore);
});

View File

@ -0,0 +1,25 @@
'use strict';
require('../common');
const assert = require('assert');
const { AsyncLocalStorage } = require('async_hooks');
const asyncLocalStorage = new AsyncLocalStorage();
const outer = {};
const inner = {};
function testInner() {
assert.strictEqual(asyncLocalStorage.getStore(), outer);
asyncLocalStorage.run(inner, () => {
assert.strictEqual(asyncLocalStorage.getStore(), inner);
});
assert.strictEqual(asyncLocalStorage.getStore(), outer);
asyncLocalStorage.exit(() => {
assert.strictEqual(asyncLocalStorage.getStore(), undefined);
});
assert.strictEqual(asyncLocalStorage.getStore(), outer);
}
asyncLocalStorage.run(outer, testInner);
assert.strictEqual(asyncLocalStorage.getStore(), undefined);

View File

@ -0,0 +1,38 @@
'use strict';
require('../common');
const assert = require('assert');
const { AsyncLocalStorage } = require('async_hooks');
const asyncLocalStorage = new AsyncLocalStorage();
const asyncLocalStorage2 = new AsyncLocalStorage();
setTimeout(() => {
asyncLocalStorage.run(new Map(), () => {
asyncLocalStorage2.run(new Map(), () => {
const store = asyncLocalStorage.getStore();
const store2 = asyncLocalStorage2.getStore();
store.set('hello', 'world');
store2.set('hello', 'foo');
setTimeout(() => {
assert.strictEqual(asyncLocalStorage.getStore().get('hello'), 'world');
assert.strictEqual(asyncLocalStorage2.getStore().get('hello'), 'foo');
asyncLocalStorage.exit(() => {
assert.strictEqual(asyncLocalStorage.getStore(), undefined);
assert.strictEqual(asyncLocalStorage2.getStore().get('hello'), 'foo');
});
assert.strictEqual(asyncLocalStorage.getStore().get('hello'), 'world');
assert.strictEqual(asyncLocalStorage2.getStore().get('hello'), 'foo');
}, 200);
});
});
}, 100);
setTimeout(() => {
asyncLocalStorage.run(new Map(), () => {
const store = asyncLocalStorage.getStore();
store.set('hello', 'earth');
setTimeout(() => {
assert.strictEqual(asyncLocalStorage.getStore().get('hello'), 'earth');
}, 100);
});
}, 100);

View File

@ -0,0 +1,28 @@
'use strict';
require('../common');
const assert = require('assert');
const { AsyncLocalStorage } = require('async_hooks');
async function main() {
const asyncLocalStorage = new AsyncLocalStorage();
const err = new Error();
const next = () => Promise.resolve()
.then(() => {
assert.strictEqual(asyncLocalStorage.getStore().get('a'), 1);
throw err;
});
await new Promise((resolve, reject) => {
asyncLocalStorage.run(new Map(), () => {
const store = asyncLocalStorage.getStore();
store.set('a', 1);
next().then(resolve, reject);
});
})
.catch((e) => {
assert.strictEqual(asyncLocalStorage.getStore(), undefined);
assert.strictEqual(e, err);
});
assert.strictEqual(asyncLocalStorage.getStore(), undefined);
}
main();

View File

@ -0,0 +1,27 @@
'use strict';
require('../common');
// Regression tests for https://github.com/nodejs/node/issues/40693
const assert = require('assert');
const net = require('net');
const { AsyncLocalStorage } = require('async_hooks');
net
.createServer((socket) => {
socket.write('Hello, world!');
socket.pipe(socket);
})
.listen(0, function() {
const asyncLocalStorage = new AsyncLocalStorage();
const store = { val: 'abcd' };
asyncLocalStorage.run(store, () => {
const client = net.connect({ port: this.address().port });
client.on('data', () => {
assert.deepStrictEqual(asyncLocalStorage.getStore(), store);
client.end();
this.close();
});
});
});

View File

@ -0,0 +1,20 @@
'use strict';
const common = require('../common');
const { Readable, finished } = require('stream');
const { AsyncLocalStorage } = require('async_hooks');
const { strictEqual } = require('assert');
// This test verifies that AsyncLocalStorage context is maintained
// when using stream.finished()
const readable = new Readable();
const als = new AsyncLocalStorage();
als.run(321, () => {
finished(readable, common.mustCall(() => {
strictEqual(als.getStore(), 321);
}));
});
readable.destroy();

View File

@ -0,0 +1,53 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const { AsyncLocalStorage } = require('async_hooks');
// This test verifies that async local storage works with thenables
const store = new AsyncLocalStorage();
const data = Symbol('verifier');
const then = common.mustCall((cb) => {
assert.strictEqual(store.getStore(), data);
setImmediate(cb);
}, 4);
function thenable() {
return {
then,
};
}
// Await a thenable
store.run(data, async () => {
assert.strictEqual(store.getStore(), data);
await thenable();
assert.strictEqual(store.getStore(), data);
});
// Returning a thenable in an async function
store.run(data, async () => {
try {
assert.strictEqual(store.getStore(), data);
return thenable();
} finally {
assert.strictEqual(store.getStore(), data);
}
});
// Resolving a thenable
store.run(data, () => {
assert.strictEqual(store.getStore(), data);
Promise.resolve(thenable());
assert.strictEqual(store.getStore(), data);
});
// Returning a thenable in a then handler
store.run(data, () => {
assert.strictEqual(store.getStore(), data);
Promise.resolve().then(() => thenable());
assert.strictEqual(store.getStore(), data);
});

View File

@ -0,0 +1,36 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
// Regression tests for https://github.com/nodejs/node/issues/40693
const assert = require('assert');
const fixtures = require('../common/fixtures');
const tls = require('tls');
const { AsyncLocalStorage } = require('async_hooks');
const options = {
cert: fixtures.readKey('rsa_cert.crt'),
key: fixtures.readKey('rsa_private.pem'),
rejectUnauthorized: false,
};
tls
.createServer(options, (socket) => {
socket.write('Hello, world!');
socket.pipe(socket);
})
.listen(0, function() {
const asyncLocalStorage = new AsyncLocalStorage();
const store = { val: 'abcd' };
asyncLocalStorage.run(store, () => {
const client = tls.connect({ port: this.address().port, ...options });
client.on('data', () => {
assert.deepStrictEqual(asyncLocalStorage.getStore(), store);
client.end();
this.close();
});
});
});

View File

@ -0,0 +1,18 @@
// Flags: --expose-internals
'use strict';
const common = require('../common');
const { internalBinding } = require('internal/test/binding');
const providers = internalBinding('async_wrap').Providers;
const assert = require('assert');
const { asyncWrapProviders } = require('async_hooks');
assert.ok(typeof asyncWrapProviders === 'object');
assert.deepStrictEqual(asyncWrapProviders, { __proto__: null, ...providers });
const providerKeys = Object.keys(asyncWrapProviders);
assert.throws(() => {
asyncWrapProviders[providerKeys[0]] = 'another value';
}, common.expectsError({
name: 'TypeError',
}), 'should not allow modify asyncWrap providers');

View File

@ -0,0 +1,91 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const { spawnSync } = require('child_process');
const async_hooks = require('async_hooks');
const initHooks = require('./init-hooks');
const arg = process.argv[2];
switch (arg) {
case 'test_init_callback':
initHooks({
oninit: common.mustCall(() => { throw new Error(arg); }),
}).enable();
new async_hooks.AsyncResource(`${arg}_type`);
return;
case 'test_callback': {
initHooks({
onbefore: common.mustCall(() => { throw new Error(arg); }),
}).enable();
const resource = new async_hooks.AsyncResource(`${arg}_type`);
resource.runInAsyncScope(() => {});
return;
}
case 'test_callback_abort':
initHooks({
oninit: common.mustCall(() => { throw new Error(arg); }),
}).enable();
new async_hooks.AsyncResource(`${arg}_type`);
return;
}
// This part should run only for the primary test
assert.ok(!arg);
{
// console.log should stay until this test's flakiness is solved
console.log('start case 1');
console.time('end case 1');
const child = spawnSync(process.execPath, [__filename, 'test_init_callback']);
assert.ifError(child.error);
const test_init_first_line = child.stderr.toString().split(/[\r\n]+/g)[0];
assert.strictEqual(test_init_first_line, 'Error: test_init_callback');
assert.strictEqual(child.status, 1);
console.timeEnd('end case 1');
}
{
console.log('start case 2');
console.time('end case 2');
const child = spawnSync(process.execPath, [__filename, 'test_callback']);
assert.ifError(child.error);
const test_callback_first_line = child.stderr.toString().split(/[\r\n]+/g)[0];
assert.strictEqual(test_callback_first_line, 'Error: test_callback');
assert.strictEqual(child.status, 1);
console.timeEnd('end case 2');
}
{
console.log('start case 3');
console.time('end case 3');
let program = process.execPath;
let args = [
'--abort-on-uncaught-exception', __filename, 'test_callback_abort' ];
let options = {};
if (!common.isWindows) {
[program, options] = common.escapePOSIXShell`ulimit -c 0 && exec "${program}" ${args[0]} "${args[1]}" ${args[2]}`;
args = [];
options.shell = true;
}
options.encoding = 'utf8';
const child = spawnSync(program, args, options);
if (common.isWindows) {
assert.strictEqual(child.status, 134);
assert.strictEqual(child.signal, null);
} else {
assert.strictEqual(child.status, null);
// Most posix systems will show 'SIGABRT', but alpine34 does not
if (child.signal !== 'SIGABRT') {
console.log(`primary received signal ${child.signal}\nchild's stderr:`);
console.log(child.stderr);
process.exit(1);
}
assert.strictEqual(child.signal, 'SIGABRT');
}
assert.strictEqual(child.stdout, '');
const firstLineStderr = child.stderr.split(/[\r\n]+/g)[0].trim();
assert.strictEqual(firstLineStderr, 'Error: test_callback_abort');
console.timeEnd('end case 3');
}

View File

@ -0,0 +1,46 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto) {
common.skip('missing crypto');
}
const { isMainThread } = require('worker_threads');
if (!isMainThread) {
common.skip('Worker bootstrapping works differently -> different async IDs');
}
const assert = require('assert');
const tick = require('../common/tick');
const initHooks = require('./init-hooks');
const { checkInvocations } = require('./hook-checks');
const crypto = require('crypto');
const hooks = initHooks();
hooks.enable();
crypto.pbkdf2('password', 'salt', 1, 20, 'sha256', common.mustCall(onpbkdf2));
function onpbkdf2() {
const as = hooks.activitiesOfTypes('PBKDF2REQUEST');
const a = as[0];
checkInvocations(a, { init: 1, before: 1 }, 'while in onpbkdf2 callback');
tick(2);
}
process.on('exit', onexit);
function onexit() {
hooks.disable();
hooks.sanityCheck('PBKDF2REQUEST');
const as = hooks.activitiesOfTypes('PBKDF2REQUEST');
assert.strictEqual(as.length, 1);
const a = as[0];
assert.strictEqual(a.type, 'PBKDF2REQUEST');
assert.strictEqual(typeof a.uid, 'number');
assert.strictEqual(a.triggerAsyncId, 1);
checkInvocations(a, { init: 1, before: 1, after: 1, destroy: 1 },
'when process exits');
}

View File

@ -0,0 +1,47 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto) {
common.skip('missing crypto');
}
const { isMainThread } = require('worker_threads');
if (!isMainThread) {
common.skip('Worker bootstrapping works differently -> different async IDs');
}
const assert = require('assert');
const tick = require('../common/tick');
const initHooks = require('./init-hooks');
const { checkInvocations } = require('./hook-checks');
const crypto = require('crypto');
const hooks = initHooks();
hooks.enable();
crypto.randomBytes(1, common.mustCall(onrandomBytes));
function onrandomBytes() {
const as = hooks.activitiesOfTypes('RANDOMBYTESREQUEST');
const a = as[0];
checkInvocations(a, { init: 1, before: 1 },
'while in onrandomBytes callback');
tick(2);
}
process.on('exit', onexit);
function onexit() {
hooks.disable();
hooks.sanityCheck('RANDOMBYTESREQUEST');
const as = hooks.activitiesOfTypes('RANDOMBYTESREQUEST');
assert.strictEqual(as.length, 1);
const a = as[0];
assert.strictEqual(a.type, 'RANDOMBYTESREQUEST');
assert.strictEqual(typeof a.uid, 'number');
assert.strictEqual(a.triggerAsyncId, 1);
checkInvocations(a, { init: 1, before: 1, after: 1, destroy: 1 },
'when process exits');
}

View File

@ -0,0 +1,97 @@
'use strict';
// Flags: --expose_gc
const common = require('../common');
const assert = require('assert');
const tick = require('../common/tick');
const { createHook, AsyncResource } = require('async_hooks');
// Test priority of destroy hook relative to nextTick,... and
// verify a microtask is scheduled in case a lot items are queued
const resType = 'MyResource';
let activeId = -1;
createHook({
init(id, type) {
if (type === resType) {
assert.strictEqual(activeId, -1);
activeId = id;
}
},
destroy(id) {
if (activeId === id) {
activeId = -1;
}
},
}).enable();
function testNextTick() {
assert.strictEqual(activeId, -1);
const res = new AsyncResource(resType);
assert.strictEqual(activeId, res.asyncId());
res.emitDestroy();
// nextTick has higher prio than emit destroy
process.nextTick(common.mustCall(() =>
assert.strictEqual(activeId, res.asyncId())),
);
}
function testQueueMicrotask() {
assert.strictEqual(activeId, -1);
const res = new AsyncResource(resType);
assert.strictEqual(activeId, res.asyncId());
res.emitDestroy();
// queueMicrotask has higher prio than emit destroy
queueMicrotask(common.mustCall(() =>
assert.strictEqual(activeId, res.asyncId())),
);
}
function testImmediate() {
assert.strictEqual(activeId, -1);
const res = new AsyncResource(resType);
assert.strictEqual(activeId, res.asyncId());
res.emitDestroy();
setImmediate(common.mustCall(() =>
assert.strictEqual(activeId, -1)),
);
}
function testPromise() {
assert.strictEqual(activeId, -1);
const res = new AsyncResource(resType);
assert.strictEqual(activeId, res.asyncId());
res.emitDestroy();
// Promise has higher prio than emit destroy
Promise.resolve().then(common.mustCall(() =>
assert.strictEqual(activeId, res.asyncId())),
);
}
async function testAwait() {
assert.strictEqual(activeId, -1);
const res = new AsyncResource(resType);
assert.strictEqual(activeId, res.asyncId());
res.emitDestroy();
for (let i = 0; i < 5000; i++) {
await Promise.resolve();
}
global.gc();
await Promise.resolve();
// Limit to trigger a microtask not yet reached
assert.strictEqual(activeId, res.asyncId());
for (let i = 0; i < 5000; i++) {
await Promise.resolve();
}
global.gc();
await Promise.resolve();
assert.strictEqual(activeId, -1);
}
testNextTick();
tick(2, testQueueMicrotask);
tick(4, testImmediate);
tick(6, testPromise);
tick(8, () => testAwait().then(common.mustCall()));

View File

@ -0,0 +1,23 @@
'use strict';
const common = require('../common');
const async_hooks = require('async_hooks');
const fs = require('fs');
let nestedCall = false;
async_hooks.createHook({
init: common.mustCall(() => {
nestedHook.disable();
if (!nestedCall) {
nestedCall = true;
fs.access(__filename, common.mustCall());
}
}, 2),
}).enable();
const nestedHook = async_hooks.createHook({
init: common.mustCall(2),
}).enable();
fs.access(__filename, common.mustCall());

View File

@ -0,0 +1,34 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const async_hooks = require('async_hooks');
const { AsyncResource } = async_hooks;
const { spawn } = require('child_process');
const initHooks = require('./init-hooks');
if (process.argv[2] === 'child') {
initHooks().enable();
class Foo extends AsyncResource {
constructor(type) {
super(type, async_hooks.executionAsyncId());
}
}
[null, undefined, 1, Date, {}, []].forEach((i) => {
assert.throws(() => new Foo(i), {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
});
});
} else {
const args = process.argv.slice(1).concat('child');
spawn(process.execPath, args)
.on('close', common.mustCall((code) => {
// No error because the type was defaulted
assert.strictEqual(code, 0);
}));
}

View File

@ -0,0 +1,103 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const tick = require('../common/tick');
const async_hooks = require('async_hooks');
const { AsyncResource } = async_hooks;
const initHooks = require('./init-hooks');
const { checkInvocations } = require('./hook-checks');
const hooks = initHooks();
hooks.enable();
assert.throws(
() => new AsyncResource(), {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
});
assert.throws(() => {
new AsyncResource('invalid_trigger_id', { triggerAsyncId: null });
}, {
code: 'ERR_INVALID_ASYNC_ID',
name: 'RangeError',
});
assert.strictEqual(
new AsyncResource('default_trigger_id').triggerAsyncId(),
async_hooks.executionAsyncId(),
);
// Create first custom event 'alcazares' with triggerAsyncId derived
// from async_hooks executionAsyncId
const alcaTriggerId = async_hooks.executionAsyncId();
const alcaEvent = new AsyncResource('alcazares', alcaTriggerId);
const alcazaresActivities = hooks.activitiesOfTypes([ 'alcazares' ]);
// Alcazares event was constructed and thus only has an `init` call
assert.strictEqual(alcazaresActivities.length, 1);
const alcazares = alcazaresActivities[0];
assert.strictEqual(alcazares.type, 'alcazares');
assert.strictEqual(typeof alcazares.uid, 'number');
assert.strictEqual(alcazares.triggerAsyncId, alcaTriggerId);
checkInvocations(alcazares, { init: 1 }, 'alcazares constructed');
assert.strictEqual(typeof alcaEvent.asyncId(), 'number');
assert.notStrictEqual(alcaEvent.asyncId(), alcaTriggerId);
assert.strictEqual(alcaEvent.triggerAsyncId(), alcaTriggerId);
alcaEvent.runInAsyncScope(() => {
checkInvocations(alcazares, { init: 1, before: 1 },
'alcazares emitted before');
});
checkInvocations(alcazares, { init: 1, before: 1, after: 1 },
'alcazares emitted after');
alcaEvent.runInAsyncScope(() => {
checkInvocations(alcazares, { init: 1, before: 2, after: 1 },
'alcazares emitted before again');
});
checkInvocations(alcazares, { init: 1, before: 2, after: 2 },
'alcazares emitted after again');
alcaEvent.emitDestroy();
tick(1, common.mustCall(tick1));
function tick1() {
checkInvocations(alcazares, { init: 1, before: 2, after: 2, destroy: 1 },
'alcazares emitted destroy');
// The below shows that we can pass any number as a trigger id
const pobTriggerId = 111;
const pobEvent = new AsyncResource('poblado', pobTriggerId);
const pobladoActivities = hooks.activitiesOfTypes([ 'poblado' ]);
const poblado = pobladoActivities[0];
assert.strictEqual(poblado.type, 'poblado');
assert.strictEqual(typeof poblado.uid, 'number');
assert.strictEqual(poblado.triggerAsyncId, pobTriggerId);
checkInvocations(poblado, { init: 1 }, 'poblado constructed');
pobEvent.runInAsyncScope(() => {
checkInvocations(poblado, { init: 1, before: 1 },
'poblado emitted before');
});
checkInvocations(poblado, { init: 1, before: 1, after: 1 },
'poblado emitted after');
// After we disable the hooks we shouldn't receive any events anymore
hooks.disable();
alcaEvent.emitDestroy();
tick(1, common.mustCall(tick2));
function tick2() {
checkInvocations(
alcazares, { init: 1, before: 2, after: 2, destroy: 1 },
'alcazares emitted destroy a second time after hooks disabled');
pobEvent.emitDestroy();
tick(1, common.mustCall(tick3));
}
function tick3() {
checkInvocations(poblado, { init: 1, before: 1, after: 1 },
'poblado emitted destroy after hooks disabled');
}
}

View File

@ -0,0 +1,11 @@
'use strict';
require('../common');
const assert = require('assert');
const async_hooks = require('async_hooks');
// Ensure that asyncResource.makeCallback returns the callback return value.
const a = new async_hooks.AsyncResource('foobar');
const ret = a.runInAsyncScope(() => {
return 1729;
});
assert.strictEqual(ret, 1729);

View File

@ -0,0 +1,71 @@
// Flags: --expose-internals
'use strict';
const common = require('../common');
const assert = require('assert');
const internal_async_hooks = require('internal/async_hooks');
const { spawn } = require('child_process');
const corruptedMsg = /async hook stack has become corrupted/;
const heartbeatMsg = /heartbeat: still alive/;
const {
newAsyncId, getDefaultTriggerAsyncId,
emitInit, emitBefore, emitAfter, emitDestroy,
} = internal_async_hooks;
const initHooks = require('./init-hooks');
if (process.argv[2] === 'child') {
const hooks = initHooks();
hooks.enable();
// Once 'destroy' has been emitted, we can no longer emit 'after'
// Emitting 'before', 'after' and then 'destroy'
{
const asyncId = newAsyncId();
const triggerId = getDefaultTriggerAsyncId();
emitInit(asyncId, 'event1', triggerId, {});
emitBefore(asyncId, triggerId);
emitAfter(asyncId);
emitDestroy(asyncId);
}
// Emitting 'after' after 'destroy'
{
const asyncId = newAsyncId();
const triggerId = getDefaultTriggerAsyncId();
emitInit(asyncId, 'event2', triggerId, {});
emitDestroy(asyncId);
console.log('heartbeat: still alive');
emitAfter(asyncId);
}
} else {
const args = ['--expose-internals']
.concat(process.argv.slice(1))
.concat('child');
let errData = Buffer.from('');
let outData = Buffer.from('');
const child = spawn(process.execPath, args);
child.stderr.on('data', (d) => { errData = Buffer.concat([ errData, d ]); });
child.stdout.on('data', (d) => { outData = Buffer.concat([ outData, d ]); });
child.on('close', common.mustCall((code, signal) => {
if ((common.isAIX ||
(common.isLinux && process.arch === 'x64')) &&
signal === 'SIGABRT') {
// XXX: The child process could be aborted due to unknown reasons. Work around it.
} else {
assert.strictEqual(signal, null);
assert.strictEqual(code, 1);
}
assert.match(outData.toString(), heartbeatMsg,
'did not crash until we reached offending line of code ' +
`(found ${outData})`);
assert.match(errData.toString(), corruptedMsg,
'printed error contains corrupted message ' +
`(found ${errData})`);
}));
}

View File

@ -0,0 +1,38 @@
'use strict';
// Flags: --expose-internals
const common = require('../common');
const assert = require('assert');
const async_hooks = require('internal/async_hooks');
const initHooks = require('./init-hooks');
const expectedId = async_hooks.newAsyncId();
const expectedTriggerId = async_hooks.newAsyncId();
const expectedType = 'test_emit_before_after_type';
// Verify that if there is no registered hook, then nothing will happen.
async_hooks.emitBefore(expectedId, expectedTriggerId);
async_hooks.emitAfter(expectedId);
const chkBefore = common.mustCall((id) => assert.strictEqual(id, expectedId));
const chkAfter = common.mustCall((id) => assert.strictEqual(id, expectedId));
const checkOnce = (fn) => {
let called = false;
return (...args) => {
if (called) return;
called = true;
fn(...args);
};
};
initHooks({
onbefore: checkOnce(chkBefore),
onafter: checkOnce(chkAfter),
allowNoInit: true,
}).enable();
async_hooks.emitInit(expectedId, expectedType, expectedTriggerId);
async_hooks.emitBefore(expectedId, expectedTriggerId);
async_hooks.emitAfter(expectedId);

View File

@ -0,0 +1,64 @@
// Flags: --expose-internals
'use strict';
const common = require('../common');
const assert = require('assert');
const internal_async_hooks = require('internal/async_hooks');
const { spawn } = require('child_process');
const corruptedMsg = /async hook stack has become corrupted/;
const heartbeatMsg = /heartbeat: still alive/;
const {
newAsyncId, getDefaultTriggerAsyncId,
emitInit, emitBefore, emitAfter, emitDestroy,
} = internal_async_hooks;
const initHooks = require('./init-hooks');
if (process.argv[2] === 'child') {
const hooks = initHooks();
hooks.enable();
// Once 'destroy' has been emitted, we can no longer emit 'before'
// Emitting 'before', 'after' and then 'destroy'
{
const asyncId = newAsyncId();
const triggerId = getDefaultTriggerAsyncId();
emitInit(asyncId, 'event1', triggerId, {});
emitBefore(asyncId, triggerId);
emitAfter(asyncId);
emitDestroy(asyncId);
}
// Emitting 'before' after 'destroy'
{
const asyncId = newAsyncId();
const triggerId = getDefaultTriggerAsyncId();
emitInit(asyncId, 'event2', triggerId, {});
emitDestroy(asyncId);
console.log('heartbeat: still alive');
emitBefore(asyncId, triggerId);
}
} else {
const args = ['--expose-internals']
.concat(process.argv.slice(1))
.concat('child');
let errData = Buffer.from('');
let outData = Buffer.from('');
const child = spawn(process.execPath, args);
child.stderr.on('data', (d) => { errData = Buffer.concat([ errData, d ]); });
child.stdout.on('data', (d) => { outData = Buffer.concat([ outData, d ]); });
child.on('close', common.mustCall((code) => {
assert.strictEqual(code, 1);
assert.match(outData.toString(), heartbeatMsg,
'did not crash until we reached offending line of code ' +
`(found ${outData})`);
assert.match(errData.toString(), corruptedMsg,
'printed error contains corrupted message ' +
`(found ${errData})`);
}));
}

View File

@ -0,0 +1,39 @@
'use strict';
// Flags: --expose-internals
const common = require('../common');
const assert = require('assert');
const async_hooks = require('internal/async_hooks');
const initHooks = require('./init-hooks');
const expectedId = async_hooks.newAsyncId();
const expectedTriggerId = async_hooks.newAsyncId();
const expectedType = 'test_emit_init_type';
const expectedResource = { key: 'test_emit_init_resource' };
const hooks1 = initHooks({
oninit: common.mustCall((id, type, triggerAsyncId, resource) => {
assert.strictEqual(id, expectedId);
assert.strictEqual(type, expectedType);
assert.strictEqual(triggerAsyncId, expectedTriggerId);
assert.strictEqual(resource.key, expectedResource.key);
}),
});
hooks1.enable();
async_hooks.emitInit(expectedId, expectedType, expectedTriggerId,
expectedResource);
hooks1.disable();
initHooks({
oninit: common.mustCall((id, type, triggerAsyncId, resource) => {
assert.strictEqual(id, expectedId);
assert.strictEqual(type, expectedType);
assert.notStrictEqual(triggerAsyncId, expectedTriggerId);
assert.strictEqual(resource.key, expectedResource.key);
}),
}).enable();
async_hooks.emitInit(expectedId, expectedType, null, expectedResource);

View File

@ -0,0 +1,268 @@
// Test Steps Explained
// ====================
//
// Initializing hooks:
//
// We initialize 3 hooks. For hook2 and hook3 we register a callback for the
// "before" and in case of hook3 also for the "after" invocations.
//
// Enabling hooks initially:
//
// We only enable hook1 and hook3 initially.
//
// Enabling hook2:
//
// When hook3's "before" invocation occurs we enable hook2. Since this
// happens right before calling `onfirstImmediate` hook2 will miss all hook
// invocations until then, including the "init" and "before" of the first
// Immediate.
// However afterwards it collects all invocations that follow on the first
// Immediate as well as all invocations on the second Immediate.
//
// This shows that a hook can enable another hook inside a life time event
// callback.
//
//
// Disabling hook1
//
// Since we registered the "before" callback for hook2 it will execute it
// right before `onsecondImmediate` is called.
// At that point we disable hook1 which is why it will miss all invocations
// afterwards and thus won't include the second "after" as well as the
// "destroy" invocations
//
// This shows that a hook can disable another hook inside a life time event
// callback.
//
// Disabling hook3
//
// When the second "after" invocation occurs (after onsecondImmediate), hook3
// disables itself.
// As a result it will not receive the "destroy" invocation.
//
// This shows that a hook can disable itself inside a life time event callback.
//
// Sample Test Log
// ===============
//
// - setting up first Immediate
// hook1.init.uid-5
// hook3.init.uid-5
// - finished setting first Immediate
//
// hook1.before.uid-5
// hook3.before.uid-5
// - enabled hook2
// - entering onfirstImmediate
//
// - setting up second Immediate
// hook1.init.uid-6
// hook3.init.uid-6
// hook2.init.uid-6
// - finished setting second Immediate
//
// - exiting onfirstImmediate
// hook1.after.uid-5
// hook3.after.uid-5
// hook2.after.uid-5
// hook1.destroy.uid-5
// hook3.destroy.uid-5
// hook2.destroy.uid-5
// hook1.before.uid-6
// hook3.before.uid-6
// hook2.before.uid-6
// - disabled hook1
// - entering onsecondImmediate
// - exiting onsecondImmediate
// hook3.after.uid-6
// - disabled hook3
// hook2.after.uid-6
// hook2.destroy.uid-6
'use strict';
const common = require('../common');
const assert = require('assert');
const tick = require('../common/tick');
const initHooks = require('./init-hooks');
const { checkInvocations } = require('./hook-checks');
const { isMainThread } = require('worker_threads');
if (!isMainThread)
common.skip('Worker bootstrapping works differently -> different timing');
// Include "Unknown"s because hook2 will not be able to identify
// the type of the first Immediate since it will miss its `init` invocation.
const types = [ 'Immediate', 'Unknown' ];
//
// Initializing hooks
//
const hook1 = initHooks();
const hook2 = initHooks({ onbefore: onhook2Before, allowNoInit: true });
const hook3 = initHooks({ onbefore: onhook3Before, onafter: onhook3After });
//
// Enabling hook1 and hook3 only, hook2 is still disabled
//
hook1.enable();
// Verify that the hook is enabled even if .enable() is called twice.
hook1.enable();
hook3.enable();
//
// Enabling hook2
//
let enabledHook2 = false;
function onhook3Before() {
if (enabledHook2) return;
hook2.enable();
enabledHook2 = true;
}
//
// Disabling hook1
//
let disabledHook3 = false;
function onhook2Before() {
if (disabledHook3) return;
hook1.disable();
// Verify that the hook is disabled even if .disable() is called twice.
hook1.disable();
disabledHook3 = true;
}
//
// Disabling hook3 during the second "after" invocations it sees
//
let count = 2;
function onhook3After() {
if (!--count) {
hook3.disable();
}
}
setImmediate(common.mustCall(onfirstImmediate));
//
// onfirstImmediate is called after all "init" and "before" callbacks of the
// active hooks were invoked
//
function onfirstImmediate() {
const as1 = hook1.activitiesOfTypes(types);
const as2 = hook2.activitiesOfTypes(types);
const as3 = hook3.activitiesOfTypes(types);
assert.strictEqual(as1.length, 1);
// hook2 was not enabled yet .. it is enabled after hook3's "before" completed
assert.strictEqual(as2.length, 0);
assert.strictEqual(as3.length, 1);
// Check that hook1 and hook3 captured the same Immediate and that it is valid
const firstImmediate = as1[0];
assert.strictEqual(as3[0].uid, as1[0].uid);
assert.strictEqual(firstImmediate.type, 'Immediate');
assert.strictEqual(typeof firstImmediate.uid, 'number');
assert.strictEqual(typeof firstImmediate.triggerAsyncId, 'number');
checkInvocations(as1[0], { init: 1, before: 1 },
'hook1[0]: on first immediate');
checkInvocations(as3[0], { init: 1, before: 1 },
'hook3[0]: on first immediate');
// Setup the second Immediate, note that now hook2 is enabled and thus
// will capture all lifetime events of this Immediate
setImmediate(common.mustCall(onsecondImmediate));
}
//
// Once we exit onfirstImmediate the "after" callbacks of the active hooks are
// invoked
//
let hook1First, hook2First, hook3First;
let hook1Second, hook2Second, hook3Second;
//
// onsecondImmediate is called after all "before" callbacks of the active hooks
// are invoked again
//
function onsecondImmediate() {
const as1 = hook1.activitiesOfTypes(types);
const as2 = hook2.activitiesOfTypes(types);
const as3 = hook3.activitiesOfTypes(types);
assert.strictEqual(as1.length, 2);
assert.strictEqual(as2.length, 2);
assert.strictEqual(as3.length, 2);
// Assign the info collected by each hook for each immediate for easier
// reference.
// hook2 saw the "init" of the second immediate before the
// "after" of the first which is why they are ordered the opposite way
hook1First = as1[0];
hook1Second = as1[1];
hook2First = as2[1];
hook2Second = as2[0];
hook3First = as3[0];
hook3Second = as3[1];
// Check that all hooks captured the same Immediate and that it is valid
const secondImmediate = hook1Second;
assert.strictEqual(hook2Second.uid, hook3Second.uid);
assert.strictEqual(hook1Second.uid, hook3Second.uid);
assert.strictEqual(secondImmediate.type, 'Immediate');
assert.strictEqual(typeof secondImmediate.uid, 'number');
assert.strictEqual(typeof secondImmediate.triggerAsyncId, 'number');
checkInvocations(hook1First, { init: 1, before: 1, after: 1, destroy: 1 },
'hook1First: on second immediate');
checkInvocations(hook1Second, { init: 1, before: 1 },
'hook1Second: on second immediate');
// hook2 missed the "init" and "before" since it was enabled after they
// occurred
checkInvocations(hook2First, { after: 1, destroy: 1 },
'hook2First: on second immediate');
checkInvocations(hook2Second, { init: 1, before: 1 },
'hook2Second: on second immediate');
checkInvocations(hook3First, { init: 1, before: 1, after: 1, destroy: 1 },
'hook3First: on second immediate');
checkInvocations(hook3Second, { init: 1, before: 1 },
'hook3Second: on second immediate');
tick(1);
}
//
// Once we exit onsecondImmediate the "after" callbacks of the active hooks are
// invoked again.
// During this second "after" invocation hook3 disables itself
// (see onhook3After).
//
process.on('exit', onexit);
function onexit() {
hook1.disable();
hook2.disable();
hook3.disable();
hook1.sanityCheck();
hook2.sanityCheck();
hook3.sanityCheck();
checkInvocations(hook1First, { init: 1, before: 1, after: 1, destroy: 1 },
'hook1First: when process exits');
// hook1 was disabled during hook2's "before" of the second immediate
// and thus did not see "after" and "destroy"
checkInvocations(hook1Second, { init: 1, before: 1 },
'hook1Second: when process exits');
// hook2 missed the "init" and "before" since it was enabled after they
// occurred
checkInvocations(hook2First, { after: 1, destroy: 1 },
'hook2First: when process exits');
checkInvocations(hook2Second, { init: 1, before: 1, after: 1, destroy: 1 },
'hook2Second: when process exits');
checkInvocations(hook3First, { init: 1, before: 1, after: 1, destroy: 1 },
'hook3First: when process exits');
// We don't see a "destroy" invocation here since hook3 disabled itself
// during its "after" invocation
checkInvocations(hook3Second, { init: 1, before: 1, after: 1 },
'hook3Second: when process exits');
}

View File

@ -0,0 +1,22 @@
'use strict';
const common = require('../common');
const async_hooks = require('async_hooks');
const fs = require('fs');
const nestedHook = async_hooks.createHook({
init: common.mustNotCall(),
});
let nestedCall = false;
async_hooks.createHook({
init: common.mustCall(() => {
nestedHook.enable();
if (!nestedCall) {
nestedCall = true;
fs.access(__filename, common.mustCall());
}
}, 2),
}).enable();
fs.access(__filename, common.mustCall());

View File

@ -0,0 +1,65 @@
'use strict';
const common = require('../common');
const initHooks = require('./init-hooks');
const { checkInvocations } = require('./hook-checks');
const fixtures = require('../common/fixtures');
if (!common.hasCrypto)
common.skip('missing crypto');
const http2 = require('http2');
const assert = require('assert');
const fs = require('fs');
// Checks that the async resource is not reused by FileHandle.
// Test is based on parallel\test-http2-respond-file-fd.js.
const hooks = initHooks();
hooks.enable();
const {
HTTP2_HEADER_CONTENT_TYPE,
} = http2.constants;
// Use large fixture to get several file operations.
const fname = fixtures.path('person-large.jpg');
const fd = fs.openSync(fname, 'r');
const server = http2.createServer();
server.on('stream', (stream) => {
stream.respondWithFD(fd, {
[HTTP2_HEADER_CONTENT_TYPE]: 'text/plain',
});
});
server.on('close', common.mustCall(() => fs.closeSync(fd)));
server.listen(0, () => {
const client = http2.connect(`http://localhost:${server.address().port}`);
const req = client.request();
req.on('response', common.mustCall());
req.on('data', () => {});
req.on('end', common.mustCall(() => {
client.close();
server.close();
}));
req.end();
});
process.on('exit', onExit);
function onExit() {
hooks.disable();
hooks.sanityCheck();
const activities = hooks.activities;
// Verify both invocations
const fsReqs = activities.filter((x) => x.type === 'FSREQCALLBACK');
assert.ok(fsReqs.length >= 2);
checkInvocations(fsReqs[0], { init: 1, destroy: 1 }, 'when process exits');
checkInvocations(fsReqs[1], { init: 1, destroy: 1 }, 'when process exits');
// Verify reuse handle has been wrapped
assert.ok(fsReqs[0].handle !== fsReqs[1].handle, 'Resource reused');
assert.ok(fsReqs[0].handle === fsReqs[1].handle.handle,
'Resource not wrapped correctly');
}

View File

@ -0,0 +1,42 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const initHooks = require('./init-hooks');
const tick = require('../common/tick');
const { checkInvocations } = require('./hook-checks');
const fs = require('fs');
const { isMainThread } = require('worker_threads');
if (!isMainThread) {
common.skip('Worker bootstrapping works differently -> different async IDs');
}
if (common.isIBMi) {
common.skip('IBMi does not support fs.watch()');
}
const hooks = initHooks();
hooks.enable();
const watcher = fs.watch(__filename, onwatcherChanged);
function onwatcherChanged() { }
watcher.close();
tick(2);
process.on('exit', onexit);
function onexit() {
hooks.disable();
hooks.sanityCheck('FSEVENTWRAP');
const as = hooks.activitiesOfTypes('FSEVENTWRAP');
assert.strictEqual(as.length, 1);
const a = as[0];
assert.strictEqual(a.type, 'FSEVENTWRAP');
assert.strictEqual(typeof a.uid, 'number');
assert.strictEqual(a.triggerAsyncId, 1);
checkInvocations(a, { init: 1, destroy: 1 }, 'when process exits');
}

View File

@ -0,0 +1,37 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const tick = require('../common/tick');
const initHooks = require('./init-hooks');
const { checkInvocations } = require('./hook-checks');
const fs = require('fs');
const hooks = initHooks();
hooks.enable();
fs.access(__filename, common.mustCall(onaccess));
function onaccess() {
const as = hooks.activitiesOfTypes('FSREQCALLBACK');
const a = as[0];
checkInvocations(a, { init: 1, before: 1 },
'while in onaccess callback');
tick(2);
}
process.on('exit', onexit);
function onexit() {
hooks.disable();
hooks.sanityCheck('FSREQCALLBACK');
const as = hooks.activitiesOfTypes('FSREQCALLBACK');
assert.strictEqual(as.length, 1);
const a = as[0];
assert.strictEqual(a.type, 'FSREQCALLBACK');
assert.strictEqual(typeof a.uid, 'number');
checkInvocations(a, { init: 1, before: 1, after: 1, destroy: 1 },
'when process exits');
}

View File

@ -0,0 +1,53 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const tick = require('../common/tick');
const initHooks = require('./init-hooks');
const { checkInvocations } = require('./hook-checks');
const fs = require('fs');
const { isMainThread } = require('worker_threads');
if (!isMainThread) {
common.skip('Worker bootstrapping works differently -> different async IDs');
}
const hooks = initHooks();
hooks.enable();
fs.readFile(__filename, common.mustCall(onread));
function onread() {
const as = hooks.activitiesOfTypes('FSREQCALLBACK');
let lastParent = 1;
for (let i = 0; i < as.length; i++) {
const a = as[i];
assert.strictEqual(a.type, 'FSREQCALLBACK');
assert.strictEqual(typeof a.uid, 'number');
assert.strictEqual(a.triggerAsyncId, lastParent);
lastParent = a.uid;
}
checkInvocations(as[0], { init: 1, before: 1, after: 1, destroy: 1 },
'reqwrap[0]: while in onread callback');
checkInvocations(as[1], { init: 1, before: 1, after: 1, destroy: 1 },
'reqwrap[1]: while in onread callback');
checkInvocations(as[2], { init: 1, before: 1, after: 1, destroy: 1 },
'reqwrap[2]: while in onread callback');
// This callback is called from within the last fs req callback therefore
// the last req is still going and after/destroy haven't been called yet
checkInvocations(as[3], { init: 1, before: 1 },
'reqwrap[3]: while in onread callback');
tick(2);
}
process.on('exit', onexit);
function onexit() {
hooks.disable();
hooks.sanityCheck('FSREQCALLBACK');
const as = hooks.activitiesOfTypes('FSREQCALLBACK');
const a = as.pop();
checkInvocations(a, { init: 1, before: 1, after: 1, destroy: 1 },
'when process exits');
}

View File

@ -0,0 +1,44 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const tick = require('../common/tick');
const initHooks = require('./init-hooks');
const { checkInvocations } = require('./hook-checks');
const dns = require('dns');
const { isMainThread } = require('worker_threads');
if (!isMainThread) {
common.skip('Worker bootstrapping works differently -> different async IDs');
}
const hooks = initHooks();
hooks.enable();
dns.lookup('www.google.com', 4, common.mustCall(onlookup));
function onlookup() {
// We don't care about the error here in order to allow
// tests to run offline (lookup will fail in that case and the err be set);
const as = hooks.activitiesOfTypes('GETADDRINFOREQWRAP');
assert.strictEqual(as.length, 1);
const a = as[0];
assert.strictEqual(a.type, 'GETADDRINFOREQWRAP');
assert.strictEqual(typeof a.uid, 'number');
assert.strictEqual(a.triggerAsyncId, 1);
checkInvocations(a, { init: 1, before: 1 }, 'while in onlookup callback');
tick(2);
}
process.on('exit', onexit);
function onexit() {
hooks.disable();
hooks.sanityCheck('GETADDRINFOREQWRAP');
const as = hooks.activitiesOfTypes('GETADDRINFOREQWRAP');
const a = as[0];
checkInvocations(a, { init: 1, before: 1, after: 1, destroy: 1 },
'when process exits');
}

View File

@ -0,0 +1,45 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const tick = require('../common/tick');
const initHooks = require('./init-hooks');
const { checkInvocations } = require('./hook-checks');
const dns = require('dns');
const { isMainThread } = require('worker_threads');
if (!isMainThread) {
common.skip('Worker bootstrapping works differently -> different async IDs');
}
const hooks = initHooks();
hooks.enable();
dns.lookupService('127.0.0.1', 80, common.mustCall(onlookupService));
function onlookupService() {
// We don't care about the error here in order to allow
// tests to run offline (lookup will fail in that case and the err be set)
const as = hooks.activitiesOfTypes('GETNAMEINFOREQWRAP');
assert.strictEqual(as.length, 1);
const a = as[0];
assert.strictEqual(a.type, 'GETNAMEINFOREQWRAP');
assert.strictEqual(typeof a.uid, 'number');
assert.strictEqual(a.triggerAsyncId, 1);
checkInvocations(a, { init: 1, before: 1 },
'while in onlookupService callback');
tick(2);
}
process.on('exit', onexit);
function onexit() {
hooks.disable();
hooks.sanityCheck('GETNAMEINFOREQWRAP');
const as = hooks.activitiesOfTypes('GETNAMEINFOREQWRAP');
const a = as[0];
checkInvocations(a, { init: 1, before: 1, after: 1, destroy: 1 },
'when process exits');
}

View File

@ -0,0 +1,26 @@
'use strict';
const common = require('../common');
const initHooks = require('./init-hooks');
const verifyGraph = require('./verify-graph');
const fs = require('fs');
const hooks = initHooks();
hooks.enable();
fs.readFile(__filename, common.mustCall(onread));
function onread() {}
process.on('exit', onexit);
function onexit() {
hooks.disable();
verifyGraph(
hooks,
[ { type: 'FSREQCALLBACK', id: 'fsreq:1', triggerAsyncId: null },
{ type: 'FSREQCALLBACK', id: 'fsreq:2', triggerAsyncId: 'fsreq:1' },
{ type: 'FSREQCALLBACK', id: 'fsreq:3', triggerAsyncId: 'fsreq:2' },
{ type: 'FSREQCALLBACK', id: 'fsreq:4', triggerAsyncId: 'fsreq:3' } ],
);
}

View File

@ -0,0 +1,53 @@
'use strict';
const common = require('../common');
if (!common.hasIPv6)
common.skip('IPv6 support required');
const initHooks = require('./init-hooks');
const verifyGraph = require('./verify-graph');
const http = require('http');
const hooks = initHooks();
hooks.enable();
const server = http.createServer(common.mustCall((req, res) => {
res.writeHead(200, { 'Connection': 'close' });
res.end();
server.close(common.mustCall());
}));
server.listen(0, common.mustCall(() => {
http.get({
host: '::1',
family: 6,
port: server.address().port,
}, common.mustCall());
}));
process.on('exit', () => {
hooks.disable();
verifyGraph(
hooks,
[ { type: 'TCPSERVERWRAP',
id: 'tcpserver:1',
triggerAsyncId: null },
{ type: 'TCPWRAP', id: 'tcp:1', triggerAsyncId: 'tcpserver:1' },
{ type: 'TCPCONNECTWRAP',
id: 'tcpconnect:1',
triggerAsyncId: 'tcp:1' },
{ type: 'HTTPCLIENTREQUEST',
id: 'httpclientrequest:1',
triggerAsyncId: 'tcpserver:1' },
{ type: 'TCPWRAP', id: 'tcp:2', triggerAsyncId: 'tcpserver:1' },
{ type: 'HTTPINCOMINGMESSAGE',
id: 'httpincomingmessage:1',
triggerAsyncId: 'tcp:2' },
{ type: 'Timeout',
id: 'timeout:1',
triggerAsyncId: null },
{ type: 'SHUTDOWNWRAP',
id: 'shutdown:1',
triggerAsyncId: 'tcp:2' } ],
);
});

View File

@ -0,0 +1,35 @@
'use strict';
const common = require('../common');
const initHooks = require('./init-hooks');
const verifyGraph = require('./verify-graph');
const TIMEOUT = 1;
const hooks = initHooks();
hooks.enable();
let count = 0;
const iv1 = setInterval(common.mustCall(onfirstInterval, 3), TIMEOUT);
let iv2;
function onfirstInterval() {
if (++count === 3) {
clearInterval(iv1);
iv2 = setInterval(common.mustCall(onsecondInterval, 1), TIMEOUT + 1);
}
}
function onsecondInterval() {
clearInterval(iv2);
}
process.on('exit', onexit);
function onexit() {
hooks.disable();
verifyGraph(
hooks,
[ { type: 'Timeout', id: 'timeout:1', triggerAsyncId: null },
{ type: 'Timeout', id: 'timeout:2', triggerAsyncId: 'timeout:1' }],
);
}

View File

@ -0,0 +1,32 @@
'use strict';
const common = require('../common');
const initHooks = require('./init-hooks');
const verifyGraph = require('./verify-graph');
const spawn = require('child_process').spawn;
const hooks = initHooks();
hooks.enable();
const sleep = spawn('sleep', [ '0.1' ]);
sleep
.on('exit', common.mustCall(onsleepExit))
.on('close', common.mustCall(onsleepClose));
function onsleepExit() {}
function onsleepClose() {}
process.on('exit', onexit);
function onexit() {
hooks.disable();
verifyGraph(
hooks,
[ { type: 'PROCESSWRAP', id: 'process:1', triggerAsyncId: null },
{ type: 'PIPEWRAP', id: 'pipe:1', triggerAsyncId: null },
{ type: 'PIPEWRAP', id: 'pipe:2', triggerAsyncId: null },
{ type: 'PIPEWRAP', id: 'pipe:3', triggerAsyncId: null } ],
);
}

View File

@ -0,0 +1,39 @@
'use strict';
const common = require('../common');
const initHooks = require('./init-hooks');
const verifyGraph = require('./verify-graph');
const net = require('net');
const tmpdir = require('../common/tmpdir');
tmpdir.refresh();
const hooks = initHooks();
hooks.enable();
const server = net.createServer((c) => {
c.end();
server.close();
}).listen(common.PIPE, common.mustCall(onlisten));
function onlisten() {
net.connect(common.PIPE, common.mustCall(onconnect));
}
function onconnect() {}
process.on('exit', onexit);
function onexit() {
hooks.disable();
verifyGraph(
hooks,
[ { type: 'PIPESERVERWRAP', id: 'pipeserver:1', triggerAsyncId: null },
{ type: 'PIPEWRAP', id: 'pipe:1', triggerAsyncId: 'pipeserver:1' },
{ type: 'PIPECONNECTWRAP', id: 'pipeconnect:1',
triggerAsyncId: 'pipe:1' },
{ type: 'PIPEWRAP', id: 'pipe:2', triggerAsyncId: 'pipeserver:1' },
{ type: 'SHUTDOWNWRAP', id: 'shutdown:1', triggerAsyncId: 'pipe:2' } ],
);
}

View File

@ -0,0 +1,46 @@
'use strict';
const common = require('../common');
if (!common.hasIPv6)
common.skip('IPv6 support required');
const initHooks = require('./init-hooks');
const verifyGraph = require('./verify-graph');
const net = require('net');
const hooks = initHooks();
hooks.enable();
const server = net
.createServer(onconnection)
.on('listening', common.mustCall(onlistening));
server.listen();
function onlistening() {
net.connect(server.address().port, common.mustCall(onconnected));
}
function onconnection(c) {
c.end();
this.close(onserverClosed);
}
function onconnected() {}
function onserverClosed() {}
process.on('exit', onexit);
function onexit() {
hooks.disable();
verifyGraph(
hooks,
[ { type: 'TCPSERVERWRAP', id: 'tcpserver:1', triggerAsyncId: null },
{ type: 'TCPWRAP', id: 'tcp:1', triggerAsyncId: 'tcpserver:1' },
{ type: 'GETADDRINFOREQWRAP',
id: 'getaddrinforeq:1', triggerAsyncId: 'tcp:1' },
{ type: 'TCPCONNECTWRAP',
id: 'tcpconnect:1', triggerAsyncId: 'tcp:1' },
{ type: 'TCPWRAP', id: 'tcp:2', triggerAsyncId: 'tcpserver:1' },
{ type: 'SHUTDOWNWRAP', id: 'shutdown:1', triggerAsyncId: 'tcp:2' } ],
);
}

View File

@ -0,0 +1,69 @@
'use strict';
const common = require('../common');
if (common.isWindows) {
common.skip('no signals on Windows');
}
const { isMainThread } = require('worker_threads');
if (!isMainThread) {
common.skip('No signal handling available in Workers');
}
const initHooks = require('./init-hooks');
const verifyGraph = require('./verify-graph');
const { exec } = require('child_process');
const hooks = initHooks();
hooks.enable();
const interval = setInterval(() => {}, 9999); // Keep event loop open
process.on('SIGUSR2', common.mustCall(onsigusr2, 2));
let count = 0;
exec(`kill -USR2 ${process.pid}`);
function onsigusr2() {
count++;
if (count === 1) {
// Trigger same signal handler again
exec(`kill -USR2 ${process.pid}`);
} else {
// Install another signal handler
process.removeAllListeners('SIGUSR2');
process.on('SIGUSR2', common.mustCall(onsigusr2Again));
exec(`kill -USR2 ${process.pid}`);
}
}
function onsigusr2Again() {
clearInterval(interval); // Let the event loop close
}
process.on('exit', onexit);
function onexit() {
hooks.disable();
verifyGraph(
hooks,
[ { type: 'SIGNALWRAP', id: 'signal:1', triggerAsyncId: null },
{ type: 'PROCESSWRAP', id: 'process:1', triggerAsyncId: null },
{ type: 'PIPEWRAP', id: 'pipe:1', triggerAsyncId: null },
{ type: 'PIPEWRAP', id: 'pipe:2', triggerAsyncId: null },
{ type: 'PIPEWRAP', id: 'pipe:3', triggerAsyncId: null },
{ type: 'PROCESSWRAP', id: 'process:2', triggerAsyncId: 'signal:1' },
{ type: 'PIPEWRAP', id: 'pipe:4', triggerAsyncId: 'signal:1' },
{ type: 'PIPEWRAP', id: 'pipe:5', triggerAsyncId: 'signal:1' },
{ type: 'PIPEWRAP', id: 'pipe:6', triggerAsyncId: 'signal:1' },
{ type: 'SIGNALWRAP', id: 'signal:2',
// TEST_THREAD_ID is set by tools/test.py. Adjust test results depending
// on whether the test was invoked via test.py or from the shell
// directly.
triggerAsyncId: process.env.TEST_THREAD_ID ? 'signal:1' : 'pipe:2' },
{ type: 'PROCESSWRAP', id: 'process:3', triggerAsyncId: 'signal:1' },
{ type: 'PIPEWRAP', id: 'pipe:7', triggerAsyncId: 'signal:1' },
{ type: 'PIPEWRAP', id: 'pipe:8', triggerAsyncId: 'signal:1' },
{ type: 'PIPEWRAP', id: 'pipe:9', triggerAsyncId: 'signal:1' } ],
);
}

View File

@ -0,0 +1,34 @@
'use strict';
require('../common');
const commonPath = require.resolve('../common');
const initHooks = require('./init-hooks');
const verifyGraph = require('./verify-graph');
const fs = require('fs');
const hooks = initHooks();
hooks.enable();
function onchange() { }
// Install first file watcher
fs.watchFile(__filename, onchange);
// Install second file watcher
fs.watchFile(commonPath, onchange);
// Remove first file watcher
fs.unwatchFile(__filename);
// Remove second file watcher
fs.unwatchFile(commonPath);
process.on('exit', onexit);
function onexit() {
hooks.disable();
verifyGraph(
hooks,
[ { type: 'STATWATCHER', id: 'statwatcher:1', triggerAsyncId: null },
{ type: 'STATWATCHER', id: 'statwatcher:2', triggerAsyncId: null } ],
);
}

View File

@ -0,0 +1,48 @@
'use strict';
const common = require('../common');
if (!common.hasIPv6)
common.skip('IPv6 support required');
const initHooks = require('./init-hooks');
const verifyGraph = require('./verify-graph');
const net = require('net');
const hooks = initHooks();
hooks.enable();
const server = net
.createServer(common.mustCall(onconnection))
.on('listening', common.mustCall(onlistening));
server.listen(0);
net.connect({ port: server.address().port, host: '::1' },
common.mustCall(onconnected));
function onlistening() {}
function onconnected() {}
function onconnection(c) {
c.end();
this.close(common.mustCall(onserverClosed));
}
function onserverClosed() {}
process.on('exit', onexit);
function onexit() {
hooks.disable();
verifyGraph(
hooks,
[ { type: 'TCPSERVERWRAP', id: 'tcpserver:1', triggerAsyncId: null },
{ type: 'TCPWRAP', id: 'tcp:1', triggerAsyncId: null },
{ type: 'TCPCONNECTWRAP',
id: 'tcpconnect:1', triggerAsyncId: 'tcp:1' },
{ type: 'TCPWRAP', id: 'tcp:2', triggerAsyncId: 'tcpserver:1' },
{ type: 'SHUTDOWNWRAP', id: 'shutdown:1', triggerAsyncId: 'tcp:2' } ],
);
}

View File

@ -0,0 +1,32 @@
'use strict';
const common = require('../common');
const initHooks = require('./init-hooks');
const verifyGraph = require('./verify-graph');
const TIMEOUT = 1;
const hooks = initHooks();
hooks.enable();
setTimeout(common.mustCall(ontimeout), TIMEOUT);
function ontimeout() {
setTimeout(onsecondTimeout, TIMEOUT + 1);
}
function onsecondTimeout() {
setTimeout(onthirdTimeout, TIMEOUT + 2);
}
function onthirdTimeout() {}
process.on('exit', onexit);
function onexit() {
hooks.disable();
verifyGraph(
hooks,
[ { type: 'Timeout', id: 'timeout:1', triggerAsyncId: null },
{ type: 'Timeout', id: 'timeout:2', triggerAsyncId: 'timeout:1' },
{ type: 'Timeout', id: 'timeout:3', triggerAsyncId: 'timeout:2' }],
);
}

View File

@ -0,0 +1,11 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const tls = require('tls');
tls.DEFAULT_MAX_VERSION = 'TLSv1.2';
require('./test-graph.tls-write.js');

View File

@ -0,0 +1,71 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
if (!common.hasIPv6)
common.skip('IPv6 support required');
const initHooks = require('./init-hooks');
const verifyGraph = require('./verify-graph');
const tls = require('tls');
const fixtures = require('../common/fixtures');
const hooks = initHooks();
hooks.enable();
//
// Creating server and listening on port
//
const server = tls
.createServer({
cert: fixtures.readKey('rsa_cert.crt'),
key: fixtures.readKey('rsa_private.pem'),
})
.on('listening', common.mustCall(onlistening))
.on('secureConnection', common.mustCall(onsecureConnection))
.listen(0);
function onlistening() {
//
// Creating client and connecting it to server
//
tls
.connect(server.address().port, { rejectUnauthorized: false })
.on('secureConnect', common.mustCall(onsecureConnect));
}
function onsecureConnection() {}
function onsecureConnect() {
// end() client socket, which causes slightly different hook events than
// destroy(), but with TLS1.3 destroy() rips the connection down before the
// server completes the handshake.
this.end();
// Closing server
server.close(common.mustCall(onserverClosed));
}
function onserverClosed() {}
process.on('exit', onexit);
function onexit() {
hooks.disable();
verifyGraph(
hooks,
[ { type: 'TCPSERVERWRAP', id: 'tcpserver:1', triggerAsyncId: null },
{ type: 'TCPWRAP', id: 'tcp:1', triggerAsyncId: 'tcpserver:1' },
{ type: 'TLSWRAP', id: 'tls:1', triggerAsyncId: 'tcpserver:1' },
{ type: 'GETADDRINFOREQWRAP',
id: 'getaddrinforeq:1', triggerAsyncId: 'tls:1' },
{ type: 'TCPCONNECTWRAP',
id: 'tcpconnect:1', triggerAsyncId: 'tcp:1' },
{ type: 'TCPWRAP', id: 'tcp:2', triggerAsyncId: 'tcpserver:1' },
{ type: 'TLSWRAP', id: 'tls:2', triggerAsyncId: 'tcpserver:1' },
],
);
}

View File

@ -0,0 +1,92 @@
'use strict';
// Flags: --expose-internals
const common = require('../common');
const initHooks = require('./init-hooks');
const { checkInvocations } = require('./hook-checks');
const assert = require('assert');
const { async_id_symbol } = require('internal/async_hooks').symbols;
const http = require('http');
// Checks that the async resource used in init in case of a reused handle
// is not reused. Test is based on parallel\test-async-hooks-http-agent.js.
const hooks = initHooks();
hooks.enable();
const reqAsyncIds = [];
let socket;
let responses = 0;
// Make sure a single socket is transparently reused for 2 requests.
const agent = new http.Agent({
keepAlive: true,
keepAliveMsecs: Infinity,
maxSockets: 1,
});
const verifyRequest = (idx) => (res) => {
reqAsyncIds[idx] = res.socket[async_id_symbol];
assert.ok(reqAsyncIds[idx] > 0, `${reqAsyncIds[idx]} > 0`);
if (socket) {
// Check that both requests share their socket.
assert.strictEqual(res.socket, socket);
} else {
socket = res.socket;
}
res.on('data', common.mustCallAtLeast());
res.on('end', common.mustCall(() => {
if (++responses === 2) {
// Clean up to let the event loop stop.
server.close();
agent.destroy();
}
}));
};
const server = http.createServer(common.mustCall((req, res) => {
req.once('data', common.mustCallAtLeast(() => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.write('foo');
}));
req.on('end', common.mustCall(() => {
res.end('bar');
}));
}, 2)).listen(0, common.mustCall(() => {
const port = server.address().port;
const payload = 'hello world';
// First request.
const r1 = http.request({
agent, port, method: 'POST',
}, common.mustCall(verifyRequest(0)));
r1.end(payload);
// Second request. Sent in parallel with the first one.
const r2 = http.request({
agent, port, method: 'POST',
}, common.mustCall(verifyRequest(1)));
r2.end(payload);
}));
process.on('exit', onExit);
function onExit() {
hooks.disable();
hooks.sanityCheck();
const activities = hooks.activities;
// Verify both invocations
const first = activities.filter((x) => x.uid === reqAsyncIds[0])[0];
checkInvocations(first, { init: 1, destroy: 1 }, 'when process exits');
const second = activities.filter((x) => x.uid === reqAsyncIds[1])[0];
checkInvocations(second, { init: 1, destroy: 1 }, 'when process exits');
// Verify reuse handle has been wrapped
assert.strictEqual(first.type, second.type);
assert.ok(first.handle !== second.handle, 'Resource reused');
assert.ok(first.handle === second.handle.handle,
'Resource not wrapped correctly');
}

View File

@ -0,0 +1,110 @@
'use strict';
// Flags: --expose-internals
const common = require('../common');
const initHooks = require('./init-hooks');
const { checkInvocations } = require('./hook-checks');
const assert = require('assert');
const { async_id_symbol } = require('internal/async_hooks').symbols;
const http = require('http');
// Checks that the async resource used in init in case of a reused handle
// is not reused. Test is based on parallel\test-async-hooks-http-agent.js.
const hooks = initHooks();
hooks.enable();
let asyncIdAtFirstReq;
let asyncIdAtSecondReq;
// Make sure a single socket is transparently reused for 2 requests.
const agent = new http.Agent({
keepAlive: true,
keepAliveMsecs: Infinity,
maxSockets: 1,
});
const server = http.createServer(common.mustCall((req, res) => {
req.once('data', common.mustCallAtLeast(() => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.write('foo');
}));
req.on('end', common.mustCall(() => {
res.end('bar');
}));
}, 2)).listen(0, common.mustCall(() => {
const port = server.address().port;
const payload = 'hello world';
// First request. This is useless except for adding a socket to the
// agents pool for reuse.
const r1 = http.request({
agent, port, method: 'POST',
}, common.mustCall((res) => {
// Remember which socket we used.
const socket = res.socket;
asyncIdAtFirstReq = socket[async_id_symbol];
assert.ok(asyncIdAtFirstReq > 0, `${asyncIdAtFirstReq} > 0`);
// Check that request and response share their socket.
assert.strictEqual(r1.socket, socket);
res.on('data', common.mustCallAtLeast());
res.on('end', common.mustCall(() => {
// setImmediate() to give the agent time to register the freed socket.
setImmediate(common.mustCall(() => {
// The socket is free for reuse now.
assert.strictEqual(socket[async_id_symbol], -1);
// Second request. To re-create the exact conditions from the
// referenced issue, we use a POST request without chunked encoding
// (hence the Content-Length header) and call .end() after the
// response header has already been received.
const r2 = http.request({
agent, port, method: 'POST', headers: {
'Content-Length': payload.length,
},
}, common.mustCall((res) => {
asyncIdAtSecondReq = res.socket[async_id_symbol];
assert.ok(asyncIdAtSecondReq > 0, `${asyncIdAtSecondReq} > 0`);
assert.strictEqual(r2.socket, socket);
// Empty payload, to hit the “right” code path.
r2.end('');
res.on('data', common.mustCallAtLeast());
res.on('end', common.mustCall(() => {
// Clean up to let the event loop stop.
server.close();
agent.destroy();
}));
}));
// Schedule a payload to be written immediately, but do not end the
// request just yet.
r2.write(payload);
}));
}));
}));
r1.end(payload);
}));
process.on('exit', onExit);
function onExit() {
hooks.disable();
hooks.sanityCheck();
const activities = hooks.activities;
// Verify both invocations
const first = activities.filter((x) => x.uid === asyncIdAtFirstReq)[0];
checkInvocations(first, { init: 1, destroy: 1 }, 'when process exits');
const second = activities.filter((x) => x.uid === asyncIdAtSecondReq)[0];
checkInvocations(second, { init: 1, destroy: 1 }, 'when process exits');
// Verify reuse handle has been wrapped
assert.strictEqual(first.type, second.type);
assert.ok(first.handle !== second.handle, 'Resource reused');
assert.ok(first.handle === second.handle.handle,
'Resource not wrapped correctly');
}

View File

@ -0,0 +1,75 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const { createHook } = require('async_hooks');
const http = require('http');
// Verify that resource emitted for an HTTPParser is not reused.
// Verify that correct create/destroy events are emitted.
const reused = Symbol('reused');
const reusedParser = [];
const incomingMessageParser = [];
const clientRequestParser = [];
const dupDestroys = [];
const destroyed = [];
createHook({
init(asyncId, type, triggerAsyncId, resource) {
switch (type) {
case 'HTTPINCOMINGMESSAGE':
incomingMessageParser.push(asyncId);
break;
case 'HTTPCLIENTREQUEST':
clientRequestParser.push(asyncId);
break;
}
if (resource[reused]) {
reusedParser.push(
`resource reused: ${asyncId}, ${triggerAsyncId}, ${type}`,
);
}
resource[reused] = true;
},
destroy(asyncId) {
if (destroyed.includes(asyncId)) {
dupDestroys.push(asyncId);
} else {
destroyed.push(asyncId);
}
},
}).enable();
const server = http.createServer((req, res) => {
res.end();
});
server.listen(0, common.mustCall(() => {
const PORT = server.address().port;
const url = `http://127.0.0.1:${PORT}`;
http.get(url, common.mustCall(() => {
server.close(common.mustCall(() => {
server.listen(PORT, common.mustCall(() => {
http.get(url, common.mustCall(() => {
server.close(common.mustCall(() => {
setTimeout(common.mustCall(verify), 200);
}));
}));
}));
}));
}));
}));
function verify() {
assert.strictEqual(reusedParser.length, 0);
assert.strictEqual(incomingMessageParser.length, 2);
assert.strictEqual(clientRequestParser.length, 2);
assert.strictEqual(dupDestroys.length, 0);
incomingMessageParser.forEach((id) => assert.ok(destroyed.includes(id)));
clientRequestParser.forEach((id) => assert.ok(destroyed.includes(id)));
}

View File

@ -0,0 +1,53 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const tick = require('../common/tick');
const initHooks = require('./init-hooks');
const { checkInvocations } = require('./hook-checks');
const hooks = initHooks();
hooks.enable();
const { HTTPParser } = require('_http_common');
const REQUEST = HTTPParser.REQUEST;
const kOnHeadersComplete = HTTPParser.kOnHeadersComplete | 0;
const request = Buffer.from(
'GET /hello HTTP/1.1\r\n\r\n',
);
const parser = new HTTPParser();
parser.initialize(REQUEST, {});
const as = hooks.activitiesOfTypes('HTTPINCOMINGMESSAGE');
const httpparser = as[0];
assert.strictEqual(as.length, 1);
assert.strictEqual(typeof httpparser.uid, 'number');
assert.strictEqual(typeof httpparser.triggerAsyncId, 'number');
checkInvocations(httpparser, { init: 1 }, 'when created new Httphttpparser');
parser[kOnHeadersComplete] = common.mustCall(onheadersComplete);
parser.execute(request, 0, request.length);
function onheadersComplete() {
checkInvocations(httpparser, { init: 1, before: 1 },
'when onheadersComplete called');
tick(1, common.mustCall(tick1));
}
function tick1() {
parser.close();
tick(1);
}
process.on('exit', onexit);
function onexit() {
hooks.disable();
hooks.sanityCheck('HTTPINCOMINGMESSAGE');
checkInvocations(httpparser, { init: 1, before: 1, after: 1, destroy: 1 },
'when process exits');
}

View File

@ -0,0 +1,64 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const tick = require('../common/tick');
const initHooks = require('./init-hooks');
const { checkInvocations } = require('./hook-checks');
const hooks = initHooks();
hooks.enable();
const { HTTPParser } = require('_http_common');
const RESPONSE = HTTPParser.RESPONSE;
const kOnHeadersComplete = HTTPParser.kOnHeadersComplete | 0;
const kOnBody = HTTPParser.kOnBody | 0;
const request = Buffer.from(
'HTTP/1.1 200 OK\r\n' +
'Content-Type: text/plain\r\n' +
'Content-Length: 4\r\n' +
'\r\n' +
'pong',
);
const parser = new HTTPParser();
parser.initialize(RESPONSE, {});
const as = hooks.activitiesOfTypes('HTTPCLIENTREQUEST');
const httpparser = as[0];
assert.strictEqual(as.length, 1);
assert.strictEqual(typeof httpparser.uid, 'number');
assert.strictEqual(typeof httpparser.triggerAsyncId, 'number');
checkInvocations(httpparser, { init: 1 }, 'when created new Httphttpparser');
parser[kOnHeadersComplete] = common.mustCall(onheadersComplete);
parser[kOnBody] = common.mustCall(onbody);
parser.execute(request, 0, request.length);
function onheadersComplete() {
checkInvocations(httpparser, { init: 1, before: 1 },
'when onheadersComplete called');
}
function onbody() {
checkInvocations(httpparser, { init: 1, before: 2, after: 1 },
'when onbody called');
tick(1, common.mustCall(tick1));
}
function tick1() {
parser.close();
tick(1);
}
process.on('exit', onexit);
function onexit() {
hooks.disable();
hooks.sanityCheck('HTTPCLIENTREQUEST');
checkInvocations(httpparser, { init: 1, before: 2, after: 2, destroy: 1 },
'when process exits');
}

View File

@ -0,0 +1,63 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const tick = require('../common/tick');
const initHooks = require('./init-hooks');
const { checkInvocations } = require('./hook-checks');
const hooks = initHooks();
hooks.enable();
// Install first immediate
setImmediate(common.mustCall(onimmediate));
const as = hooks.activitiesOfTypes('Immediate');
assert.strictEqual(as.length, 1);
const imd1 = as[0];
assert.strictEqual(imd1.type, 'Immediate');
assert.strictEqual(typeof imd1.uid, 'number');
assert.strictEqual(typeof imd1.triggerAsyncId, 'number');
checkInvocations(imd1, { init: 1 },
'imd1: when first set immediate installed');
let imd2;
function onimmediate() {
let as = hooks.activitiesOfTypes('Immediate');
assert.strictEqual(as.length, 1);
checkInvocations(imd1, { init: 1, before: 1 },
'imd1: when first set immediate triggered');
// Install second immediate
setImmediate(common.mustCall(onimmediateTwo));
as = hooks.activitiesOfTypes('Immediate');
assert.strictEqual(as.length, 2);
imd2 = as[1];
assert.strictEqual(imd2.type, 'Immediate');
assert.strictEqual(typeof imd2.uid, 'number');
assert.strictEqual(typeof imd2.triggerAsyncId, 'number');
checkInvocations(imd1, { init: 1, before: 1 },
'imd1: when second set immediate installed');
checkInvocations(imd2, { init: 1 },
'imd2: when second set immediate installed');
}
function onimmediateTwo() {
checkInvocations(imd1, { init: 1, before: 1, after: 1, destroy: 1 },
'imd1: when second set immediate triggered');
checkInvocations(imd2, { init: 1, before: 1 },
'imd2: when second set immediate triggered');
tick(1);
}
process.on('exit', onexit);
function onexit() {
hooks.disable();
hooks.sanityCheck('Immediate');
checkInvocations(imd1, { init: 1, before: 1, after: 1, destroy: 1 },
'imd1: when process exits');
checkInvocations(imd2, { init: 1, before: 1, after: 1, destroy: 1 },
'imd2: when process exits');
}

View File

@ -0,0 +1,63 @@
// Flags: --expose-internals
'use strict';
const common = require('../common');
const assert = require('assert');
const internal_async_hooks = require('internal/async_hooks');
const { spawn } = require('child_process');
const corruptedMsg = /async hook stack has become corrupted/;
const heartbeatMsg = /heartbeat: still alive/;
const {
newAsyncId, getDefaultTriggerAsyncId,
emitInit, emitBefore, emitAfter,
} = internal_async_hooks;
const initHooks = require('./init-hooks');
if (process.argv[2] === 'child') {
const hooks = initHooks();
hooks.enable();
// Async hooks enforce proper order of 'before' and 'after' invocations
// Proper ordering
{
const asyncId = newAsyncId();
const triggerId = getDefaultTriggerAsyncId();
emitInit(asyncId, 'event1', triggerId, {});
emitBefore(asyncId, triggerId);
emitAfter(asyncId);
}
// Improper ordering
// Emitting 'after' without 'before' which is illegal
{
const asyncId = newAsyncId();
const triggerId = getDefaultTriggerAsyncId();
emitInit(asyncId, 'event2', triggerId, {});
console.log('heartbeat: still alive');
emitAfter(asyncId);
}
} else {
const args = ['--expose-internals']
.concat(process.argv.slice(1))
.concat('child');
let errData = Buffer.from('');
let outData = Buffer.from('');
const child = spawn(process.execPath, args);
child.stderr.on('data', (d) => { errData = Buffer.concat([ errData, d ]); });
child.stdout.on('data', (d) => { outData = Buffer.concat([ outData, d ]); });
child.on('close', common.mustCall((code) => {
assert.strictEqual(code, 1);
assert.match(outData.toString(), heartbeatMsg,
'did not crash until we reached offending line of code ' +
`(found ${outData})`);
assert.match(errData.toString(), corruptedMsg,
'printed error contains corrupted message ' +
`(found ${errData})`);
}));
}

View File

@ -0,0 +1,74 @@
// Flags: --expose-internals
'use strict';
const common = require('../common');
const assert = require('assert');
const internal_async_hooks = require('internal/async_hooks');
const { spawn } = require('child_process');
const corruptedMsg = /async hook stack has become corrupted/;
const heartbeatMsg = /heartbeat: still alive/;
const {
newAsyncId, getDefaultTriggerAsyncId,
emitInit, emitBefore, emitAfter,
} = internal_async_hooks;
const initHooks = require('./init-hooks');
if (process.argv[2] === 'child') {
const hooks = initHooks();
hooks.enable();
// In both the below two cases 'before' of event2 is nested inside 'before'
// of event1.
// Therefore the 'after' of event2 needs to occur before the
// 'after' of event 1.
// The first test of the two below follows that rule,
// the second one doesn't.
const eventOneId = newAsyncId();
const eventTwoId = newAsyncId();
const triggerId = getDefaultTriggerAsyncId();
emitInit(eventOneId, 'event1', triggerId, {});
emitInit(eventTwoId, 'event2', triggerId, {});
// Proper unwind
emitBefore(eventOneId, triggerId);
emitBefore(eventTwoId, triggerId);
emitAfter(eventTwoId);
emitAfter(eventOneId);
// Improper unwind
emitBefore(eventOneId, triggerId);
emitBefore(eventTwoId, triggerId);
console.log('heartbeat: still alive');
emitAfter(eventOneId);
} else {
const args = ['--expose-internals']
.concat(process.argv.slice(1))
.concat('child');
let errData = Buffer.from('');
let outData = Buffer.from('');
const child = spawn(process.execPath, args);
child.stderr.on('data', (d) => { errData = Buffer.concat([ errData, d ]); });
child.stdout.on('data', (d) => { outData = Buffer.concat([ outData, d ]); });
child.on('close', common.mustCall((code, signal) => {
if ((common.isAIX ||
(common.isLinux && process.arch === 'x64')) &&
signal === 'SIGABRT') {
// XXX: The child process could be aborted due to unknown reasons. Work around it.
} else {
assert.strictEqual(signal, null);
assert.strictEqual(code, 1);
}
assert.match(outData.toString(), heartbeatMsg,
'did not crash until we reached offending line of code ' +
`(found ${outData})`);
assert.match(errData.toString(), corruptedMsg,
'printed error contains corrupted message ' +
`(found ${errData})`);
}));
}

View File

@ -0,0 +1,51 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const async_hooks = require('async_hooks');
// Checks that enabling async hooks in a callback actually
// triggers after & destroy as expected.
const fnsToTest = [setTimeout, (cb) => {
setImmediate(() => {
cb();
// We need to keep the event loop open for this to actually work
// since destroy hooks are triggered in unrefed Immediates
setImmediate(() => {
hook.disable();
});
});
}, (cb) => {
setImmediate(() => {
process.nextTick(() => {
cb();
// We need to keep the event loop open for this to actually work
// since destroy hooks are triggered in unrefed Immediates
setImmediate(() => {
hook.disable();
assert.strictEqual(fnsToTest.length, 0);
});
});
});
}];
const hook = async_hooks.createHook({
before: common.mustNotCall(),
after: common.mustCall(3),
destroy: common.mustCall(() => {
hook.disable();
nextTest();
}, 3),
});
nextTest();
function nextTest() {
if (fnsToTest.length > 0) {
fnsToTest.shift()(common.mustCall(() => {
hook.enable();
}));
}
}

View File

@ -0,0 +1,11 @@
'use strict';
const common = require('../common');
const net = require('net');
const server = net.createServer();
// This test was based on an error raised by Haraka.
// It caused server.getConnections to raise an exception.
// Ref: https://github.com/haraka/Haraka/pull/1951
server.getConnections(common.mustCall());

View File

@ -0,0 +1,28 @@
'use strict';
const common = require('../common');
// This tests ensures that the triggerId of the nextTick function sets the
// triggerAsyncId correctly.
const assert = require('assert');
const async_hooks = require('async_hooks');
const initHooks = require('./init-hooks');
const { checkInvocations } = require('./hook-checks');
const hooks = initHooks();
hooks.enable();
const rootAsyncId = async_hooks.executionAsyncId();
process.nextTick(common.mustCall(() => {
assert.strictEqual(async_hooks.triggerAsyncId(), rootAsyncId);
}));
process.on('exit', () => {
hooks.sanityCheck();
const as = hooks.activitiesOfTypes('TickObject');
checkInvocations(as[0], {
init: 1, before: 1, after: 1, destroy: 1,
}, 'when process exits');
});

View File

@ -0,0 +1,15 @@
'use strict';
// Flags: --no-force-async-hooks-checks --expose-internals
const common = require('../common');
const { isMainThread } = require('worker_threads');
if (!isMainThread) {
common.skip('Workers don\'t inherit per-env state like the check flag');
}
const async_hooks = require('internal/async_hooks');
// Negative asyncIds and invalid type name
async_hooks.emitInit(-1, null, -1, {});
async_hooks.emitBefore(-1, -1);
async_hooks.emitAfter(-1);
async_hooks.emitDestroy(-1);

View File

@ -0,0 +1,95 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const tick = require('../common/tick');
const initHooks = require('./init-hooks');
const { checkInvocations } = require('./hook-checks');
const tmpdir = require('../common/tmpdir');
const net = require('net');
tmpdir.refresh();
const hooks = initHooks();
hooks.enable();
let pipe1, pipe2;
let pipeserver;
let pipeconnect;
const server = net.createServer(common.mustCall((c) => {
c.end();
server.close();
process.nextTick(maybeOnconnect.bind(null, 'server'));
})).listen(common.PIPE, common.mustCall(onlisten));
function onlisten() {
const pipeservers = hooks.activitiesOfTypes('PIPESERVERWRAP');
let pipeconnects = hooks.activitiesOfTypes('PIPECONNECTWRAP');
assert.strictEqual(pipeservers.length, 1);
assert.strictEqual(pipeconnects.length, 0);
net.connect(common.PIPE,
common.mustCall(maybeOnconnect.bind(null, 'client')));
const pipes = hooks.activitiesOfTypes('PIPEWRAP');
pipeconnects = hooks.activitiesOfTypes('PIPECONNECTWRAP');
assert.strictEqual(pipes.length, 1);
assert.strictEqual(pipeconnects.length, 1);
pipeserver = pipeservers[0];
pipe1 = pipes[0];
pipeconnect = pipeconnects[0];
assert.strictEqual(pipeserver.type, 'PIPESERVERWRAP');
assert.strictEqual(pipe1.type, 'PIPEWRAP');
assert.strictEqual(pipeconnect.type, 'PIPECONNECTWRAP');
for (const a of [ pipeserver, pipe1, pipeconnect ]) {
assert.strictEqual(typeof a.uid, 'number');
assert.strictEqual(typeof a.triggerAsyncId, 'number');
checkInvocations(a, { init: 1 }, 'after net.connect');
}
}
const awaitOnconnectCalls = new Set(['server', 'client']);
function maybeOnconnect(source) {
// Both server and client must call onconnect. On most OS's waiting for
// the client is sufficient, but on CentOS 5 the sever needs to respond too.
assert.ok(awaitOnconnectCalls.size > 0);
awaitOnconnectCalls.delete(source);
if (awaitOnconnectCalls.size > 0) return;
const pipes = hooks.activitiesOfTypes('PIPEWRAP');
const pipeconnects = hooks.activitiesOfTypes('PIPECONNECTWRAP');
assert.strictEqual(pipes.length, 2);
assert.strictEqual(pipeconnects.length, 1);
pipe2 = pipes[1];
assert.strictEqual(typeof pipe2.uid, 'number');
assert.strictEqual(typeof pipe2.triggerAsyncId, 'number');
checkInvocations(pipeserver, { init: 1, before: 1, after: 1 },
'pipeserver, client connected');
checkInvocations(pipe1, { init: 1 }, 'pipe1, client connected');
checkInvocations(pipeconnect, { init: 1, before: 1 },
'pipeconnect, client connected');
checkInvocations(pipe2, { init: 1 }, 'pipe2, client connected');
tick(5);
}
process.on('exit', onexit);
function onexit() {
hooks.disable();
hooks.sanityCheck('PIPEWRAP');
hooks.sanityCheck('PIPESERVERWRAP');
hooks.sanityCheck('PIPECONNECTWRAP');
// TODO(thlorenz) why have some of those 'before' and 'after' called twice
checkInvocations(pipeserver, { init: 1, before: 1, after: 1, destroy: 1 },
'pipeserver, process exiting');
checkInvocations(pipe1, { init: 1, before: 2, after: 2, destroy: 1 },
'pipe1, process exiting');
checkInvocations(pipeconnect, { init: 1, before: 1, after: 1, destroy: 1 },
'pipeconnect, process exiting');
checkInvocations(pipe2, { init: 1, before: 2, after: 2, destroy: 1 },
'pipe2, process exiting');
}

View File

@ -0,0 +1,92 @@
// NOTE: this also covers process wrap as one is created along with the pipes
// when we launch the sleep process
'use strict';
// Flags: --expose-gc
const common = require('../common');
const assert = require('assert');
const tick = require('../common/tick');
const initHooks = require('./init-hooks');
const { checkInvocations } = require('./hook-checks');
const { spawn } = require('child_process');
const { isMainThread } = require('worker_threads');
if (!isMainThread) {
common.skip('Worker bootstrapping works differently -> different async IDs');
}
const hooks = initHooks();
hooks.enable();
const nodeVersionSpawn = spawn(process.execPath, [ '--version' ]);
nodeVersionSpawn
.on('exit', common.mustCall(onsleepExit))
.on('close', common.mustCall(onsleepClose));
// A process wrap and 3 pipe wraps for std{in,out,err} are initialized
// synchronously
const processes = hooks.activitiesOfTypes('PROCESSWRAP');
const pipes = hooks.activitiesOfTypes('PIPEWRAP');
assert.strictEqual(processes.length, 1);
assert.strictEqual(pipes.length, 3);
const processwrap = processes[0];
const pipe1 = pipes[0];
const pipe2 = pipes[1];
const pipe3 = pipes[2];
assert.strictEqual(processwrap.type, 'PROCESSWRAP');
assert.strictEqual(processwrap.triggerAsyncId, 1);
checkInvocations(processwrap, { init: 1 },
'processwrap when sleep.spawn was called');
[ pipe1, pipe2, pipe3 ].forEach((x) => {
assert.strictEqual(x.type, 'PIPEWRAP');
assert.strictEqual(x.triggerAsyncId, 1);
checkInvocations(x, { init: 1 }, 'pipe wrap when sleep.spawn was called');
});
function onsleepExit() {
checkInvocations(processwrap, { init: 1, before: 1 },
'processwrap while in onsleepExit callback');
}
function onsleepClose() {
tick(1, () =>
checkInvocations(
processwrap,
{ init: 1, before: 1, after: 1 },
'processwrap while in onsleepClose callback'),
);
}
process.on('exit', onexit);
function onexit() {
hooks.disable();
hooks.sanityCheck('PROCESSWRAP');
hooks.sanityCheck('PIPEWRAP');
checkInvocations(
processwrap,
{ init: 1, before: 1, after: 1 },
'processwrap while in onsleepClose callback');
[ pipe1, pipe2, pipe3 ].forEach((x) => {
assert.strictEqual(x.type, 'PIPEWRAP');
assert.strictEqual(x.triggerAsyncId, 1);
});
const ioEvents = Math.min(pipe2.before.length, pipe2.after.length);
// 2 events without any IO and at least one more for the node version data.
// Usually it is just one event, but it can be more.
assert.ok(ioEvents >= 3, `at least 3 stdout io events, got ${ioEvents}`);
checkInvocations(pipe1, { init: 1, before: 1, after: 1 },
'pipe wrap when sleep.spawn was called');
checkInvocations(pipe2, { init: 1, before: ioEvents, after: ioEvents },
'pipe wrap when sleep.spawn was called');
checkInvocations(pipe3, { init: 1, before: 2, after: 2 },
'pipe wrap when sleep.spawn was called');
}

View File

@ -0,0 +1,43 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const initHooks = require('./init-hooks');
const { checkInvocations } = require('./hook-checks');
const { isMainThread } = require('worker_threads');
if (!isMainThread) {
common.skip('Worker bootstrapping works differently -> different async IDs');
}
const p = new Promise(common.mustCall(function executor(resolve) {
resolve(5);
}));
p.then(function afterResolution(val) {
assert.strictEqual(val, 5);
return val;
});
// Init hooks after chained promise is created
const hooks = initHooks();
hooks._allowNoInit = true;
hooks.enable();
process.on('exit', function onexit() {
hooks.disable();
hooks.sanityCheck('PROMISE');
// Because the init event was never emitted the hooks listener doesn't
// know what the type was. Thus check for Unknown rather than PROMISE.
const as = hooks.activitiesOfTypes('PROMISE');
const unknown = hooks.activitiesOfTypes('Unknown');
assert.strictEqual(as.length, 0);
assert.strictEqual(unknown.length, 1);
const a0 = unknown[0];
assert.strictEqual(a0.type, 'Unknown');
assert.strictEqual(typeof a0.uid, 'number');
checkInvocations(a0, { before: 1, after: 1 }, 'when process exits');
});

View File

@ -0,0 +1,56 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const initHooks = require('./init-hooks');
const { checkInvocations } = require('./hook-checks');
const { isMainThread } = require('worker_threads');
if (!isMainThread) {
common.skip('Worker bootstrapping works differently -> different async IDs');
}
const hooks = initHooks();
hooks.enable();
const p = new Promise(common.mustCall(executor));
p.then(function afterResolution(val) {
assert.strictEqual(val, 5);
const as = hooks.activitiesOfTypes('PROMISE');
assert.strictEqual(as.length, 2);
checkInvocations(as[0], { init: 1 }, 'after resolution parent promise');
checkInvocations(as[1], { init: 1, before: 1 },
'after resolution child promise');
});
function executor(resolve) {
const as = hooks.activitiesOfTypes('PROMISE');
assert.strictEqual(as.length, 1);
const a = as[0];
checkInvocations(a, { init: 1 }, 'while in promise executor');
resolve(5);
}
process.on('exit', onexit);
function onexit() {
hooks.disable();
hooks.sanityCheck('PROMISE');
const as = hooks.activitiesOfTypes('PROMISE');
assert.strictEqual(as.length, 2);
const a0 = as[0];
assert.strictEqual(a0.type, 'PROMISE');
assert.strictEqual(typeof a0.uid, 'number');
assert.strictEqual(a0.triggerAsyncId, 1);
checkInvocations(a0, { init: 1 }, 'when process exits');
const a1 = as[1];
assert.strictEqual(a1.type, 'PROMISE');
assert.strictEqual(typeof a1.uid, 'number');
assert.strictEqual(a1.triggerAsyncId, a0.uid);
// We expect a destroy hook as well but we cannot guarantee predictable gc.
checkInvocations(a1, { init: 1, before: 1, after: 1 }, 'when process exits');
}

View File

@ -0,0 +1,42 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const initHooks = require('./init-hooks');
const { checkInvocations } = require('./hook-checks');
const p = new Promise(common.mustCall(function executor(resolve) {
resolve(5);
}));
// Init hooks after promise was created
const hooks = initHooks({ allowNoInit: true });
hooks.enable();
p.then(function afterResolution(val) {
assert.strictEqual(val, 5);
const as = hooks.activitiesOfTypes('PROMISE');
assert.strictEqual(as.length, 1);
checkInvocations(as[0], { init: 1, before: 1 },
'after resolution child promise');
return val;
});
process.on('exit', function onexit() {
hooks.disable();
hooks.sanityCheck('PROMISE');
const as = hooks.activitiesOfTypes('PROMISE');
assert.strictEqual(as.length, 1);
const a0 = as[0];
assert.strictEqual(a0.type, 'PROMISE');
assert.strictEqual(typeof a0.uid, 'number');
// We can't get the asyncId from the parent dynamically, since init was
// never called. However, it is known that the parent promise was created
// immediately before the child promise, thus there should only be one
// difference in id.
assert.strictEqual(a0.triggerAsyncId, a0.uid - 1);
// We expect a destroy hook as well but we cannot guarantee predictable gc.
checkInvocations(a0, { init: 1, before: 1, after: 1 }, 'when process exits');
});

View File

@ -0,0 +1,40 @@
'use strict';
// Flags: --expose-gc
const common = require('../common');
const assert = require('assert');
const tick = require('../common/tick');
const initHooks = require('./init-hooks');
const { checkInvocations } = require('./hook-checks');
const dns = require('dns');
const hooks = initHooks();
hooks.enable();
// Uses cares for queryA which in turn uses QUERYWRAP
dns.resolve('localhost', common.mustCall(onresolved));
function onresolved() {
const as = hooks.activitiesOfTypes('QUERYWRAP');
const a = as[0];
assert.strictEqual(as.length, 1);
checkInvocations(a, { init: 1, before: 1 }, 'while in onresolved callback');
tick(1E4);
}
process.on('exit', onexit);
function onexit() {
hooks.disable();
hooks.sanityCheck('QUERYWRAP');
const as = hooks.activitiesOfTypes('QUERYWRAP');
assert.strictEqual(as.length, 1);
const a = as[0];
assert.strictEqual(a.type, 'QUERYWRAP');
assert.strictEqual(typeof a.uid, 'number');
assert.strictEqual(typeof a.triggerAsyncId, 'number');
checkInvocations(a, { init: 1, before: 1, after: 1, destroy: 1 },
'when process exits');
}

View File

@ -0,0 +1,25 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const async_hooks = require('async_hooks');
const initHooks = require('./init-hooks');
const { checkInvocations } = require('./hook-checks');
const hooks = initHooks();
hooks.enable();
const rootAsyncId = async_hooks.executionAsyncId();
queueMicrotask(common.mustCall(() => {
assert.strictEqual(async_hooks.triggerAsyncId(), rootAsyncId);
}));
process.on('exit', () => {
hooks.sanityCheck();
const as = hooks.activitiesOfTypes('Microtask');
checkInvocations(as[0], {
init: 1, before: 1, after: 1, destroy: 1,
}, 'when process exits');
});

View File

@ -0,0 +1,63 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const initHooks = require('./init-hooks');
const { checkInvocations } = require('./hook-checks');
const net = require('net');
const hooks = initHooks();
hooks.enable();
const server = net
.createServer(onconnection)
.on('listening', common.mustCall(onlistening));
server.listen();
function onlistening() {
net.connect(server.address().port, common.mustCall(onconnected));
}
// It is non-deterministic in which order onconnection and onconnected fire.
// Therefore we track here if we ended the connection already or not.
let endedConnection = false;
function onconnection(c) {
assert.strictEqual(hooks.activitiesOfTypes('SHUTDOWNWRAP').length, 0);
c.end();
process.nextTick(() => {
endedConnection = true;
const as = hooks.activitiesOfTypes('SHUTDOWNWRAP');
assert.strictEqual(as.length, 1);
checkInvocations(as[0], { init: 1 }, 'after ending client connection');
this.close(onserverClosed);
});
}
function onconnected() {
if (endedConnection) {
assert.strictEqual(hooks.activitiesOfTypes('SHUTDOWNWRAP').length, 1);
} else {
assert.strictEqual(hooks.activitiesOfTypes('SHUTDOWNWRAP').length, 0);
}
}
function onserverClosed() {
const as = hooks.activitiesOfTypes('SHUTDOWNWRAP');
checkInvocations(as[0], { init: 1, before: 1, after: 1, destroy: 1 },
'when server closed');
}
process.on('exit', onexit);
function onexit() {
hooks.disable();
hooks.sanityCheck('SHUTDOWNWRAP');
const as = hooks.activitiesOfTypes('SHUTDOWNWRAP');
const a = as[0];
assert.strictEqual(a.type, 'SHUTDOWNWRAP');
assert.strictEqual(typeof a.uid, 'number');
assert.strictEqual(typeof a.triggerAsyncId, 'number');
checkInvocations(as[0], { init: 1, before: 1, after: 1, destroy: 1 },
'when process exits');
}

View File

@ -0,0 +1,107 @@
'use strict';
const common = require('../common');
if (common.isWindows) {
common.skip('no signals in Windows');
}
const { isMainThread } = require('worker_threads');
if (!isMainThread) {
common.skip('No signal handling available in Workers');
}
const assert = require('assert');
const initHooks = require('./init-hooks');
const { checkInvocations } = require('./hook-checks');
const exec = require('child_process').exec;
const hooks = initHooks();
hooks.enable();
// Keep the event loop open so process doesn't exit before receiving signals.
const interval = setInterval(() => {}, 9999);
process.on('SIGUSR2', common.mustCall(onsigusr2, 2));
const as = hooks.activitiesOfTypes('SIGNALWRAP');
assert.strictEqual(as.length, 1);
const signal1 = as[0];
assert.strictEqual(signal1.type, 'SIGNALWRAP');
assert.strictEqual(typeof signal1.uid, 'number');
assert.strictEqual(typeof signal1.triggerAsyncId, 'number');
checkInvocations(signal1, { init: 1 }, 'when SIGUSR2 handler is set up');
let count = 0;
exec(`kill -USR2 ${process.pid}`);
let signal2;
function onsigusr2() {
count++;
if (count === 1) {
// first invocation
checkInvocations(
signal1, { init: 1, before: 1 },
' signal1: when first SIGUSR2 handler is called for the first time');
// Trigger same signal handler again
exec(`kill -USR2 ${process.pid}`);
} else {
// second invocation
checkInvocations(
signal1, { init: 1, before: 2, after: 1 },
'signal1: when first SIGUSR2 handler is called for the second time');
// Install another signal handler
process.removeAllListeners('SIGUSR2');
process.on('SIGUSR2', common.mustCall(onsigusr2Again));
const as = hooks.activitiesOfTypes('SIGNALWRAP');
// The isTTY checks are needed to allow test to work whether run with
// test.py or directly with the node executable. The third signal event
// listener is the SIGWINCH handler that node installs when it thinks
// process.stdout is a tty.
const expectedLen = 2 + (!!process.stdout.isTTY || !!process.stderr.isTTY);
assert.strictEqual(as.length, expectedLen);
signal2 = as[expectedLen - 1]; // Last item in the array.
assert.strictEqual(signal2.type, 'SIGNALWRAP');
assert.strictEqual(typeof signal2.uid, 'number');
assert.strictEqual(typeof signal2.triggerAsyncId, 'number');
checkInvocations(
signal1, { init: 1, before: 2, after: 1 },
'signal1: when second SIGUSR2 handler is set up');
checkInvocations(
signal2, { init: 1 },
'signal2: when second SIGUSR2 handler is setup');
exec(`kill -USR2 ${process.pid}`);
}
}
function onsigusr2Again() {
clearInterval(interval);
setImmediate(() => {
checkInvocations(
signal1, { init: 1, before: 2, after: 2, destroy: 1 },
'signal1: when second SIGUSR2 handler is called');
checkInvocations(
signal2, { init: 1, before: 1 },
'signal2: when second SIGUSR2 handler is called');
});
}
process.on('exit', onexit);
function onexit() {
hooks.disable();
hooks.sanityCheck('SIGNALWRAP');
checkInvocations(
signal1, { init: 1, before: 2, after: 2, destroy: 1 },
'signal1: when second SIGUSR2 process exits');
// Second signal not destroyed yet since its event listener is still active
checkInvocations(
signal2, { init: 1, before: 1, after: 1 },
'signal2: when second SIGUSR2 process exits');
}

View File

@ -0,0 +1,108 @@
'use strict';
const common = require('../common');
const tmpdir = require('../common/tmpdir');
const assert = require('assert');
const initHooks = require('./init-hooks');
const { checkInvocations } = require('./hook-checks');
const fs = require('fs');
const { isMainThread } = require('worker_threads');
if (!isMainThread) {
common.skip('Worker bootstrapping works differently -> different async IDs');
}
tmpdir.refresh();
const file1 = tmpdir.resolve('file1');
const file2 = tmpdir.resolve('file2');
const onchangex = (x) => (curr, prev) => {
console.log(`Watcher: ${x}`);
console.log('current stat data:', curr);
console.log('previous stat data:', prev);
};
const checkWatcherStart = (name, watcher) => {
assert.strictEqual(watcher.type, 'STATWATCHER');
assert.strictEqual(typeof watcher.uid, 'number');
assert.strictEqual(watcher.triggerAsyncId, 1);
checkInvocations(watcher, { init: 1 },
`${name}: when started to watch file`);
};
const hooks = initHooks();
hooks.enable();
// Install first file watcher.
const w1 = fs.watchFile(file1, { interval: 10 }, onchangex('w1'));
let as = hooks.activitiesOfTypes('STATWATCHER');
assert.strictEqual(as.length, 1);
// Count all change events to account for all of them in checkInvocations.
let w1HookCount = 0;
w1.on('change', () => w1HookCount++);
const statwatcher1 = as[0];
checkWatcherStart('watcher1', statwatcher1);
// Install second file watcher
const w2 = fs.watchFile(file2, { interval: 10 }, onchangex('w2'));
as = hooks.activitiesOfTypes('STATWATCHER');
assert.strictEqual(as.length, 2);
// Count all change events to account for all of them in checkInvocations.
let w2HookCount = 0;
w2.on('change', () => w2HookCount++);
const statwatcher2 = as[1];
checkInvocations(statwatcher1, { init: 1 },
'watcher1: when started to watch second file');
checkWatcherStart('watcher2', statwatcher2);
setTimeout(() => fs.writeFileSync(file1, 'foo++'),
common.platformTimeout(100));
w1.on('change', common.mustCallAtLeast((curr, prev) => {
console.log('w1 change to', curr, 'from', prev);
// Wait until we get the write above.
if (prev.size !== 0 || curr.size !== 5)
return;
setImmediate(() => {
checkInvocations(statwatcher1,
{ init: 1, before: w1HookCount, after: w1HookCount },
'watcher1: when unwatched first file');
checkInvocations(statwatcher2, { init: 1 },
'watcher2: when unwatched first file');
setTimeout(() => fs.writeFileSync(file2, 'bar++'),
common.platformTimeout(100));
w2.on('change', common.mustCallAtLeast((curr, prev) => {
console.log('w2 change to', curr, 'from', prev);
// Wait until we get the write above.
if (prev.size !== 0 || curr.size !== 5)
return;
setImmediate(() => {
checkInvocations(statwatcher1,
{ init: 1, before: w1HookCount, after: w1HookCount },
'watcher1: when unwatched second file');
checkInvocations(statwatcher2,
{ init: 1, before: w2HookCount, after: w2HookCount },
'watcher2: when unwatched second file');
fs.unwatchFile(file1);
fs.unwatchFile(file2);
});
}));
});
}));
process.once('exit', () => {
hooks.disable();
hooks.sanityCheck('STATWATCHER');
checkInvocations(statwatcher1,
{ init: 1, before: w1HookCount, after: w1HookCount },
'watcher1: when process exits');
checkInvocations(statwatcher2,
{ init: 1, before: w2HookCount, after: w2HookCount },
'watcher2: when process exits');
});

View File

@ -0,0 +1,165 @@
// Covers TCPWRAP and related TCPCONNECTWRAP
'use strict';
const common = require('../common');
if (!common.hasIPv6)
common.skip('IPv6 support required');
const assert = require('assert');
const tick = require('../common/tick');
const initHooks = require('./init-hooks');
const { checkInvocations } = require('./hook-checks');
const net = require('net');
let tcp1, tcp2;
let tcpserver;
let tcpconnect;
const hooks = initHooks();
hooks.enable();
const server = net
.createServer(common.mustCall(onconnection))
.on('listening', common.mustCall(onlistening));
// Calling server.listen creates a TCPWRAP synchronously
{
server.listen(0);
const tcpsservers = hooks.activitiesOfTypes('TCPSERVERWRAP');
const tcpconnects = hooks.activitiesOfTypes('TCPCONNECTWRAP');
assert.strictEqual(tcpsservers.length, 1);
assert.strictEqual(tcpconnects.length, 0);
tcpserver = tcpsservers[0];
assert.strictEqual(tcpserver.type, 'TCPSERVERWRAP');
assert.strictEqual(typeof tcpserver.uid, 'number');
assert.strictEqual(typeof tcpserver.triggerAsyncId, 'number');
checkInvocations(tcpserver, { init: 1 }, 'when calling server.listen');
}
// Calling net.connect creates another TCPWRAP synchronously
{
net.connect(
{ port: server.address().port, host: '::1' },
common.mustCall(onconnected));
const tcps = hooks.activitiesOfTypes('TCPWRAP');
assert.strictEqual(tcps.length, 1);
process.nextTick(() => {
const tcpconnects = hooks.activitiesOfTypes('TCPCONNECTWRAP');
assert.strictEqual(tcpconnects.length, 1);
});
tcp1 = tcps[0];
assert.strictEqual(tcps.length, 1);
assert.strictEqual(tcp1.type, 'TCPWRAP');
assert.strictEqual(typeof tcp1.uid, 'number');
assert.strictEqual(typeof tcp1.triggerAsyncId, 'number');
checkInvocations(tcpserver, { init: 1 },
'tcpserver when client is connecting');
checkInvocations(tcp1, { init: 1 }, 'tcp1 when client is connecting');
}
function onlistening() {
assert.strictEqual(hooks.activitiesOfTypes('TCPWRAP').length, 1);
}
// Depending on timing we see client: onconnected or server: onconnection first
// Therefore we can't depend on any ordering, but when we see a connection for
// the first time we assign the tcpconnectwrap.
function ontcpConnection(serverConnection) {
if (tcpconnect != null) {
// When client receives connection first ('onconnected') and the server
// second then we see an 'after' here, otherwise not
const expected = serverConnection ?
{ init: 1, before: 1, after: 1 } :
{ init: 1, before: 1 };
checkInvocations(
tcpconnect, expected,
'tcpconnect: when both client and server received connection');
return;
}
// Only focusing on TCPCONNECTWRAP here
const tcpconnects = hooks.activitiesOfTypes('TCPCONNECTWRAP');
assert.strictEqual(tcpconnects.length, 1);
tcpconnect = tcpconnects[0];
assert.strictEqual(tcpconnect.type, 'TCPCONNECTWRAP');
assert.strictEqual(typeof tcpconnect.uid, 'number');
assert.strictEqual(typeof tcpconnect.triggerAsyncId, 'number');
// When client receives connection first ('onconnected'), we 'before' has
// been invoked at this point already, otherwise it only was 'init'ed
const expected = serverConnection ? { init: 1 } : { init: 1, before: 1 };
checkInvocations(tcpconnect, expected,
'tcpconnect: when tcp connection is established');
}
let serverConnected = false;
function onconnected() {
ontcpConnection(false);
// In the case that the client connects before the server TCPWRAP 'before'
// and 'after' weren't invoked yet. Also @see ontcpConnection.
const expected = serverConnected ?
{ init: 1, before: 1, after: 1 } :
{ init: 1 };
checkInvocations(tcpserver, expected, 'tcpserver when client connects');
checkInvocations(tcp1, { init: 1 }, 'tcp1 when client connects');
}
function onconnection(c) {
serverConnected = true;
ontcpConnection(true);
const tcps = hooks.activitiesOfTypes([ 'TCPWRAP' ]);
const tcpconnects = hooks.activitiesOfTypes('TCPCONNECTWRAP');
assert.strictEqual(tcps.length, 2);
assert.strictEqual(tcpconnects.length, 1);
tcp2 = tcps[1];
assert.strictEqual(tcp2.type, 'TCPWRAP');
assert.strictEqual(typeof tcp2.uid, 'number');
assert.strictEqual(typeof tcp2.triggerAsyncId, 'number');
checkInvocations(tcpserver, { init: 1, before: 1 },
'tcpserver when server receives connection');
checkInvocations(tcp1, { init: 1 }, 'tcp1 when server receives connection');
checkInvocations(tcp2, { init: 1 }, 'tcp2 when server receives connection');
c.end();
this.close(common.mustCall(onserverClosed));
}
function onserverClosed() {
setImmediate(() => {
checkInvocations(tcpserver, { init: 1, before: 1, after: 1, destroy: 1 },
'tcpserver when server is closed');
checkInvocations(tcp1, { init: 1, before: 2, after: 2, destroy: 1 },
'tcp1 after server is closed');
});
checkInvocations(tcp2, { init: 1, before: 1, after: 1 },
'tcp2 synchronously when server is closed');
tick(2, () => {
checkInvocations(tcp2, { init: 1, before: 2, after: 2, destroy: 1 },
'tcp2 when server is closed');
checkInvocations(tcpconnect, { init: 1, before: 1, after: 1, destroy: 1 },
'tcpconnect when server is closed');
});
}
process.on('exit', onexit);
function onexit() {
hooks.disable();
hooks.sanityCheck([ 'TCPWRAP', 'TCPSERVERWRAP', 'TCPCONNECTWRAP' ]);
checkInvocations(tcpserver, { init: 1, before: 1, after: 1, destroy: 1 },
'tcpserver when process exits');
checkInvocations(
tcp1, { init: 1, before: 2, after: 2, destroy: 1 },
'tcp1 when process exits');
checkInvocations(
tcp2, { init: 1, before: 2, after: 2, destroy: 1 },
'tcp2 when process exits');
checkInvocations(
tcpconnect, { init: 1, before: 1, after: 1, destroy: 1 },
'tcpconnect when process exits');
}

View File

@ -0,0 +1,46 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const initHooks = require('./init-hooks');
const { checkInvocations } = require('./hook-checks');
const TIMEOUT = common.platformTimeout(100);
const hooks = initHooks();
hooks.enable();
const interval = setInterval(common.mustCall(ontimeout, 2), TIMEOUT);
const as = hooks.activitiesOfTypes('Timeout');
assert.strictEqual(as.length, 1);
const t1 = as[0];
assert.strictEqual(t1.type, 'Timeout');
assert.strictEqual(typeof t1.uid, 'number');
assert.strictEqual(typeof t1.triggerAsyncId, 'number');
checkInvocations(t1, { init: 1 }, 't1: when timer installed');
let iter = 0;
function ontimeout() {
if (iter === 0) {
checkInvocations(t1, { init: 1, before: 1 }, 't1: when first timer fired');
} else {
checkInvocations(t1, { init: 1, before: 2, after: 1 },
't1: when first interval fired again');
clearInterval(interval);
return;
}
iter++;
throw new Error('setInterval Error');
}
process.once('uncaughtException', common.mustCall((err) => {
assert.strictEqual(err.message, 'setInterval Error');
}));
process.on('exit', () => {
hooks.disable();
hooks.sanityCheck('Timeout');
checkInvocations(t1, { init: 1, before: 2, after: 2, destroy: 1 },
't1: when process exits');
});

View File

@ -0,0 +1,61 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const tick = require('../common/tick');
const initHooks = require('./init-hooks');
const { checkInvocations } = require('./hook-checks');
const TIMEOUT = common.platformTimeout(100);
const hooks = initHooks();
hooks.enable();
// Install first timeout
setTimeout(common.mustCall(ontimeout), TIMEOUT);
const as = hooks.activitiesOfTypes('Timeout');
assert.strictEqual(as.length, 1);
const t1 = as[0];
assert.strictEqual(t1.type, 'Timeout');
assert.strictEqual(typeof t1.uid, 'number');
assert.strictEqual(typeof t1.triggerAsyncId, 'number');
checkInvocations(t1, { init: 1 }, 't1: when first timer installed');
let timer;
let t2;
function ontimeout() {
checkInvocations(t1, { init: 1, before: 1 }, 't1: when first timer fired');
setTimeout(onSecondTimeout, TIMEOUT).unref();
const as = hooks.activitiesOfTypes('Timeout');
t2 = as[1];
assert.strictEqual(as.length, 2);
checkInvocations(t1, { init: 1, before: 1 },
't1: when second timer installed');
checkInvocations(t2, { init: 1 },
't2: when second timer installed');
timer = setTimeout(common.mustNotCall(), 2 ** 31 - 1);
}
function onSecondTimeout() {
const as = hooks.activitiesOfTypes('Timeout');
assert.strictEqual(as.length, 3);
checkInvocations(t1, { init: 1, before: 1, after: 1 },
't1: when second timer fired');
checkInvocations(t2, { init: 1, before: 1 },
't2: when second timer fired');
clearTimeout(timer);
tick(2);
}
process.on('exit', onexit);
function onexit() {
hooks.disable();
hooks.sanityCheck('Timeout');
checkInvocations(t1, { init: 1, before: 1, after: 1, destroy: 1 },
't1: when process exits');
checkInvocations(t2, { init: 1, before: 1, after: 1, destroy: 1 },
't2: when process exits');
}

View File

@ -0,0 +1,139 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const fixtures = require('../common/fixtures');
const tls = require('tls');
const tick = require('../common/tick');
const initHooks = require('./init-hooks');
const { checkInvocations } = require('./hook-checks');
const hooks = initHooks();
hooks.enable();
// TODO(@sam-github) assumes server handshake completes before client, true for
// 1.2, not for 1.3. Might need a rewrite for TLS1.3.
tls.DEFAULT_MAX_VERSION = 'TLSv1.2';
//
// Creating server and listening on port
//
const server = tls
.createServer({
cert: fixtures.readKey('rsa_cert.crt'),
key: fixtures.readKey('rsa_private.pem'),
})
.on('listening', common.mustCall(onlistening))
.on('secureConnection', common.mustCall(onsecureConnection))
.listen(0);
let svr, client;
function onlistening() {
//
// Creating client and connecting it to server
//
tls
.connect(server.address().port, { rejectUnauthorized: false })
.on('secureConnect', common.mustCall(onsecureConnect));
const as = hooks.activitiesOfTypes('TLSWRAP');
assert.strictEqual(as.length, 1);
svr = as[0];
assert.strictEqual(svr.type, 'TLSWRAP');
assert.strictEqual(typeof svr.uid, 'number');
assert.strictEqual(typeof svr.triggerAsyncId, 'number');
checkInvocations(svr, { init: 1 }, 'server: when client connecting');
}
function onsecureConnection() {
//
// Server received client connection
//
const as = hooks.activitiesOfTypes('TLSWRAP');
assert.strictEqual(as.length, 2);
// TODO(@sam-github) This happens after onsecureConnect, with TLS1.3.
client = as[1];
assert.strictEqual(client.type, 'TLSWRAP');
assert.strictEqual(typeof client.uid, 'number');
assert.strictEqual(typeof client.triggerAsyncId, 'number');
// TODO(thlorenz) which callback did the server wrap execute that already
// finished as well?
checkInvocations(svr, { init: 1, before: 1, after: 1 },
'server: when server has secure connection');
checkInvocations(client, { init: 1, before: 2, after: 1 },
'client: when server has secure connection');
}
function onsecureConnect() {
//
// Client connected to server
//
checkInvocations(svr, { init: 1, before: 2, after: 1 },
'server: when client connected');
checkInvocations(client, { init: 1, before: 2, after: 2 },
'client: when client connected');
//
// Destroying client socket
//
this.destroy(); // This destroys client before server handshakes, with TLS1.3
checkInvocations(svr, { init: 1, before: 2, after: 1 },
'server: when destroying client');
checkInvocations(client, { init: 1, before: 2, after: 2 },
'client: when destroying client');
tick(5, tick1);
function tick1() {
checkInvocations(svr, { init: 1, before: 2, after: 2 },
'server: when client destroyed');
// TODO: why is client not destroyed here even after 5 ticks?
// or could it be that it isn't actually destroyed until
// the server is closed?
if (client.before.length < 3) {
tick(5, tick1);
return;
}
checkInvocations(client, { init: 1, before: 3, after: 3 },
'client: when client destroyed');
//
// Closing server
//
server.close(common.mustCall(onserverClosed));
// No changes to invocations until server actually closed below
checkInvocations(svr, { init: 1, before: 2, after: 2 },
'server: when closing server');
checkInvocations(client, { init: 1, before: 3, after: 3 },
'client: when closing server');
}
}
function onserverClosed() {
//
// Server closed
//
tick(1E4, common.mustCall(() => {
checkInvocations(svr, { init: 1, before: 2, after: 2 },
'server: when server closed');
checkInvocations(client, { init: 1, before: 3, after: 3 },
'client: when server closed');
}));
}
process.on('exit', onexit);
function onexit() {
hooks.disable();
hooks.sanityCheck('TLSWRAP');
checkInvocations(svr, { init: 1, before: 2, after: 2 },
'server: when process exits');
checkInvocations(client, { init: 1, before: 3, after: 3 },
'client: when process exits');
}

View File

@ -0,0 +1,47 @@
'use strict';
const common = require('../common');
const assert = require('assert');
// General hook test setup
const tick = require('../common/tick');
const initHooks = require('./init-hooks');
const { checkInvocations } = require('./hook-checks');
const hooks = initHooks();
hooks.enable();
if (!process.stdin.isTTY)
return common.skip('no valid readable TTY available');
// test specific setup
const checkInitOpts = { init: 1 };
const checkEndedOpts = { init: 1, before: 1, after: 1, destroy: 1 };
// test code
//
// listen to stdin except on Windows
const activities = hooks.activitiesOfTypes('TTYWRAP');
assert.strictEqual(activities.length, 1);
const tty = activities[0];
assert.strictEqual(tty.type, 'TTYWRAP');
assert.strictEqual(typeof tty.uid, 'number');
assert.strictEqual(typeof tty.triggerAsyncId, 'number');
checkInvocations(tty, checkInitOpts, 'when tty created');
const delayedOnCloseHandler = common.mustCall(() => {
checkInvocations(tty, checkEndedOpts, 'when tty ended');
});
process.stdin.on('error', (err) => assert.fail(err));
process.stdin.on('close', common.mustCall(() =>
tick(2, delayedOnCloseHandler),
));
process.stdin.destroy();
checkInvocations(tty, checkInitOpts, 'when tty.end() was invoked');
process.on('exit', () => {
hooks.disable();
hooks.sanityCheck('TTYWRAP');
checkInvocations(tty, checkEndedOpts, 'when process exits');
});

View File

@ -0,0 +1,56 @@
// Flags: --test-udp-no-try-send
'use strict';
const common = require('../common');
const assert = require('assert');
const initHooks = require('./init-hooks');
const { checkInvocations } = require('./hook-checks');
const dgram = require('dgram');
const hooks = initHooks();
hooks.enable();
let send;
const sock = dgram
.createSocket('udp4')
.on('listening', common.mustCall(onlistening))
.bind();
function onlistening() {
sock.send(
Buffer.alloc(2), 0, 2, sock.address().port,
undefined, common.mustCall(onsent));
// Init not called synchronously because dns lookup always wraps
// callback in a next tick even if no lookup is needed
// TODO (trevnorris) submit patch to fix creation of tick objects and instead
// create the send wrap synchronously.
assert.strictEqual(hooks.activitiesOfTypes('UDPSENDWRAP').length, 0);
}
function onsent() {
const as = hooks.activitiesOfTypes('UDPSENDWRAP');
send = as[0];
assert.strictEqual(as.length, 1);
assert.strictEqual(send.type, 'UDPSENDWRAP');
assert.strictEqual(typeof send.uid, 'number');
assert.strictEqual(typeof send.triggerAsyncId, 'number');
checkInvocations(send, { init: 1, before: 1 }, 'when message sent');
sock.close(common.mustCall(onsockClosed));
}
function onsockClosed() {
checkInvocations(send, { init: 1, before: 1, after: 1 }, 'when sock closed');
}
process.on('exit', onexit);
function onexit() {
hooks.disable();
hooks.sanityCheck('UDPSENDWRAP');
checkInvocations(send, { init: 1, before: 1, after: 1, destroy: 1 },
'when process exits');
}

View File

@ -0,0 +1,37 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const tick = require('../common/tick');
const initHooks = require('./init-hooks');
const { checkInvocations } = require('./hook-checks');
const dgram = require('dgram');
const hooks = initHooks();
hooks.enable();
const sock = dgram.createSocket('udp4');
const as = hooks.activitiesOfTypes('UDPWRAP');
const udpwrap = as[0];
assert.strictEqual(as.length, 1);
assert.strictEqual(udpwrap.type, 'UDPWRAP');
assert.strictEqual(typeof udpwrap.uid, 'number');
assert.strictEqual(typeof udpwrap.triggerAsyncId, 'number');
checkInvocations(udpwrap, { init: 1 }, 'after dgram.createSocket call');
sock.close(common.mustCall(onsockClosed));
function onsockClosed() {
checkInvocations(udpwrap, { init: 1 }, 'when socket is closed');
tick(2);
}
process.on('exit', onexit);
function onexit() {
hooks.disable();
hooks.sanityCheck('UDPWRAP');
checkInvocations(udpwrap, { init: 1, destroy: 1 },
'when process exits');
}

View File

@ -0,0 +1,17 @@
'use strict';
const common = require('../common');
const initHooks = require('./init-hooks');
const hooks = initHooks();
hooks.enable();
setImmediate(() => {
throw new Error();
});
setTimeout(() => {
throw new Error();
}, 1);
process.on('uncaughtException', common.mustCall(2));

View File

@ -0,0 +1,29 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const initHooks = require('./init-hooks');
const async_hooks = require('async_hooks');
const { isMainThread } = require('worker_threads');
if (!isMainThread) {
common.skip('Worker bootstrapping works differently -> different async IDs');
}
const promiseAsyncIds = [];
const hooks = initHooks({
oninit(asyncId, type) {
if (type === 'PROMISE') {
promiseAsyncIds.push(asyncId);
}
},
});
hooks.enable();
Promise.reject();
process.on('unhandledRejection', common.mustCall(() => {
assert.strictEqual(promiseAsyncIds.length, 1);
assert.strictEqual(async_hooks.executionAsyncId(), promiseAsyncIds[0]);
}));

View File

@ -0,0 +1,113 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const initHooks = require('./init-hooks');
const { checkInvocations } = require('./hook-checks');
const net = require('net');
const hooks = initHooks();
hooks.enable();
//
// Creating server and listening on port
//
const server = net.createServer()
.on('listening', common.mustCall(onlistening))
.on('connection', common.mustCall(onconnection))
.listen(0);
assert.strictEqual(hooks.activitiesOfTypes('WRITEWRAP').length, 0);
function onlistening() {
assert.strictEqual(hooks.activitiesOfTypes('WRITEWRAP').length, 0);
//
// Creating client and connecting it to server
//
net
.connect(server.address().port)
.on('connect', common.mustCall(onconnect));
assert.strictEqual(hooks.activitiesOfTypes('WRITEWRAP').length, 0);
}
function checkDestroyedWriteWraps(n, stage) {
const as = hooks.activitiesOfTypes('WRITEWRAP');
assert.strictEqual(as.length, n,
`${as.length} out of ${n} WRITEWRAPs when ${stage}`);
function checkValidWriteWrap(w) {
assert.strictEqual(w.type, 'WRITEWRAP');
assert.strictEqual(typeof w.uid, 'number');
assert.strictEqual(typeof w.triggerAsyncId, 'number');
checkInvocations(w, { init: 1 }, `when ${stage}`);
}
as.forEach(checkValidWriteWrap);
}
function onconnection(conn) {
conn.write('hi'); // Let the client know we're ready.
conn.resume();
//
// Server received client connection
//
checkDestroyedWriteWraps(0, 'server got connection');
}
function onconnect() {
//
// Client connected to server
//
checkDestroyedWriteWraps(0, 'client connected');
this.once('data', common.mustCall(ondata));
}
function ondata() {
//
// Writing data to client socket
//
const write = () => {
let writeFinished = false;
this.write('f'.repeat(1280000), () => {
writeFinished = true;
});
process.nextTick(() => {
if (writeFinished) {
// Synchronous finish, write more data immediately.
writeFinished = false;
write();
} else {
// Asynchronous write; this is what we are here for.
onafterwrite(this);
}
});
};
write();
}
function onafterwrite(self) {
checkDestroyedWriteWraps(1, 'client destroyed');
self.end();
checkDestroyedWriteWraps(1, 'client destroyed');
//
// Closing server
//
server.close(common.mustCall(onserverClosed));
checkDestroyedWriteWraps(1, 'server closing');
}
function onserverClosed() {
checkDestroyedWriteWraps(1, 'server closed');
}
process.on('exit', onexit);
function onexit() {
hooks.disable();
hooks.sanityCheck('WRITEWRAP');
checkDestroyedWriteWraps(1, 'process exits');
}

View File

@ -0,0 +1,75 @@
// Flags: --expose-internals
'use strict';
const common = require('../common');
const assert = require('assert');
const initHooks = require('./init-hooks');
const { checkInvocations } = require('./hook-checks');
const hooks = initHooks();
hooks.enable();
const { internalBinding } = require('internal/test/binding');
const { Zlib } = internalBinding('zlib');
const constants = require('zlib').constants;
const handle = new Zlib(constants.DEFLATE);
const as = hooks.activitiesOfTypes('ZLIB');
assert.strictEqual(as.length, 1);
const hdl = as[0];
assert.strictEqual(hdl.type, 'ZLIB');
assert.strictEqual(typeof hdl.uid, 'number');
assert.strictEqual(typeof hdl.triggerAsyncId, 'number');
checkInvocations(hdl, { init: 1 }, 'when created handle');
// Store all buffers together so that they do not get
// garbage collected.
const buffers = {
writeResult: new Uint32Array(2),
dictionary: new Uint8Array(0),
inBuf: new Uint8Array([0x78]),
outBuf: new Uint8Array(1),
};
handle.init(
constants.Z_DEFAULT_WINDOWBITS,
constants.Z_MIN_LEVEL,
constants.Z_DEFAULT_MEMLEVEL,
constants.Z_DEFAULT_STRATEGY,
buffers.writeResult,
function processCallback() { this.cb(); },
buffers.dictionary,
);
checkInvocations(hdl, { init: 1 }, 'when initialized handle');
let count = 2;
handle.cb = common.mustCall(onwritten, 2);
handle.write(true, buffers.inBuf, 0, 1, buffers.outBuf, 0, 1);
checkInvocations(hdl, { init: 1 }, 'when invoked write() on handle');
function onwritten() {
if (--count) {
// first write
checkInvocations(hdl, { init: 1, before: 1 },
'when wrote to handle the first time');
handle.write(true, buffers.inBuf, 0, 1, buffers.outBuf, 0, 1);
} else {
// second write
checkInvocations(hdl, { init: 1, before: 2, after: 1 },
'when wrote to handle the second time');
}
}
process.on('exit', onexit);
function onexit() {
hooks.disable();
hooks.sanityCheck('ZLIB');
// TODO: destroy never called here even with large amounts of ticks
// is that correct?
checkInvocations(hdl, { init: 1, before: 2, after: 2 }, 'when process exits');
// Do something with `buffers` to keep them alive until here.
buffers.buffers = buffers;
}

View File

@ -0,0 +1,6 @@
import sys, os
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
import testpy
def GetConfiguration(context, root):
return testpy.ParallelTestConfiguration(context, root, 'async-hooks')

View File

@ -0,0 +1,139 @@
'use strict';
require('../common');
const assert = require('assert');
const util = require('util');
function findInGraph(graph, type, n) {
let found = 0;
for (let i = 0; i < graph.length; i++) {
const node = graph[i];
if (node.type === type) found++;
if (found === n) return node;
}
}
function pruneTickObjects(activities) {
// Remove one TickObject on each pass until none is left anymore
// not super efficient, but simplest especially to handle
// multiple TickObjects in a row
const tickObject = {
found: true,
index: null,
data: null,
};
if (!Array.isArray(activities))
return activities;
while (tickObject.found) {
for (let i = 0; i < activities.length; i++) {
if (activities[i].type === 'TickObject') {
tickObject.index = i;
break;
} else if (i + 1 >= activities.length) {
tickObject.found = false;
}
}
if (tickObject.found) {
// Point all triggerAsyncIds that point to the tickObject
// to its triggerAsyncId and finally remove it from the activities
tickObject.data = activities[tickObject.index];
const triggerId = {
new: tickObject.data.triggerAsyncId,
old: tickObject.data.uid,
};
activities.forEach(function repointTriggerId(x) {
if (x.triggerAsyncId === triggerId.old)
x.triggerAsyncId = triggerId.new;
});
activities.splice(tickObject.index, 1);
}
}
return activities;
}
module.exports = function verifyGraph(hooks, graph) {
pruneTickObjects(hooks);
// Map actual ids to standin ids defined in the graph
const idtouid = {};
const uidtoid = {};
const typeSeen = {};
const errors = [];
const activities = pruneTickObjects(hooks.activities);
activities.forEach(processActivity);
function processActivity(x) {
typeSeen[x.type] ||= 0;
typeSeen[x.type]++;
const node = findInGraph(graph, x.type, typeSeen[x.type]);
if (node == null) return;
idtouid[node.id] = x.uid;
uidtoid[x.uid] = node.id;
if (node.triggerAsyncId == null) return;
const tid = idtouid[node.triggerAsyncId];
if (x.triggerAsyncId === tid) return;
errors.push({
id: node.id,
expectedTid: node.triggerAsyncId,
actualTid: uidtoid[x.triggerAsyncId],
});
}
if (errors.length) {
errors.forEach((x) =>
console.error(
`'${x.id}' expected to be triggered by '${x.expectedTid}', ` +
`but was triggered by '${x.actualTid}' instead.`,
),
);
}
assert.strictEqual(errors.length, 0);
// Verify that all expected types are present (but more/others are allowed)
const expTypes = { __proto__: null };
for (let i = 0; i < graph.length; i++) {
expTypes[graph[i].type] ??= 0;
expTypes[graph[i].type]++;
}
for (const type in expTypes) {
assert.ok(typeSeen[type] >= expTypes[type],
`Type '${type}': expecting: ${expTypes[type]} ` +
`found: ${typeSeen[type]}`);
}
};
//
// Helper to generate the input to the verifyGraph tests
//
function inspect(obj, depth) {
console.error(util.inspect(obj, false, depth || 5, true));
}
module.exports.printGraph = function printGraph(hooks) {
const ids = {};
const uidtoid = {};
const activities = pruneTickObjects(hooks.activities);
const graph = [];
activities.forEach(processNode);
function processNode(x) {
const key = x.type.replace(/WRAP/, '').toLowerCase();
ids[key] ||= 1;
const id = `${key}:${ids[key]++}`;
uidtoid[x.uid] = id;
const triggerAsyncId = uidtoid[x.triggerAsyncId] || null;
graph.push({ type: x.type, id, triggerAsyncId });
}
inspect(graph);
};