The API is undergoing major review and revision, but the techniques I will demonstrate today will still be relevant.
The API changes are "breaking" but not fundamentally different on a conceptual level.
function foo () {
bar();
}
function bar () {
foobar();
}
function foobar () {
throw new Error('Surprise!');
}
foo();
process.on('uncaughtException', (e) => process._rawDebug(e.stack));
tracing-async-operations/ex1.js:10
throw new Error('Surprise!');
^
Error: Surprise!
at foobar (/src/tracing-async-operations/ex1.js:10:9)
at bar (/src/tracing-async-operations/ex1.js:6:3)
at foo (/src/tracing-async-operations/ex1.js:2:3)
at Object.<anonymous> (/src/tracing-async-operations/ex1.js:13:1)
at Module._compile (module.js:570:32)
at Object.Module._extensions..js (module.js:579:10)
at Module.load (module.js:487:32)
at tryModuleLoad (module.js:446:12)
at Function.Module._load (module.js:438:3)
at Module.runMain (module.js:604:10)
function foo () {
setTimeout(bar, 10);
}
function bar () {
setTimeout(foobar, 50);
}
function foobar () {
const fs = require('fs');
fs.readFile(__filename, (_)=>{ throw new Error('Surprise!'); });
}
foo();
process.on('uncaughtException', (e) => process._rawDebug(e.stack));
Error: Surprise!
at require.readFile (/src/tracing-async-operations/ex2.js:10:56)
at FSReqWrap.readFileAfterClose [as oncomplete] (fs.js:445:3)
Error: Surprise!
at require.readFile (/src/tracing-async-operations/ex2.js:10:56)
at FSReqWrap.readFileAfterClose [as oncomplete] (fs.js:445:3)
There is no mention of foo(), bar(), etc.
All we have is that after the close event there was an Error.
const asyncWrap = process.binding('async_wrap');
asyncWrap.setupHooks({ init, pre, post, destroy });
asyncWrap.enable();
function init (uid, provider, parentUid, parentHandle) {}
function pre (uid) {}
function post (uid, didThrow) {}
function destroy (uid) {}
function init (uid, provider, parentUid, parentHandle) {
// 'this' is the async Handle object
process._rawDebug(`init: ${uid} : ${provider}`);
}
init()
called when an asynchronous operation is placed on the event queue
> const asyncWrap = process.binding('async_wrap');
> asyncWrap.Providers
{ NONE: 0, CRYPTO: 1,
FSEVENTWRAP: 2, FSREQWRAP: 3,
GETADDRINFOREQWRAP: 4, GETNAMEINFOREQWRAP: 5,
HTTPPARSER: 6, JSSTREAM: 7,
PIPEWRAP: 8, PIPECONNECTWRAP: 9,
PROCESSWRAP: 10, QUERYWRAP: 11,
SHUTDOWNWRAP: 12, SIGNALWRAP: 13,
STATWATCHER: 14, TCPWRAP: 15,
TCPCONNECTWRAP: 16, TIMERWRAP: 17,
TLSWRAP: 18, TTYWRAP: 19,
UDPWRAP: 20, UDPSENDWRAP: 21,
WRITEWRAP: 22, ZLIB: 23 }
function pre (uid) {
// 'this' is the async Handle object
process._rawDebug(`pre: ${uid}`);
}
pre()
called just before an asynchronous operation is executed
function init (uid, provider, parentUid, parentHandle) {
this.someValue = 'foo';
}
function pre (uid) {
process._rawDebug(this.someValue); // > 'foo'
}
function post (uid, didThrow) {
// 'this' is the async Handle object
process._rawDebug(`post: ${uid}`);
}
post()
called just after an asynchronous operation is executed
function destroy (uid) {
process._rawDebug(`destroy: ${uid}`);
}
destroy()
called when the C++ handle object is destroyed
function init (uid, provider, parentUid, parentHandle) {
process._rawDebug(`init: ${uid} : ${provider}`);
}
function pre (uid) {
process._rawDebug(`pre: ${uid}`);
}
function post (uid, didThrow) {
process._rawDebug(`post: ${uid}`);
}
function destroy (uid) {
process._rawDebug(`destroy: ${uid}`);
}
asyncWrap.setupHooks({ init, pre, post, destroy });
asyncWrap.enable();
process._rawDebug('user: before');
foo();
process._rawDebug('user: after');
function foo () {
setTimeout(foobar, 10);
}
function foobar () {
process._rawDebug('user: during');
}
$ node ex3.js
user: before
init: 1 : 17
user: after
pre: 1
user: during
post: 1
destroy: 1
"If you can't observe your software, you have no idea what it's doing." - C J Silverio
Use the AsyncWrap API to keep track of where our operations are within the call stack.
When an async operation is placed into the event queue - i.e. on init(), store the current call stack.
If things blow up, we can get some insight into what went wrong.
function foo () {
setTimeout(foobar, 50);
}
function foobar () {
throw new Error('Surprise!');
}
foo();
/src/tracing-async-operations/ex5.js:6
throw new Error('Surprise!');
^
Error: Surprise!
at Timeout.foobar [as _onTimeout] (/Users/lanceball/presentations/tracing-async-operations/ex4.js:34:9)
at ontimeout (timers.js:365:14)
at tryOnTimeout (timers.js:237:5)
at Timer.listOnTimeout (timers.js:207:5)
(Again note, context is lost)
const stacks = new WeakMap();
let callSite = null;
function init (uid, provider, parentUid, parentHandle) {
stacks.set(this, getCallSites(2)); // getCallSites returns call stack
}
function pre (uid) {
// async stuff about to go down. restore the previous call stack
callSite = stacks.get(this);
}
function post (uid, didThrow) {
callSite = null;
}
asyncWrap.setupHooks({ init, pre, post });
asyncWrap.enable();
Error.prepareStackTrace = function prepareStackTrace (err, stack) {
if (currentCallSite) stack.push.apply(stack, currentCallSite);
return formatter(err, stack);
};
function foo () {
setTimeout(foobar, 50);
}
function foobar () {
throw new Error('Surprise!');
}
foo();
Error: Surprise!
at Timeout.foobar [as _onTimeout] (/src/tracing-async-operations/ex4.js:30:9)
at foo (/src/tracing-async-operations/ex4.js:26:3)
at Object.<anonymous> (/src/tracing-async-operations/ex4.js:55:1)
lightbright - https://github.com/bucharest-gold/lightbright
async-hooks - https://github.com/AndreasMadsen/async-hook
clarify - https://github.com/AndreasMadsen/clarify
https://redhat.slides.com/lball/tracing-asynchronous-operations
AsyncWrap Public API Proposal - https://github.com/nodejs/node-eps/pull/18
async_hooks initial implementation - https://github.com/nodejs/node/pull/8531