test: make timers-blocking-callback more reliable · nodejs/node@7245082
11'use strict';
2233/*
4- * This is a regression test for https://github.com/joyent/node/issues/15447
5- * and https://github.com/joyent/node/issues/9333.
4+ * This is a regression test for
5+ * https://github.com/nodejs/node-v0.x-archive/issues/15447 and
6+ * and https://github.com/nodejs/node-v0.x-archive/issues/9333.
67 *
78 * When a timer is added in another timer's callback, its underlying timer
89 * handle was started with a timeout that was actually incorrect.
@@ -28,17 +29,23 @@ const Timer = process.binding('timer_wrap').Timer;
28292930const TIMEOUT = 100;
303131-let nbBlockingCallbackCalls = 0;
32-let latestDelay = 0;
33-let timeCallbackScheduled = 0;
32+let nbBlockingCallbackCalls;
33+let latestDelay;
34+let timeCallbackScheduled;
35+36+// These tests are timing dependent so they may fail even when the bug is
37+// not present (if the host is sufficiently busy that the timers are delayed
38+// significantly). However, they fail 100% of the time when the bug *is*
39+// present, so to increase reliability, allow for a small number of retries.
40+let retries = 2;
34413542function initTest() {
3643nbBlockingCallbackCalls = 0;
3744latestDelay = 0;
3845timeCallbackScheduled = 0;
3946}
404741-function blockingCallback(callback) {
48+function blockingCallback(retry, callback) {
4249++nbBlockingCallbackCalls;
43504451if (nbBlockingCallbackCalls > 1) {
@@ -47,34 +54,61 @@ function blockingCallback(callback) {
4754// to fire, they shouldn't generally be more than 100% late in this case.
4855// But they are guaranteed to be at least 100ms late given the bug in
4956// https://github.com/nodejs/node-v0.x-archive/issues/15447 and
50-// https://github.com/nodejs/node-v0.x-archive/issues/9333..
51-assert(latestDelay < TIMEOUT * 2);
57+// https://github.com/nodejs/node-v0.x-archive/issues/9333.
58+if (latestDelay >= TIMEOUT * 2) {
59+if (retries > 0) {
60+retries--;
61+return retry(callback);
62+}
63+assert.fail(null, null,
64+`timeout delayed by more than 100% (${latestDelay}ms)`);
65+}
5266if (callback)
5367return callback();
5468} else {
5569// block by busy-looping to trigger the issue
5670common.busyLoop(TIMEOUT);
57715872timeCallbackScheduled = Timer.now();
59-setTimeout(blockingCallback.bind(null, callback), TIMEOUT);
73+setTimeout(blockingCallback.bind(null, retry, callback), TIMEOUT);
6074}
6175}
627663-const testAddingTimerToEmptyTimersList = common.mustCall(function(callback) {
77+function testAddingTimerToEmptyTimersList(callback) {
6478initTest();
6579// Call setTimeout just once to make sure the timers list is
6680// empty when blockingCallback is called.
67-setTimeout(blockingCallback.bind(null, callback), TIMEOUT);
68-});
81+setTimeout(
82+blockingCallback.bind(null, testAddingTimerToEmptyTimersList, callback),
83+TIMEOUT
84+);
85+}
86+87+function testAddingTimerToNonEmptyTimersList() {
88+// If both timers fail and attempt a retry, only actually do anything for one
89+// of them.
90+let retryOK = true;
91+const retry = () => {
92+if (retryOK)
93+testAddingTimerToNonEmptyTimersList();
94+retryOK = false;
95+};
699670-const testAddingTimerToNonEmptyTimersList = common.mustCall(function() {
7197initTest();
7298// Call setTimeout twice with the same timeout to make
7399// sure the timers list is not empty when blockingCallback is called.
74-setTimeout(blockingCallback, TIMEOUT);
75-setTimeout(blockingCallback, TIMEOUT);
76-});
100+setTimeout(
101+blockingCallback.bind(null, retry),
102+TIMEOUT
103+);
104+setTimeout(
105+blockingCallback.bind(null, retry),
106+TIMEOUT
107+);
108+}
7710978110// Run the test for the empty timers list case, and then for the non-empty
79-// timers list one
80-testAddingTimerToEmptyTimersList(testAddingTimerToNonEmptyTimersList);
111+// timers list one.
112+testAddingTimerToEmptyTimersList(
113+common.mustCall(testAddingTimerToNonEmptyTimersList)
114+);