Skip to content

init() stack capture is misleading (captures async_hooks internals, not blocker call site) #39

@engkareeem

Description

@engkareeem

In index.js, init() currently does:

const e = {}
Error.captureStackTrace(e)
const cached = { asyncId, type, stack: e.stack }

Because this runs inside the async_hooks init callback, the captured stack is dominated by the async_hooks machinery (and init() itself). In practice this is not a reliable "who blocked the process" call site; it’s at best "where the async resource was initialized" and often looks like it’s pointing at the hook rather than userland.

I’d like to propose adding a separate API to manually set the "blame" context on demand, instead of always relying on the async_hook init callback.

The idea is:

  • Expose a function (e.g. setBlockedContext() or similar) that users can call at the point in their code where they expect blocking might happen.
  • When this function is called, it captures the stack at that moment and stores it.
  • Later, if the event loop is detected as blocked, the stored stack is used, so the reported stack trace reflects the userland call site where setBlockedContext() was called, not the internals of the async_hook.

This way, the tool can still use async_hooks to detect blocking, but the visible stack points to the relevant application code (the call site that opted into tracking), rather than to the async_hooks implementation itself.

Example of the current stack

    at AsyncHook.init (/node_modules/blocked-at/index.js:31:11)
    at TLSWrap.emitInitNative (node:internal/async_hooks:205:43)
    at TLSSocket._wrapHandle (node:_tls_wrap:616:24)
    at new TLSSocket (node:_tls_wrap:515:18)
    at Object.connect (node:_tls_wrap:1623:19)
    at Socket.<anonymous> (/node_modules/pg/lib/connection.js:94:27)
    at Object.onceWrapper (node:events:510:26)
    at Socket.emit (node:events:390:28)
    at addChunk (node:internal/streams/readable:315:12)
    at readableAddChunk (node:internal/streams/readable:289:9)
    at Socket.Readable.push (node:internal/streams/readable:228:10)
    at TCP.onStreamRead (node:internal/stream_base_commons:199:23)
    at TCP.callbackTrampoline (node:internal/async_hooks:130:17)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions