@ -1,8 +1,9 @@
'use strict' ;
'use strict' ;
/ *
/ *
* This is a regression test for https : //github.com/joyent/node/issues/15447
* This is a regression test for
* and https : //github.com/joyent/node/issues/9333.
* https : //github.com/nodejs/node-v0.x-archive/issues/15447 and
* and https : //github.com/nodejs/node-v0.x-archive/issues/9333.
*
*
* When a timer is added in another timer ' s callback , its underlying timer
* When a timer is added in another timer ' s callback , its underlying timer
* handle was started with a timeout that was actually incorrect .
* handle was started with a timeout that was actually incorrect .
@ -28,9 +29,15 @@ const Timer = process.binding('timer_wrap').Timer;
const TIMEOUT = 100 ;
const TIMEOUT = 100 ;
let nbBlockingCallbackCalls = 0 ;
let nbBlockingCallbackCalls ;
let latestDelay = 0 ;
let latestDelay ;
let timeCallbackScheduled = 0 ;
let timeCallbackScheduled ;
// These tests are timing dependent so they may fail even when the bug is
// not present (if the host is sufficiently busy that the timers are delayed
// significantly). However, they fail 100% of the time when the bug *is*
// present, so to increase reliability, allow for a small number of retries.
let retries = 2 ;
function initTest ( ) {
function initTest ( ) {
nbBlockingCallbackCalls = 0 ;
nbBlockingCallbackCalls = 0 ;
@ -38,7 +45,7 @@ function initTest() {
timeCallbackScheduled = 0 ;
timeCallbackScheduled = 0 ;
}
}
function blockingCallback ( callback ) {
function blockingCallback ( retry , callback ) {
++ nbBlockingCallbackCalls ;
++ nbBlockingCallbackCalls ;
if ( nbBlockingCallbackCalls > 1 ) {
if ( nbBlockingCallbackCalls > 1 ) {
@ -47,8 +54,15 @@ function blockingCallback(callback) {
// to fire, they shouldn't generally be more than 100% late in this case.
// to fire, they shouldn't generally be more than 100% late in this case.
// But they are guaranteed to be at least 100ms late given the bug in
// But they are guaranteed to be at least 100ms late given the bug in
// https://github.com/nodejs/node-v0.x-archive/issues/15447 and
// https://github.com/nodejs/node-v0.x-archive/issues/15447 and
// https://github.com/nodejs/node-v0.x-archive/issues/9333..
// https://github.com/nodejs/node-v0.x-archive/issues/9333.
assert ( latestDelay < TIMEOUT * 2 ) ;
if ( latestDelay >= TIMEOUT * 2 ) {
if ( retries > 0 ) {
retries -- ;
return retry ( callback ) ;
}
assert . fail ( null , null ,
` timeout delayed by more than 100% ( ${ latestDelay } ms) ` ) ;
}
if ( callback )
if ( callback )
return callback ( ) ;
return callback ( ) ;
} else {
} else {
@ -56,25 +70,45 @@ function blockingCallback(callback) {
common . busyLoop ( TIMEOUT ) ;
common . busyLoop ( TIMEOUT ) ;
timeCallbackScheduled = Timer . now ( ) ;
timeCallbackScheduled = Timer . now ( ) ;
setTimeout ( blockingCallback . bind ( null , callback ) , TIMEOUT ) ;
setTimeout ( blockingCallback . bind ( null , retry , callback ) , TIMEOUT ) ;
}
}
}
}
const testAddingTimerToEmptyTimersList = common . mustCall ( function ( callback ) {
function testAddingTimerToEmptyTimersList ( callback ) {
initTest ( ) ;
initTest ( ) ;
// Call setTimeout just once to make sure the timers list is
// Call setTimeout just once to make sure the timers list is
// empty when blockingCallback is called.
// empty when blockingCallback is called.
setTimeout ( blockingCallback . bind ( null , callback ) , TIMEOUT ) ;
setTimeout (
} ) ;
blockingCallback . bind ( null , testAddingTimerToEmptyTimersList , callback ) ,
TIMEOUT
) ;
}
function testAddingTimerToNonEmptyTimersList ( ) {
// If both timers fail and attempt a retry, only actually do anything for one
// of them.
let retryOK = true ;
const retry = ( ) => {
if ( retryOK )
testAddingTimerToNonEmptyTimersList ( ) ;
retryOK = false ;
} ;
const testAddingTimerToNonEmptyTimersList = common . mustCall ( function ( ) {
initTest ( ) ;
initTest ( ) ;
// Call setTimeout twice with the same timeout to make
// Call setTimeout twice with the same timeout to make
// sure the timers list is not empty when blockingCallback is called.
// sure the timers list is not empty when blockingCallback is called.
setTimeout ( blockingCallback , TIMEOUT ) ;
setTimeout (
setTimeout ( blockingCallback , TIMEOUT ) ;
blockingCallback . bind ( null , retry ) ,
} ) ;
TIMEOUT
) ;
setTimeout (
blockingCallback . bind ( null , retry ) ,
TIMEOUT
) ;
}
// Run the test for the empty timers list case, and then for the non-empty
// Run the test for the empty timers list case, and then for the non-empty
// timers list one
// timers list one.
testAddingTimerToEmptyTimersList ( testAddingTimerToNonEmptyTimersList ) ;
testAddingTimerToEmptyTimersList (
common . mustCall ( testAddingTimerToNonEmptyTimersList )
) ;