From 951168f96c6656d4c7a05dfd63e001badd4dfdfe Mon Sep 17 00:00:00 2001 From: Roman Reiss Date: Sat, 21 Mar 2015 17:39:35 +0100 Subject: [PATCH 1/5] timers: assure setTimeout callback only runs once This fixes an edge case where running this.unref() during the callback caused the callback to get executed multiple times. --- lib/timers.js | 5 +++++ test/parallel/test-timers-unref.js | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/lib/timers.js b/lib/timers.js index 102a927a19c6a1..dfba03d97923e7 100644 --- a/lib/timers.js +++ b/lib/timers.js @@ -85,6 +85,7 @@ function listOnTimeout() { if (domain) domain.enter(); threw = true; + first._called = true; first._onTimeout(); if (domain) domain.exit(); @@ -305,6 +306,9 @@ Timeout.prototype.unref = function() { if (this._handle) { this._handle.unref(); } else if (typeof(this._onTimeout) === 'function') { + // Prevent running callback multiple times + // when unref() is called during the callback + if (this._called) return; var now = Timer.now(); if (!this._idleStart) this._idleStart = now; var delay = this._idleStart + this._idleTimeout - now; @@ -492,6 +496,7 @@ function unrefTimeout() { if (domain) domain.enter(); threw = true; debug('unreftimer firing timeout'); + first._called = true; first._onTimeout(); threw = false; if (domain) diff --git a/test/parallel/test-timers-unref.js b/test/parallel/test-timers-unref.js index 9434dbbd36ad78..23d4fc4cc5f953 100644 --- a/test/parallel/test-timers-unref.js +++ b/test/parallel/test-timers-unref.js @@ -5,6 +5,7 @@ var interval_fired = false, timeout_fired = false, unref_interval = false, unref_timer = false, + unref_callbacks = 0, interval, check_unref, checks = 0; var LONG_TIME = 10 * 1000; @@ -34,6 +35,11 @@ check_unref = setInterval(function() { checks += 1; }, 100); +setTimeout(function() { + unref_callbacks++; + this.unref(); +}, SHORT_TIME); + // Should not assert on args.Holder()->InternalFieldCount() > 0. See #4261. (function() { var t = setInterval(function() {}, 1); @@ -46,4 +52,5 @@ process.on('exit', function() { assert.strictEqual(timeout_fired, false, 'Timeout should not fire'); assert.strictEqual(unref_timer, true, 'An unrefd timeout should still fire'); assert.strictEqual(unref_interval, true, 'An unrefd interval should still fire'); + assert.strictEqual(unref_callbacks, 1, 'Callback should only run once'); }); From bc94f6c3adf0b8a96acf5c1f04d22a7d0fe892cf Mon Sep 17 00:00:00 2001 From: Roman Reiss Date: Wed, 25 Mar 2015 19:19:29 +0100 Subject: [PATCH 2/5] Add _called to prototype --- lib/timers.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/timers.js b/lib/timers.js index dfba03d97923e7..1ea00d7ce44edd 100644 --- a/lib/timers.js +++ b/lib/timers.js @@ -294,6 +294,7 @@ exports.clearInterval = function(timer) { const Timeout = function(after) { + this._called = false; this._idleTimeout = after; this._idlePrev = this; this._idleNext = this; From a58d1492d564a78b6bfcd4cf1738035029a797f2 Mon Sep 17 00:00:00 2001 From: Roman Reiss Date: Wed, 25 Mar 2015 19:55:07 +0100 Subject: [PATCH 3/5] Add test for unref during setInterval --- test/parallel/test-timers-unref.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/parallel/test-timers-unref.js b/test/parallel/test-timers-unref.js index 23d4fc4cc5f953..69a67bddc3ff55 100644 --- a/test/parallel/test-timers-unref.js +++ b/test/parallel/test-timers-unref.js @@ -35,10 +35,14 @@ check_unref = setInterval(function() { checks += 1; }, 100); +// unref() during the callback setTimeout(function() { unref_callbacks++; this.unref(); }, SHORT_TIME); +setInterval(function() { + this.unref(); +}, SHORT_TIME); // Should not assert on args.Holder()->InternalFieldCount() > 0. See #4261. (function() { From c99318c733f8c4b6bb4cd94afd5bb3d555f5fde2 Mon Sep 17 00:00:00 2001 From: Roman Reiss Date: Wed, 25 Mar 2015 20:44:15 +0100 Subject: [PATCH 4/5] Let timer unenroll and prevent setInterval issues --- lib/timers.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/timers.js b/lib/timers.js index 1ea00d7ce44edd..dd39e7b3d9edaf 100644 --- a/lib/timers.js +++ b/lib/timers.js @@ -307,14 +307,15 @@ Timeout.prototype.unref = function() { if (this._handle) { this._handle.unref(); } else if (typeof(this._onTimeout) === 'function') { - // Prevent running callback multiple times - // when unref() is called during the callback - if (this._called) return; var now = Timer.now(); if (!this._idleStart) this._idleStart = now; var delay = this._idleStart + this._idleTimeout - now; if (delay < 0) delay = 0; exports.unenroll(this); + + // Prevent running cb again when unref() is called during the same cb + if (this._called && !this._repeat) return; + this._handle = new Timer(); this._handle[kOnTimeout] = this._onTimeout; this._handle.start(delay, 0); From 1a824b17b12a2dff30089c77b322285e22afae5f Mon Sep 17 00:00:00 2001 From: Roman Reiss Date: Wed, 25 Mar 2015 21:22:24 +0100 Subject: [PATCH 5/5] Add note to setInterval test --- test/parallel/test-timers-unref.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/parallel/test-timers-unref.js b/test/parallel/test-timers-unref.js index 69a67bddc3ff55..2f750228a4ef3f 100644 --- a/test/parallel/test-timers-unref.js +++ b/test/parallel/test-timers-unref.js @@ -35,11 +35,12 @@ check_unref = setInterval(function() { checks += 1; }, 100); -// unref() during the callback setTimeout(function() { unref_callbacks++; this.unref(); }, SHORT_TIME); + +// Should not timeout the test setInterval(function() { this.unref(); }, SHORT_TIME);