Domain
异步异常捕获
由于node的回调异步特性,无法通过try catch来捕捉所有的异常:
try {
process.nextTick(function () {
foo.bar();
});
} catch (err) {
//can not catch it
}如果try catch能够捕获所有的异常,这样我们可以在代码出现一些非预期的错误时,能够记录下错误的同时,友好的给调用者返回一个500错误。可惜,try catch无法捕获异步中的异常。所以我们能做的只能是:
app.get('/index', function (req, res) {
// 业务逻辑
});
process.on('uncaughtException', function (err) {
logger.error(err);
});这个时候,虽然我们可以记录下这个错误的日志,且进程也不会异常退出,但是我们是没有办法对发现错误的请求友好返回的,因为异常处理只返回给我们一个冷冰冰的 error, 脱离了上下文,我们只能够让它超时返回。
domain
在node v0.8+版本的时候,发布了一个模块domain。这个模块做的就是try catch所无法做到的:捕捉异步回调中出现的异常。 于是乎,我们上面那个无奈的例子好像有了解决的方案:
domain虽然捕捉到了异常,但是还是由于异常而导致的堆栈丢失会导致内存泄漏,所以出现这种情况的时候还是需要重启这个进程的。
Domain剖析
Domain 自身其实也是 Event 模块一个典型的应用, 它通过事件的方式来传递捕获的错误。
另外, domain 为了支持深层次的嵌套, 提供了 Domain#enter 和 Domain#exit 的 API。 先来看 enter的实现,
设置当前活跃的 domain, 并且为了便于回溯,将当前的 domain 加入到队列的后面,更新栈的深度。
再看 exit实现,
相反的, 退出当前的 domain, 更新长度,设置当前活跃的 domain。
读者可能好奇,我并没有显式地调用 enter, exit, 而只是简单的创建了一个 domain, 怎么会达到这种效果?
读者可以看看 AsyncWrap::MakeCallback(), 每次C++ --> JS, 都会检查 domain, 如果使用,则会显式地调用他们。 其他地方读者可以自行寻找。
为了解决不在当前作用域的异常处理, Domain 也提供 Domain#add 和 Domain#remove 来增加 emitter 或者 Timer。
回到事件的根本, 什么时候触发domain的error事件?
如果当前 process 使用了 domain, 也是就 process.domain 不为空,就调用 _errorHandler 来处理, 当前也存在没有处理的情况,职责链来到 process, process 则触发 uncaughtException 事件。
总结
domain很强大,但它只能捕获在其作用域范围内的异常。对于非预期的异常产生的时候, 我们最好让当前请求超时,然后让这个进程停止服务,之后重新启动。
但始终 Domain 在异常处理上有各种不完美,目前该模块处于即将废除阶段,取代他的可能是另一种机制。
详细讨论见:
https://github.com/nodejs/node/issues/66
参考
http://node.alibaba-inc.com/post/async-error-handle-and-domain.html?spm=0.0.0.0.7r8vQ2
Last updated