为什么异常堆栈中的根条目因上下文而异?

时间:2013-08-22 16:03:09

标签: node.js

我的理解是(unclustered)nodejs程序中的所有代码都在同一个线程中运行。鉴于此,我希望所有这些代码都作为同一个根事件循环的子代运行,因此如果检查了不同回调中运行的代码的堆栈跟踪,我们最终仍会回溯到同一个条目(该事件循环的“调度事件”行。但事实并非如此,我不明白为什么。

请考虑以下事项:

function printStackTrace() {
    console.log(new Error().stack);
}

printStackTrace();
setTimeout(printStackTrace, 1000);

运行产生:

Error
    at printStackTrace (/tmp/node/test.js:4:17)
    at Object.<anonymous> (/tmp/node/test.js:7:1)
    at Module._compile (module.js:446:26)
    at Object..js (module.js:464:10)
    at Module.load (module.js:353:32)
    at Function._load (module.js:311:12)
    at Array.0 (module.js:484:10)
    at EventEmitter._tickCallback (node.js:190:39)
Error
    at Object.printStackTrace [as _onTimeout] (/tmp/node/test.js:4:17)
    at Timer.ontimeout (timers.js:94:19)

只需在REPL中运行console.log(new Error().stack);,即可提供不同的根目录:

Error
    at repl:1:13
    at REPLServer.eval (repl.js:80:21)
    at repl.js:190:20
    at REPLServer.eval (repl.js:87:5)
    at Interface.<anonymous> (repl.js:182:12)
    at Interface.emit (events.js:67:17)
    at Interface._onLine (readline.js:162:10)
    at Interface._line (readline.js:426:8)
    at Interface._ttyWrite (readline.js:603:14)
    at ReadStream.<anonymous> (readline.js:82:12)

因此每个上的根(最下面)项是不同的(分别在EventEmitter,Timer和ReadStream中)。对于其他回调(例如网络)也是如此。

所以我想象一下

  • 事件循环是本机(C ++)代码,因此它不会显示在堆栈跟踪中,异步服务的基本提供程序(repl.js,timers.js等)使用本机注册它自己v8 api call。
  • 事件循环是JavaScript,但是Error()有特殊代码来隐藏它(作为不必要的实现细节)

这些(如果有的话)是这种情况,并且通常在nodejs(edit:或v8)源中我可以读取实际根事件循环的实现吗?

1 个答案:

答案 0 :(得分:1)

答案(或至少是线索)就在堆栈跟踪中。只需按照堆栈底部文件中的代码即可。

我不确定您正在使用的节点版本(0.6?更新时间!),但最新版本(0.10.17),

setTimeout(function() { console.log(new Error().stack) }, 1);

打印出来:

Error
    at null._onTimeout (repl:1:38)
    at Timer.listOnTimeout [as ontimeout] (timers.js:110:15)

让我们转到timers.js:110。此行位于listOnTimeout函数内,该函数已分配给ontimeout实例的Timer属性。

Timer is a C++ module interfaces与libuv;它是invokes ontimeout函数的C ++代码。

所以有你的答案:堆栈的根是由C ++代码调用的JavaScript函数(无论是计时器还是流管道)。

Error提供给您的堆栈跟踪不会显示调用函数所涉及的任何本机代码。事件循环本身是由V8(本机代码)实现的,而不是JavaScript,因此你应该看到除了边界之外没有任何东西。

所以发生的事情非常接近你的第一次猜测。 JavaScript代码通过将某个属性设置为函数(或将函数作为参数传递给本机代码)来注册回调。当C ++想要调用该函数时,它会获取引用并指示V8通过调用v8::Function::Call来调用该函数。

如果您对V8如何工作感兴趣,embedder's guide是一个良好的开端。