Introduction to AsyncWrap

You

  • Published npm module
  • Looked at io.js
  • Read nodecore code

Me

Andreas Madsen

Once nodecore hacker

Cluster, Child Process, Stream v2

Now mathmagician

AI, Machine Learning, GPU programming
Previous Talk: Writing an asynchronous traceing tool
npm install trace
io.js tracing-wg (working group) member

Tracing-wg

Improve the profiling and debugging experience
https://github.com/iojs/tracing-wg

What do you need ?

AsyncWrap

  • Undocumented API
  • No interface like you are used too

The basics


// Right now async_wrap is an internal C++ module,
// thus require doesn't work.

var asyncWrap = process.binding('async_wrap');
						

// Think of `CallInitHook` as the property name
// and `kCallInitHook` as the way to access it
var kCallInitHook = 0;
var asyncHooksObject = {};

// Enable async wrap
asyncWrap.setupHooks(asyncHooksObject,
    asyncInit, asyncBefore, asyncAfter);

// Enable async_wrap events (default 0)
// Must be set after `async_wrap.setupHooks` is called
asyncHooksObject[kCallInitHook] = 1;

function asyncInit() {
  log('async_wrap: init');
}
function asyncBefore() {
  log('async_wrap: before');
}
function asyncAfter() {
  log('async_wrap: after');
}

log('user: before');
fs.open(__filename, 'r', function (err, fd) {
  log('user: done');
});
log('user: after');
						
$ node the_basic.js|
user: before
async_wrap: init
user: after
async_wrap: before
user: done
async_wrap: after
						

Debugging


function asyncInit() {
  console.log('async_wrap: init');
}
function asyncBefore() {
  console.log('async_wrap: before');
}
function asyncAfter() {
  console.log('async_wrap: after');
}

console.log('user: before');
fs.open(__filename, 'r', function (err, fd) {
  console.log('user: done');
});
console.log('user: after');
						
$ node evil_recursion.js|
user: before
FATAL ERROR:
  node::AsyncWrap::AsyncWrap
  init hook threw
						
console.log is async, thus it invokes async_wrap.

// Undocumented way
process._rawDebug(data, ...)

// Sync write to stdout (1), or stderr (2)
fs.writeSync(1, util.format(data, ...))
						

Timers


log('user: before #1');
setTimeout(function () {
  log('user: done #1');
}, 2000);
log('user: after #1');

log('user: before #2');
setTimeout(function () {
  log('user: done #2');
}, 2000);
log('user: after #2');
						
$ node timer_issue.js|
user: before #1
async_wrap: init
user: after #1
user: before #2
user: after #2

async_wrap: before
user: done #1
user: done #2
async_wrap: after


						

Known issue

  • Timers are joint together. Only one call to C++
  • Solution is to overwrite setTimeout
  • Also setInterval, setImmediate and nextTick

Error catching


function asyncBefore() {
  log('async_wrap: before');
}
function asyncAfter() {
  log('async_wrap: after');
}

fs.open(__filename, 'r', function () {
  throw new Error('ups');
});
						
$ node leaky_error.js|
async_wrap: init
async_wrap: before
/demos/leaky_error.js:33
  throw new Error('ups');
        ^
Error: ups
  at /demos/leaky_error.js:33:9
  at FSReqWrap.oncomplete (fs.js:73:15)
						

function asyncBefore() {
  process.on(
    'uncaughtException', asyncError);
}
function asyncAfter() {
  process.removeListener(
    'uncaughtException', asyncError);
}
function asyncError(err) {
  log('(catch error)', err.stack);
}

fs.open(__filename, 'r', function () {
  throw new Error('ups');
});
						
$ node catch_error.js|
(catch error) Error: ups
  at /demos/catch_error.js:38:9
  at FSReqWrap.oncomplete (fs.js:73:15)
						

Known issue

  • By catching, the throw origin is removed
  • Mutate the error, but let it bubble up
  • For now, use: process._fatalException
  • Even more undocumented and private API
    than async_wrap!

var catchError = false;
function asyncBefore() {
  catchError = true;
}
function asyncAfter() {
  catchError = false;
}
var old = process._fatalException;
process._fatalException = function (e){
  if (catchError)
    e.stack = 'Mutate' + e.stack;
  old.apply(process, arguments);
};

fs.open(__filename, 'r', function () {
  throw new Error('ups');
});
						
$ node catch_error.js|
/demos/mutate_error.js:41
  throw new Error('ups');
        ^
MutateError: ups
  at /demos/mutate_error.js:41:9
  at FSReqWrap.oncomplete (fs.js:73:15)

Handle object


function asyncInit() {
  process._rawDebug(show(this));
}

function show (handle) {
  // compilcated
}

net.connect(80, 'google.com',
  function (socket) { });
						
$ node catch_error.js|
/demos/handle_explore.js:41
[TCP] {
  fd: [don't touch me]
  onread: [event]
  ...
}
[GetAddrInfoReqWrap] {
  family: 0
  hostname: google.com
  ...
}
[TCPConnectWrap] {
  address: 80.239.174.116
  port: 80
  ...
}

Handle objects

  • Short story: what is send to C++ land
  • Some properties causes segmentaion failure
  • this.constructor.name is information too
  • Can carry user properties
  • Safe property: this._asyncQueue = {}

function asyncInit() {
  this._start = Date.now();
}
function asyncBefore() {
  process._rawDebug(
    this.constructor.name +
    ': took ' +
    (Date.now() - this._start) + ' ms'
  );
}

net.connect(80, 'google.com',
  function () { });
						
$ node handle_mutate.js|
GetAddrInfoReqWrap: took 2 ms
TCPConnectWrap: took 4 ms
						

Links

Demos, slides, blink tag

https://github.com/AndreasMadsen/talk-async-wrap

npm install trace

https://github.com/AndreasMadsen/trace/

Trevnorris blog post

http://blog.trevnorris.com/2015/02/asyncwrap-tutorial-introduction.html