0
0
mirror of https://github.com/nodejs/node.git synced 2024-11-29 23:16:30 +01:00
nodejs/test/sequential/test-timers-blocking-callback.js
Rich Trott c473d80a9c test: make timers-blocking-callback more reliable
test-timers-blocking-callback may fail erroneously on
resource-constrained machines due to the timing nature of the test.

There is likely no way around the timing issue. This change tries to
decrease the probability of the test failing erroneously by having it
retry a small number of times on failure.

Tested on 0.10.38 (which has a bug that this test was written for) and
(modifying the test slightly to remove ES6 stuff) the test still seems
to fail 100% of the time there, which is what we want/expect.

PR-URL: https://github.com/nodejs/node/pull/14831
Fixes: https://github.com/nodejs/node/issues/14792
Reviewed-By: Refael Ackermann <refack@gmail.com>
Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
2017-08-20 15:41:18 -07:00

114 lines
3.6 KiB
JavaScript

'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)
);