Skip to content

Commit 8bb66cd

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: #9606Fixes: #9561 PR-URL: #9685 Reviewed-By: Rich Trott <[email protected]>
1 parent acebfed commit 8bb66cd

File tree

2 files changed

+63
-2
lines changed

2 files changed

+63
-2
lines changed

‎lib/timers.js‎

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,9 @@ function ontimeout(timer){
384384

385385

386386
functionrearm(timer){
387+
// // Do not re-arm unenroll'd or closed timers.
388+
if(timer._idleTimeout===-1)return;
389+
387390
// If timer is unref'd (or was - it's permanently removed from the list.)
388391
if(timer._handle&&timerinstanceofTimeout){
389392
timer._handle.start(timer._repeat);
@@ -463,9 +466,17 @@ function Timeout(after, callback, args){
463466

464467

465468
functionunrefdHandle(){
466-
ontimeout(this.owner);
467-
if(!this.owner._repeat)
469+
// Don't attempt to call the callback if it is not a function.
470+
if(typeofthis.owner._onTimeout==='function'){
471+
ontimeout(this.owner);
472+
}
473+
474+
// Make sure we clean up if the callback is no longer a function
475+
// even if the timer is an interval.
476+
if(!this.owner._repeat
477+
||typeofthis.owner._onTimeout!=='function'){
468478
this.owner.close();
479+
}
469480
}
470481

471482

@@ -505,6 +516,7 @@ Timeout.prototype.ref = function(){
505516
Timeout.prototype.close=function(){
506517
this._onTimeout=null;
507518
if(this._handle){
519+
this._idleTimeout=-1;
508520
this._handle[kOnTimeout]=null;
509521
this._handle.close();
510522
}else{
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
'use strict';
2+
3+
constcommon=require('../common');
4+
consttimers=require('timers');
5+
6+
{
7+
constinterval=setInterval(common.mustCall(()=>{
8+
clearTimeout(interval);
9+
}),1).unref();
10+
}
11+
12+
{
13+
constinterval=setInterval(common.mustCall(()=>{
14+
interval.close();
15+
}),1).unref();
16+
}
17+
18+
{
19+
constinterval=setInterval(common.mustCall(()=>{
20+
timers.unenroll(interval);
21+
}),1).unref();
22+
}
23+
24+
{
25+
constinterval=setInterval(common.mustCall(()=>{
26+
interval._idleTimeout=-1;
27+
}),1).unref();
28+
}
29+
30+
{
31+
constinterval=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)