LoginSignup
31
31

More than 5 years have passed since last update.

setIntervalの中でエラーが飛ぶとタイマーが停止する

Last updated at Posted at 2014-08-01

サーバーとの定期的な疎通確認を 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

これならどうだと思ったけどこれも失敗。

throwをdomainで直接包んでみる
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);
});
domainごとsetInterval内に放り込む
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 で引っかかったらそのときに修正すればいいやくらいの気分でいたんですが、まさかタイマーが停止するとは思ってなかった…

これ以上は追ってないのでどこかで関連する話がされていれば是非教えてくださいー

31
31
10

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
31
31