From 9bb2b675b669ee71f60aace1a1797d281f5bebec Mon Sep 17 00:00:00 2001 From: Jeremy Whitlock <jwhitlock@apache.org> Date: Thu, 23 Jul 2015 18:09:21 -0600 Subject: [PATCH] timers: fix processing of nested same delay timers Whenever a timer with a specific timeout value creates a new timer with the same timeout, the newly added timer might be processed immediately in the same tick of the event loop instead of during the next tick of the event loop at the earliest. Fixes #25607 --- lib/timers.js | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/lib/timers.js b/lib/timers.js index 83493eb609ee..042abe3daa7f 100644 --- a/lib/timers.js +++ b/lib/timers.js @@ -87,13 +87,35 @@ function listOnTimeout() { var first; while (first = L.peek(list)) { - // If the previous iteration caused a timer to be added, - // update the value of "now" so that timing computations are - // done correctly. See test/simple/test-timers-blocking-callback.js - // for more information. + // This handles the case of a timer that was created within a timers + // callback with the same timeout value. For instance, when processing the + // timer that would call `bar` in such code: + // + // setTimeout(function foo() { setTimeout(function bar() {}, 0) }, 0); + // + // or + // + // setTimeout(function foo() { setTimeout(function bar() {}, 500) }, 500); + // + // We want to make sure that newly added timer fires in the next turn of the + // event loop at the earliest. So even if it's already expired now, + // reschedule it to fire later. + // + // At that point, it's not necessary to process any other timer in that + // list, because any remaining timer has been added within a callback of a + // timer that has already been processed, and thus needs to be processed at + // the earliest not in the current tick, but when the rescheduled timer will + // expire. + // + // See: https://github.com/joyent/node/issues/25607 if (now < first._monotonicStartTime) { - now = Timer.now(); - debug('now: %d', now); + var timeRemaining = msecs - (Timer.now() - first._monotonicStartTime); + if (timeRemaining < 0) { + timeRemaining = 0; + } + debug(msecs + ' list wait because timer was added from another timer'); + list.start(timeRemaining, 0); + return; } var diff = now - first._monotonicStartTime;