Skip to content

Instantly share code, notes, and snippets.

@Mr0grog
Created May 20, 2021 16:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Mr0grog/f6f3d97a234087ebf247b8faf73822b1 to your computer and use it in GitHub Desktop.
Save Mr0grog/f6f3d97a234087ebf247b8faf73822b1 to your computer and use it in GitHub Desktop.
Test out scenarios with Node.js async hooks
/**
* Test how async hooks works in various scenarios.
*
* Call `hook.enable()` to start tracking async resources and `hook.disable()`
* to stop or pause tracking. Then use `logAndResetHooks()` to print a log of
* all the creation and destruction of async hooks.
*/
const asyncHooks = require("async_hooks");
const http = require("http");
const util = require("util");
// TOOLING FOR TESTING ASYNC HOOKS -----------------------------------------
let logs = [];
function log(...args) {
logs.push(`${util.format(...args)}`);
}
class ErrorWithStack extends Error {
constructor(message, callsite) {
// Ensure we have a large stack length so we get full details.
const stackLimit = Error.stackTraceLimit;
Error.stackTraceLimit = Math.max(100, stackLimit || 10);
super(message);
if (Error.captureStackTrace) {
Error.captureStackTrace(this, callsite);
}
Error.stackTraceLimit = stackLimit;
}
}
const noStack = new Set(["TickObject", "Immediate"])
const handles = new Map();
const hook = asyncHooks.createHook({
init: function initHook (asyncId, type, triggerAsyncId, resource) {
const error = new ErrorWithStack(type, initHook);
log(`init ( asyncId: ${asyncId}, type: "${type}", triggerAsyncId: ${triggerAsyncId} )`);
if (!noStack.has(type)) log(" Stack:", error.stack);
handles.set(asyncId, { asyncId, type, triggerAsyncId });
},
destroy(asyncId) {
log(`destroy ( asyncId: ${asyncId} )`);
handles.delete(asyncId);
},
});
function logAndResetHooks() {
console.log(logs.join("\n"));
handles.clear();
logs = [];
}
// ACTUAL TEST SCENARIOS --------------------------------------------------
const respondWithOk = (_, response) => response.end("ok");
// 1. `server.listen` with just a port creates TCPSERVERWRAP directly.
function userCode1 (done) {
hook.enable();
const server = http.createServer(respondWithOk);
server.listen(0, () => {
// Wait until after the server.listen callback is completed to stop hooks.
setImmediate(() => {
// Disable before closing the server to simulate a server left running
hook.disable();
server.close();
logAndResetHooks();
if (done) done();
});
});
}
// 2. `server.listen` with a host creates GETADDRINFOREQWRAP directly and
// TCPSERVERWRAP indirectly.
function userCode2 (done) {
hook.enable();
const server = http.createServer(respondWithOk);
server.listen({host: "localhost", port: 0}, () => {
// Wait until after the server.listen callback is completed to stop hooks.
setImmediate(() => {
// Disable before closing the server to simulate a server left running
hook.disable();
server.close();
logAndResetHooks();
if (done) done();
});
});
}
// Run these!
console.log("TEST 1: `server.listen` with just a port");
console.log("----------------------------------------")
userCode1(() => {
console.log("\n\n\n");
console.log("TEST 2: `server.listen` with a host option");
console.log("------------------------------------------")
userCode2();
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment