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;