サーバーとの定期的な疎通確認を setInterval
で実装してたら、いつの間にか処理が停止してたという話。
setIntervalの中でエラーが飛ぶとタイマーが停止する。 ΩΩΩ
process.on('uncaughtException', function (err) {
console.error(err.stack);
});
setInterval(function () {
JSON.parse('{}}}'); // throw
}, 1000);
setInterval(function () {
console.log('xxx');
}, 1000);
実行結果。1秒ごとにエラーが発生すると思って書いたつもりだったけど発生しない。
$ node a.js
SyntaxError: Unexpected token }
at Object.parse (native)
at null.<anonymous> (/home/ajido/a.js:6:10)
at wrapper [as _onTimeout] (timers.js:252:14)
at Timer.listOnTimeout [as ontimeout] (timers.js:110:15)
xxx
xxx
xxx
xxx
xxx
xxx
xxx
スコープ内で try..catch
するとタイマーは停止しないので、 uncaughtException
でスコープ外に飛んだのが原因かと考え domain
に変更してみるも結果は同じ。
var d = require('domain').create();
d.on('error', function (err) {
console.error(err.stack);
});
d.run(function () {
setInterval(function () {
throw new Error('hoge');
}, 1000);
setInterval(function () {
console.log('xxx');
}, 1000);
});
実行結果
Error: hoge
at /home/ajido/a.js:10:19
at b (domain.js:183:18)
at Domain.run (domain.js:123:23)
at null.<anonymous> (/home/ajido/a.js:9:11)
at wrapper [as _onTimeout] (timers.js:252:14)
at Timer.listOnTimeout [as ontimeout] (timers.js:110:15)
xxx
xxx
xxx
xxx
これならどうだと思ったけどこれも失敗。
ar d = require('domain').create();
d.on('error', function (err) {
console.error(err.stack);
});
d.run(function () {
setInterval(function () {
d.run(function () {
throw new Error('hoge');
});
}, 1000);
setInterval(function () {
d.run(function () {
console.log('xxx');
});
}, 1000);
});
setInterval(function () {
var d = require('domain').create();
d.on('error', function (err) {
console.error(err.stack);
});
d.run(function () {
throw new Error('hoge');
});
}, 1000);
setInterval(function () {
var d = require('domain').create();
d.on('error', function (err) {
console.error(err.stack);
});
d.run(function () {
console.log('xxx');
});
}, 1000);
実行結果
Error: hoge
at /home/ajido/a.js:9:15
at b (domain.js:183:18)
at Domain.run (domain.js:123:23)
at null.<anonymous> (/home/ajido/a.js:8:7)
at wrapper [as _onTimeout] (timers.js:252:14)
at Timer.listOnTimeout [as ontimeout] (timers.js:110:15)
xxx
xxx
xxx
xxx
setTimeoutに変更して対処
try..catch
ではすべてのエラーを担保できないので setTimeout
の再起に変更して対処。ただエラー内容が at Timer.listOnTimeout (timers.js:110:15)
になってうーんという感じ。
process.on('uncaughtException', function (err) {
console.error(err.stack);
});
function func() {
setTimeout(func, 1000);
console.log('xxx');
throw new Error();
}
func();
ちなみにブラウザだとタイマーは停止しない。
> setInterval(function (){ console.log ('xxx'); throw new Error(); }, 1000);
< 18
< xxx
< Uncaught Error
< xxx
< Uncaught Error
< xxx
< Uncaught Error
非同期処理の内部で発生する可能性のあるすべてのエラーをしっかりハンドリングしていれば、 setInterval
内で発生するエラーもフックできるので問題ないと思いますが、今回は定期的に行う疎通確認部分で外部のモジュールを使っていて、その中のソケット通信実装部分でエラーハンドリングに漏れがあったので上位の uncaughtException/domain
まで飛んでいました。ちなみに ECONNREFUSED
です。
ただこういうエラーハンドリングの漏れはよくあることだと思うので、気楽に uncaughtException/domain
で引っかかったらそのときに修正すればいいやくらいの気分でいたんですが、まさかタイマーが停止するとは思ってなかった…
これ以上は追ってないのでどこかで関連する話がされていれば是非教えてくださいー