Skip to content

Commit 553d95d

Browse files
Fishrock123MylesBorins
authored andcommitted
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: #9606 Fixes: #9561 PR-URL: #9685 Reviewed-By: Rich Trott <rtrott@gmail.com> Conflicts: lib/timers.js
1 parent ae2eff2 commit 553d95d

File tree

2 files changed

+63
-5
lines changed

2 files changed

+63
-5
lines changed

lib/timers.js

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -274,12 +274,12 @@ exports.setInterval = function(callback, repeat) {
274274
function wrapper() {
275275
timer._repeat();
276276

277-
// Timer might be closed - no point in restarting it
278-
if (!timer._repeat)
277+
// Do not re-arm unenroll'd or closed timers.
278+
if (timer._idleTimeout === -1)
279279
return;
280280

281281
// If timer is unref'd (or was - it's permanently removed from the list.)
282-
if (this._handle) {
282+
if (this._handle && timer instanceof Timeout) {
283283
this._handle.start(repeat, 0);
284284
} else {
285285
timer._idleTimeout = repeat;
@@ -309,9 +309,17 @@ const Timeout = function(after) {
309309

310310

311311
function unrefdHandle() {
312-
this.owner._onTimeout();
313-
if (!this.owner._repeat)
312+
// Don't attempt to call the callback if it is not a function.
313+
if (typeof this.owner._onTimeout === 'function') {
314+
this.owner._onTimeout();
315+
}
316+
317+
// Make sure we clean up if the callback is no longer a function
318+
// even if the timer is an interval.
319+
if (!this.owner._repeat
320+
|| typeof this.owner._onTimeout !== 'function') {
314321
this.owner.close();
322+
}
315323
}
316324

317325

@@ -351,6 +359,7 @@ Timeout.prototype.ref = function() {
351359
Timeout.prototype.close = function() {
352360
this._onTimeout = null;
353361
if (this._handle) {
362+
this._idleTimeout = -1;
354363
this._handle[kOnTimeout] = null;
355364
this._handle.close();
356365
} else {
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const timers = require('timers');
5+
6+
{
7+
const interval = setInterval(common.mustCall(() => {
8+
clearTimeout(interval);
9+
}), 1).unref();
10+
}
11+
12+
{
13+
const interval = setInterval(common.mustCall(() => {
14+
interval.close();
15+
}), 1).unref();
16+
}
17+
18+
{
19+
const interval = setInterval(common.mustCall(() => {
20+
timers.unenroll(interval);
21+
}), 1).unref();
22+
}
23+
24+
{
25+
const interval = setInterval(common.mustCall(() => {
26+
interval._idleTimeout = -1;
27+
}), 1).unref();
28+
}
29+
30+
{
31+
const interval = setInterval(common.mustCall(() => {
32+
interval._onTimeout = null;
33+
}), 1).unref();
34+
}
35+
36+
// Use timers' intrinsic behavior to keep this open
37+
// exactly long enough for the problem to manifest.
38+
//
39+
// See https://github.com/nodejs/node/issues/9561
40+
//
41+
// Since this is added after it will always fire later
42+
// than the previous timeouts, unrefed or not.
43+
//
44+
// Keep the event loop alive for one timeout and then
45+
// another. Any problems will occur when the second
46+
// should be called but before it is able to be.
47+
setTimeout(common.mustCall(() => {
48+
setTimeout(common.mustCall(() => {}), 1);
49+
}), 1);

0 commit comments

Comments
 (0)