|
|
|
'use strict';
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This is a regression test for
|
|
|
|
* 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
|
|
|
|
* handle was started with a timeout that was actually incorrect.
|
|
|
|
*
|
|
|
|
* The reason was that the value that represents the current time was not
|
|
|
|
* updated between the time the original callback was called and the time
|
|
|
|
* the added timer was processed by timers.listOnTimeout. That led the
|
|
|
|
* logic in timers.listOnTimeout to do an incorrect computation that made
|
|
|
|
* the added timer fire with a timeout of scheduledTimeout +
|
|
|
|
* timeSpentInCallback.
|
|
|
|
*
|
|
|
|
* This test makes sure that a timer added by another timer's callback
|
|
|
|
* fires with the expected timeout.
|
|
|
|
*
|
|
|
|
* It makes sure that it works when the timers list for a given timeout is
|
|
|
|
* empty (see testAddingTimerToEmptyTimersList) and when the timers list
|
|
|
|
* is not empty (see testAddingTimerToNonEmptyTimersList).
|
|
|
|
*/
|
|
|
|
|
|
|
|
const common = require('../common');
|
|
|
|
const assert = require('assert');
|
|
|
|
const Timer = process.binding('timer_wrap').Timer;
|
|
|
|
|
|
|
|
const TIMEOUT = 100;
|
|
|
|
|
|
|
|
let nbBlockingCallbackCalls;
|
|
|
|
let latestDelay;
|
|
|
|
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() {
|
|
|
|
nbBlockingCallbackCalls = 0;
|
|
|
|
latestDelay = 0;
|
|
|
|
timeCallbackScheduled = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
function blockingCallback(retry, callback) {
|
|
|
|
++nbBlockingCallbackCalls;
|
|
|
|
|
|
|
|
if (nbBlockingCallbackCalls > 1) {
|
|
|
|
latestDelay = Timer.now() - timeCallbackScheduled;
|
|
|
|
// Even if timers can fire later than when they've been scheduled
|
|
|
|
// 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
|
|
|
|
// https://github.com/nodejs/node-v0.x-archive/issues/15447 and
|
|
|
|
// https://github.com/nodejs/node-v0.x-archive/issues/9333.
|
|
|
|
if (latestDelay >= TIMEOUT * 2) {
|
|
|
|
if (retries > 0) {
|
|
|
|
retries--;
|
|
|
|
return retry(callback);
|
|
|
|
}
|
|
|
|
assert.fail(`timeout delayed by more than 100% (${latestDelay}ms)`);
|
|
|
|
}
|
|
|
|
if (callback)
|
|
|
|
return callback();
|
|
|
|
} else {
|
|
|
|
// block by busy-looping to trigger the issue
|
|
|
|
common.busyLoop(TIMEOUT);
|
|
|
|
|
|
|
|
timeCallbackScheduled = Timer.now();
|
|
|
|
setTimeout(blockingCallback.bind(null, retry, callback), TIMEOUT);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function testAddingTimerToEmptyTimersList(callback) {
|
|
|
|
initTest();
|
|
|
|
// Call setTimeout just once to make sure the timers list is
|
|
|
|
// empty when blockingCallback is called.
|
|
|
|
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;
|
|
|
|
};
|
|
|
|
|
|
|
|
initTest();
|
|
|
|
// Call setTimeout twice with the same timeout to make
|
|
|
|
// sure the timers list is not empty when blockingCallback is called.
|
|
|
|
setTimeout(
|
|
|
|
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
|
|
|
|
// timers list one.
|
|
|
|
testAddingTimerToEmptyTimersList(
|
|
|
|
common.mustCall(testAddingTimerToNonEmptyTimersList)
|
|
|
|
);
|