Skip to content

"Maximum call stack size exceeded" causes a crash in "async_hooks" module #37989

Description

@zyscoder

What steps will reproduce the bug?

Setup a node instance,

» node

and run the following javascript code.

async_hooks.createHook({after:new async_hooks.AsyncResource('str').bind(()=>{})}).enable();

Then the node instance crashes.

How often does it reproduce? Is there a required condition?

This abort can always be triggered following the steps above.

What is the expected behavior?

If any error occurs, an exception or other similar error-reporting stuff should be thrown. There is no reason to abort the whole node process.

What do you see instead?

» node
Welcome to Node.js v14.15.1.
Type ".help" for more information.
> async_hooks.createHook({after:new async_hooks.AsyncResource('str').bind(()=>{})}).enable();
AsyncHook {
  [Symbol(init)]: undefined,
  [Symbol(before)]: undefined,
  [Symbol(after)]: [Function: bound runInAsyncScope] {
    asyncResource: AsyncResource {
      [Symbol(async_id_symbol)]: 26,
      [Symbol(trigger_async_id_symbol)]: 5,
      [Symbol(destroyed)]: [Object]
    }
  },
  [Symbol(destroy)]: undefined,
  [Symbol(promiseResolve)]: undefined
}
> RangeError: Maximum call stack size exceeded
    at process._rawDebug (internal/process/per_thread.js:53:30)
    at fatalError (internal/async_hooks.js:159:13)
    at emitHook (internal/async_hooks.js:234:5)
    at emitAfterScript (internal/async_hooks.js:480:5)
    at AsyncResource.runInAsyncScope (async_hooks.js:198:9)
    at emitHook (internal/async_hooks.js:230:38)
    at emitAfterScript (internal/async_hooks.js:480:5)
    at AsyncResource.runInAsyncScope (async_hooks.js:198:9)
    at emitHook (internal/async_hooks.js:230:38)
    at emitAfterScript (internal/async_hooks.js:480:5)                                                                                                                                                                                                                                           

Additional information

Activity

  1. zyscoder commented on Mar 31, 2021

    @zyscoder
    Author

    The following js code can also trigger "Maximum call stack size exceeded":

     `async_hooks.createHook({before:new async_hooks.AsyncResource('str').bind(()=>{})}).enable();`
    
    » node
    Welcome to Node.js v14.15.1.
    Type ".help" for more information.
    > async_hooks.createHook({before:new async_hooks.AsyncResource('str').bind(()=>{})}).enable();
    AsyncHook {
      [Symbol(init)]: undefined,
      [Symbol(before)]: [Function: bound runInAsyncScope] {
        asyncResource: AsyncResource {
          [Symbol(async_id_symbol)]: 26,
          [Symbol(trigger_async_id_symbol)]: 5,
          [Symbol(destroyed)]: [Object]
        }
      },
      [Symbol(after)]: undefined,
      [Symbol(destroy)]: undefined,
      [Symbol(promiseResolve)]: undefined
    }
    > RangeError: Maximum call stack size exceeded
        at emitHook (internal/async_hooks.js:234:5)
        at emitBeforeScript (internal/async_hooks.js:474:5)
        at AsyncResource.runInAsyncScope (async_hooks.js:188:5)
        at emitHook (internal/async_hooks.js:230:38)
        at emitBeforeScript (internal/async_hooks.js:474:5)
        at AsyncResource.runInAsyncScope (async_hooks.js:188:5)
        at emitHook (internal/async_hooks.js:230:38)
        at emitBeforeScript (internal/async_hooks.js:474:5)
        at AsyncResource.runInAsyncScope (async_hooks.js:188:5)
        at emitHook (internal/async_hooks.js:230:38)
    
  2. Flarna commented on Mar 31, 2021

    @Flarna
    Member

    If any error occurs, an exception or other similar error-reporting stuff should be thrown.

    Well, there is a RangeError thrown. it's not catched in your code.
    But if you put your before/after handler within a try/catch a real crash happens which is described in #36079

  3. added
    async_hooksIssues and PRs related to the async hooks subsystem.
    on Mar 31, 2021
  4. zyscoder commented on Mar 31, 2021

    @zyscoder
    Author

    Well, there is a RangeError thrown. it's not catched in your code.

    I'm not sure what you mean but the node instance still exits (abnormally) although there has been a RangeError thrown. I don't know if this is a normal behavior.

  5. Flarna commented on Mar 31, 2021

    @Flarna
    Member

    If any AsyncHook callbacks throw, the application will print the stack trace and exit see https://nodejs.org/dist/latest-v14.x/docs/api/async_hooks.html#async_hooks_error_handling

  6. PhakornKiong commented on Mar 31, 2021

    @PhakornKiong
    Contributor

    @zyscoder

    Let me try to chip in on this,

    You can try to run this in Nodejs REPL and see

    let codeA = `function debug2(...args){fs.writeFileSync(1, \`\${util.format(...args)}\n\`, { flag: 'a' });}async_hooks.createHook({init(asyncId, type, triggerAsyncId, resource) {debug2('init:  ',resource);},after(asyncId) {debug2(asyncId);new async_hooks.AsyncResource('str').bind(() => {});},}).enable();`;
    
    eval(codeA)

    Which is actually just a very simple function

    function debug2(...args) {
      fs.writeFileSync(1, `${util.format(...args)}\n`, { flag: 'a' });
    }
    async_hooks
      .createHook({
        init(asyncId, type, triggerAsyncId, resource) {
          debug2('init:  ', resource);
        },
        after(asyncId) {
          debug2(asyncId);
          new async_hooks.AsyncResource('str').bind(() => {});
        },
      })
      .enable();

    This would produce (a snippet of it since it keeps repeating)

     init:   <ref *1> Timeout {
      _idleTimeout: 15,
      _idlePrev: [Circular *1],
      _idleNext: [Circular *1],
      _idleStart: null,
      _onTimeout: [Function: flushHistory],
      _timerArgs: undefined,
      _repeat: null,
      _destroyed: false,
      [Symbol(refed)]: true,
      [Symbol(kHasPrimitive)]: false,
      [Symbol(asyncId)]: 73,
      [Symbol(triggerId)]: 5
    }
    init:   {
      callback: [Function: maybeReadMore_],
      args: [
        ReadStream {
          connecting: false,
          _hadError: false,
          _parent: null,
          _host: null,
          _readableState: [ReadableState],
          _events: [Object: null prototype],
          _eventsCount: 4,
          _maxListeners: undefined,
          _writableState: [WritableState],
          allowHalfOpen: false,
          _sockname: null,
          _pendingData: null,
          _pendingEncoding: '',
          server: null,
          _server: null,
    init:   {aw: true,
      callback: [Function: afterWriteTick],
      args: [ 0,
        { [Symbol(async_id_symbol)]: 5,
          count: 1,Handle)]: [TTY],
          cb: [Function: nop],]: false,
          stream: [WriteStream],Size)]: 0,
          state: [WritableState]l,
        } [Symbol(kBuffer)]: null,

    I presumed the REPL is ran with some async stuff like setTimout, process.nextTick() that is causing StackOverflow.

    Any reason why you would want to do this in REPL?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    async_hooksIssues and PRs related to the async hooks subsystem.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions