【问题标题】:Async Stack Traces in Node.js 14.15.0Node.js 14.15.0 中的异步堆栈跟踪
【发布时间】:2021-02-25 22:59:56
【问题描述】:

根据文档,我假设 Node.js 14 现在确实支持异步代码中的堆栈跟踪,但不幸的是,使用 node --async-stack-traces test.js 仍然只生成部分堆栈跟踪,我最有兴趣了解原因。

运行以下代码:

main()

function main() {
    sub();
}

async function sub() {
    console.trace('before sleep');
    await delay(1000);
    console.trace('after sleep');
}

function delay(ms) {
    return new Promise(resolve => {
        setTimeout(() => resolve(), ms);
    });
}

显示使用await之前的完整堆栈但仅显示部分堆栈:

~/MyDev/doberkofler/test $ node test.js
Trace: before sleep
    at sub (/Users/doberkofler/MyDev/doberkofler/test/test.js:10:10)
    at main (/Users/doberkofler/MyDev/doberkofler/test/test.js:6:2)
    at Object.<anonymous> (/Users/doberkofler/MyDev/doberkofler/test/test.js:3:1)
    at Module._compile (internal/modules/cjs/loader.js:1063:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10)
    at Module.load (internal/modules/cjs/loader.js:928:32)
    at Function.Module._load (internal/modules/cjs/loader.js:769:14)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:72:12)
    at internal/main/run_main_module.js:17:47
Trace: after sleep
    at sub (/Users/doberkofler/MyDev/doberkofler/test/test.js:12:10)
~/MyDev/doberkofler/test $

【问题讨论】:

  • 如果我不得不猜测,我会说它会输出你上次调用console.trace的跟踪,值得测试一下
  • 如果你删除第一个console.trace,第二个仍然没有堆栈。

标签: node.js async-await callstack


【解决方案1】:

异步堆栈跟踪将起作用,但仅适用于完整的异步方法链。因此,要修复您的示例,请使您的 main 方法异步,然后等待对 sub 的调用。

如果你使用了 promise,或者在 return 之前错过了 await,那么它就会中断。

这里有解释:https://github.com/nodejs/node/issues/36126#issuecomment-827023528

这不是一个错误,它只是 v8 实现的 zero-cost async stack traces mechanism 的一个限制。

具体来说,我们看一下原来的例子:

main()

function main() {
    sub();
}

async function sub() {
    console.trace('before sleep');
    await delay(1000);
    console.trace('after sleep');
}

function delay(ms) {
    return new Promise(resolve => {
        setTimeout(() => resolve(), ms);
    });
}

main 是一个常规同步函数。它调用sub()main() 的调用堆栈范围内触发console.trace('before sleep') 同步。这就是before sleep 包含完整堆栈跟踪信息的原因。接下来发生的是main() 的调用堆栈在微任务队列稍后触发console.trace('after sleep') 之前展开。到第二个console.trace() 被调用时,main() 调用栈早已不复存在,并且关于它的信息不再存在,v8 无法对其进行任何处理。虽然sub() 看起来它是一个单独的调用堆栈,但实际上它是随着时间的推移分散开来的。

为了让after sleep 获得完整的堆栈跟踪,sub() 的调用者必须是异步函数,而对sub() 的调用使用等待。例如:

main()

async function main() {
    await sub();
}

async function sub() {
    console.trace('before sleep');
    await delay(1000);
    console.trace('after sleep');
}

function delay(ms) {
    return new Promise(resolve => {
        setTimeout(() => resolve(), ms);
    });
}

这会如何改变事情?好吧,使main() 异步并使其具有await sub() 意味着v8 必须保留main() 的调用堆栈信息,直到sub() 完成——也就是说,main() 的完整调用堆栈信息仍将驻留在内存中,并且v8 可以轻松使用。当第二次调用console.trace() 发生时(它创建了用于生成打印堆栈的内部Error 对象,所有调用堆栈详细信息都将很容易获得,并且正确的错误堆栈 使用await 回到最顶层的异步函数生成:

Trace: before sleep
    at sub (/root/node/tmp/f.js:8:13)
    at main (/root/node/tmp/f.js:4:10)
    at Object.<anonymous> (/root/node/tmp/f.js:1:1)
    at Module._compile (node:internal/modules/cjs/loader:1108:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1137:10)
    at Module.load (node:internal/modules/cjs/loader:988:32)
    at Function.Module._load (node:internal/modules/cjs/loader:828:14)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:76:12)
    at node:internal/main/run_main_module:17:47
Trace: after sleep
    at sub (/root/node/tmp/f.js:10:13)
    at async main (/root/node/tmp/f.js:4:4)

底线是它绝对不是错误。异步堆栈跟踪机制的工作方式与设计工作完全一样——它只是限制了它可以捕获的信息。现在,是否可以改进是次要讨论,但 Node.js 中没有错误,因为我们完全依赖 v8 提供给我们的内容。

【讨论】:

    【解决方案2】:

    这个问题可能是 v8 中的一个错误:https://github.com/nodejs/node/issues/36126

    【讨论】:

      猜你喜欢
      • 2018-02-21
      • 2022-12-10
      • 1970-01-01
      • 2011-01-05
      • 2017-08-06
      • 2010-09-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多