Skip to content

Commit caf0b36

Browse files
committed
timers: assure setTimeout callback only runs once
Calling this.unref() during the callback of SetTimeout caused the callback to get executed twice because unref() didn't expect to be called during that time and did not stop the ref()ed Timeout but did start a new timer. This commit prevents the new timer creation when the callback was already called. Fixes: #1191 Reviewed-by: Trevor Norris <[email protected]> Reviewed-By: Jeremiah Senkpiel <[email protected]> PR-URL: #1231
1 parent 2ccc8f3 commit caf0b36

File tree

2 files changed

+19
-0
lines changed

2 files changed

+19
-0
lines changed

lib/timers.js

+7
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ function listOnTimeout() {
8585
if (domain)
8686
domain.enter();
8787
threw = true;
88+
first._called = true;
8889
first._onTimeout();
8990
if (domain)
9091
domain.exit();
@@ -293,6 +294,7 @@ exports.clearInterval = function(timer) {
293294

294295

295296
const Timeout = function(after) {
297+
this._called = false;
296298
this._idleTimeout = after;
297299
this._idlePrev = this;
298300
this._idleNext = this;
@@ -310,6 +312,10 @@ Timeout.prototype.unref = function() {
310312
var delay = this._idleStart + this._idleTimeout - now;
311313
if (delay < 0) delay = 0;
312314
exports.unenroll(this);
315+
316+
// Prevent running cb again when unref() is called during the same cb
317+
if (this._called && !this._repeat) return;
318+
313319
this._handle = new Timer();
314320
this._handle[kOnTimeout] = this._onTimeout;
315321
this._handle.start(delay, 0);
@@ -492,6 +498,7 @@ function unrefTimeout() {
492498
if (domain) domain.enter();
493499
threw = true;
494500
debug('unreftimer firing timeout');
501+
first._called = true;
495502
first._onTimeout();
496503
threw = false;
497504
if (domain)

test/parallel/test-timers-unref.js

+12
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ var interval_fired = false,
55
timeout_fired = false,
66
unref_interval = false,
77
unref_timer = false,
8+
unref_callbacks = 0,
89
interval, check_unref, checks = 0;
910

1011
var LONG_TIME = 10 * 1000;
@@ -34,6 +35,16 @@ check_unref = setInterval(function() {
3435
checks += 1;
3536
}, 100);
3637

38+
setTimeout(function() {
39+
unref_callbacks++;
40+
this.unref();
41+
}, SHORT_TIME);
42+
43+
// Should not timeout the test
44+
setInterval(function() {
45+
this.unref();
46+
}, SHORT_TIME);
47+
3748
// Should not assert on args.Holder()->InternalFieldCount() > 0. See #4261.
3849
(function() {
3950
var t = setInterval(function() {}, 1);
@@ -46,4 +57,5 @@ process.on('exit', function() {
4657
assert.strictEqual(timeout_fired, false, 'Timeout should not fire');
4758
assert.strictEqual(unref_timer, true, 'An unrefd timeout should still fire');
4859
assert.strictEqual(unref_interval, true, 'An unrefd interval should still fire');
60+
assert.strictEqual(unref_callbacks, 1, 'Callback should only run once');
4961
});

0 commit comments

Comments
 (0)