Browse Source

timers: use consistent checks for canceled timers

Previously not all codepaths set `timer._idleTimeout = -1` for canceled
or closed timers, and not all codepaths checked it either.

Unenroll uses this to say that a timer is indeed closed and it is the
closest thing there is to an authoritative source for this.

Refs: https://github.com/nodejs/node/pull/9606
Fixes: https://github.com/nodejs/node/issues/9561
PR-URL: https://github.com/nodejs/node/pull/9685
Reviewed-By: Rich Trott <rtrott@gmail.com>

 Conflicts:
	lib/timers.js
v4.x
Jeremiah Senkpiel 8 years ago
committed by Myles Borins
parent
commit
553d95da15
  1. 17
      lib/timers.js
  2. 49
      test/parallel/test-timers-unenroll-unref-interval.js

17
lib/timers.js

@ -274,12 +274,12 @@ exports.setInterval = function(callback, repeat) {
function wrapper() { function wrapper() {
timer._repeat(); timer._repeat();
// Timer might be closed - no point in restarting it // Do not re-arm unenroll'd or closed timers.
if (!timer._repeat) if (timer._idleTimeout === -1)
return; return;
// If timer is unref'd (or was - it's permanently removed from the list.) // If timer is unref'd (or was - it's permanently removed from the list.)
if (this._handle) { if (this._handle && timer instanceof Timeout) {
this._handle.start(repeat, 0); this._handle.start(repeat, 0);
} else { } else {
timer._idleTimeout = repeat; timer._idleTimeout = repeat;
@ -309,9 +309,17 @@ const Timeout = function(after) {
function unrefdHandle() { function unrefdHandle() {
// Don't attempt to call the callback if it is not a function.
if (typeof this.owner._onTimeout === 'function') {
this.owner._onTimeout(); this.owner._onTimeout();
if (!this.owner._repeat) }
// Make sure we clean up if the callback is no longer a function
// even if the timer is an interval.
if (!this.owner._repeat
|| typeof this.owner._onTimeout !== 'function') {
this.owner.close(); this.owner.close();
}
} }
@ -351,6 +359,7 @@ Timeout.prototype.ref = function() {
Timeout.prototype.close = function() { Timeout.prototype.close = function() {
this._onTimeout = null; this._onTimeout = null;
if (this._handle) { if (this._handle) {
this._idleTimeout = -1;
this._handle[kOnTimeout] = null; this._handle[kOnTimeout] = null;
this._handle.close(); this._handle.close();
} else { } else {

49
test/parallel/test-timers-unenroll-unref-interval.js

@ -0,0 +1,49 @@
'use strict';
const common = require('../common');
const timers = require('timers');
{
const interval = setInterval(common.mustCall(() => {
clearTimeout(interval);
}), 1).unref();
}
{
const interval = setInterval(common.mustCall(() => {
interval.close();
}), 1).unref();
}
{
const interval = setInterval(common.mustCall(() => {
timers.unenroll(interval);
}), 1).unref();
}
{
const interval = setInterval(common.mustCall(() => {
interval._idleTimeout = -1;
}), 1).unref();
}
{
const interval = setInterval(common.mustCall(() => {
interval._onTimeout = null;
}), 1).unref();
}
// Use timers' intrinsic behavior to keep this open
// exactly long enough for the problem to manifest.
//
// See https://github.com/nodejs/node/issues/9561
//
// Since this is added after it will always fire later
// than the previous timeouts, unrefed or not.
//
// Keep the event loop alive for one timeout and then
// another. Any problems will occur when the second
// should be called but before it is able to be.
setTimeout(common.mustCall(() => {
setTimeout(common.mustCall(() => {}), 1);
}), 1);
Loading…
Cancel
Save