Node.js 14.15.0 中的异步堆栈跟踪

dob*_*ler 11 callstack node.js async-await

根据文档,我假设 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);
    });
}
Run Code Online (Sandbox Code Playgroud)

显示使用之前的完整堆栈,但仅显示使用之后的部分堆栈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 $
Run Code Online (Sandbox Code Playgroud)

Dav*_*sel 10

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

如果你使用承诺,或者在返回之前错过等待,那么它就会中断。

此处对此进行了解释:https ://github.com/nodejs/node/issues/36126#issuecomment-827023528

这并不是一个错误,而只是v8 实现的零成本异步堆栈跟踪机制的限制。

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

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);
    });
}
Run Code Online (Sandbox Code Playgroud)

main是一个常规的同步函数。它调用sub()在 的调用堆栈范围内console.trace('before sleep') 同步触发。main()这就是为什么before sleep包含完整的堆栈跟踪信息。接下来发生的事情是,main()在微任务队列稍后触发console.trace('after sleep'). 当第二个console.trace()被调用时,main()callstack 早已消失,有关它的信息不再存在,v8 无法对其执行任何操作。虽然sub() 看起来它是一个单一的调用堆栈,但实际上它是随着时间的推移分散的多个调用堆栈。

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

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);
    });
}
Run Code Online (Sandbox Code Playgroud)

这如何改变事情?好吧,创建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)
Run Code Online (Sandbox Code Playgroud)

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


dob*_*ler 2

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