AsyncHooks API: O recurso que você precisa e não sabia!

André Werlang

@awerlang

The Developer's Conference 2017 - Porto Alegre

Definition

The async_hooks module provides an API to register callbacks tracking the lifetime of asynchronous resources created inside a Node.js application

The Problem

  • Node.js has a single thread on userland.

Incoming Request

Response

Concurrent Requests

Use Cases

  • Monitoring & timing
  • Diagnostics & tracing
  • Long stack traces
  • Continuation local storage

Stack traces


Stack trace: ProvisionedThroughputExceededException: The level of configured
provisioned throughput for the table was exceeded. Consider increasing your
provisioning level with the UpdateTable API.
 at Request.extractError (node_modules/aws-sdk/lib/protocol/json.js:43:27)
 at Request.callListeners (node_modules/aws-sdk/lib/sequential_executor.js:105:20)
 at Request.emit (node_modules/aws-sdk/lib/request.js:668:14)
 at Request.transition (node_modules/aws-sdk/lib/request.js:22:10)
 at AcceptorStateMachine.runTo (node_modules/aws-sdk/lib/state_machine.js:14:12)
 at node_modules/aws-sdk/lib/state_machine.js:26:10
 at Request.anonymous (node_modules/aws-sdk/lib/request.js:670:12)
 at Request.callListeners (node_modules/aws-sdk/lib/sequential_executor.js:115:18)
 at Request.emit (node_modules/aws-sdk/lib/request.js:668:14)
 at Request.transition (node_modules/aws-sdk/lib/request.js:22:10)
 at AcceptorStateMachine.runTo (node_modules/aws-sdk/lib/state_machine.js:14:12)
 at node_modules/aws-sdk/lib/state_machine.js:26:10
                

Steps to enlightment

  • Step 1: understand code flow
  • Step 2: understand data

Attempt #1: global data


let currentRequest;

new http.Server(async (req, res) => {
    currentRequest = req         // <---- save for later

    const delay = +req.url.substr(1)
    await timeout(delay)
    respond(res)
}).listen(8080)

function respond(res) {
    const req = currentRequest   // <---- retrieve

    res.write('Response for ' + req.url)
    res.end()
}

GET /8000 < Response for 3000

GET /3000 < Response for 3000

Monkeypatching...

Async Hooks

  • From Node.js Diagnostics Working Group
  • Evolution of prior efforts since 2013
  • Generic solution to capture any asynchronous activity
  • Low level api is flexible

Resources

  • What is a resource?
  • What asynchronous resources are there?

API


const asyncHooks = require('async_hooks')

function init(id, type, triggerAsyncId, resource) {
}

function before(id) {
}

function after(id) {
}

function destroy(id) {
}

const hook = asyncHooks.createHook({ init, before, after, destroy })
hook.enable()
                

AsyncHook solution


requests.track()                     // <---- enable tracking

new http.Server(async (req, res) => {
    requests.setRequest(req);        // <---- save for later
    const delay = +req.url.substr(1)
    await timeout(delay)
    respond(res)
}).listen(8080)

function respond(res) {
    const req = requests.current()    // <---- retrieve
    res.write('Response for ' + req.url)
    res.end()
}
                

AsyncHook solution


const initState = new Map(), prevState = new Map()

function init(id, type, triggerAsyncId, resource) {
    initState.set(id, currentRequest)
}
function before(id) {
    prevState.set(id, currentRequest)
    currentRequest = initState.get(id)
}
function after(id) {
    currentRequest = prevState.get(id)
}
function destroy(id) {
    prevState.delete(id)
    initState.delete(id)
}
                

Prior to Node.js 8

  • async-listener
  • continuation-local-storage

References

  • https://nodejs.org/api/async_hooks.html
  • https://www.youtube.com/watch?v=twhQAUd6Z58
  • https://www.youtube.com/watch?v=UUzGT-g0X70
  • https://github.com/nodejs/diagnostics/issues/29
  • https://github.com/nodejs/diagnostics/blob/master/tracing/AsyncWrap/README.md
  • https://github.com/nodejs/node-eps/blob/master/006-asynchooks-api.md

THANKS

@awerlang