| 'use strict'; |
| |
| /* |
| * This is a regression test for |
| * https://github.com/nodejs/node-v0.x-archive/issues/15447 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 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 = Date.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 = Date.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) |
| ); |