Skip to content

Async_hooks use case #46278

Open
Open
@orinatic

Description

@orinatic

Affected URL(s)

https://nodejs.org/api/async_hooks.html#async-hooks

Description of the problem

I need to track state for async contexts that I do not control.

Specifically, I am tracking the 'stack' through setTimeout.

I have overridden setTimeout to be (simplified):

const handler = {
    apply(target: Function, self: unknown, args: unknown[]) {
      const [func, ...rest] = args

      const st = getStackTrace()
      var wrapper = function(...args: unknown[]) {
        getCallStack().unshift(st)
        func.apply(this, ...args)
        getCallStack().pop()
      }

      return Reflect.apply(target, self, [wrapper, ...rest])
    }
  }
setTimeout = new Proxy(setTimeout, handler)

and getCallStack is (simplified)

import { createHook, executionAsyncId } from "async_hooks"
const callStacks: Map<number, string[]> = new Map()

function init() {
  const id = executionAsyncId()
  callStacks.set(0, [])

  function onThreadInit(id: number, {}, triggerId: number, {}) {
    const parentStack = callStacks.get(triggerId)
    if(!parentStack) {
      callStacks.set(id, [])
    } else {
      callStacks.set(id, [...parentStack])
    }
  }

  const hook = createHook({init: onThreadInit)
  hook.enable()
}
init()

export function getCallStack() {
  const id = executionAsyncId()
  return callStacks.get(id)
}

and getStackTrace uses Error to get a stacktrace.

Within several calls deep within the setTimeout-d function, further instrumentation can access getCallStack() to trace where the function was called from.

I do something similar with EventEmitters -- I want to know where an event was registered

The use-case for this is an instrumentation library -- I do not control any of the code other than the specific functions that I overwrite. Thus AsyncLocalStorage, which (as far as I can tell?) requires modifying the async calls, doesn't work for this use-case. Also, threads need to be able to access/get a copy of their parent's storage, which doesn't seem possible with AsyncLocalStorage either?

It is entirely possible I am missing something obvious, but so far async_hooks seems like the only way to do this.

Metadata

Metadata

Assignees

No one assigned

    Labels

    async_hooksIssues and PRs related to the async hooks subsystem.docIssues and PRs related to the documentations.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions