From f46ad012bc5a40194242ea1e9669c9cc25bd7047 Mon Sep 17 00:00:00 2001 From: Timothy J Fontaine Date: Fri, 17 May 2013 15:04:24 -0700 Subject: [PATCH] timers: internal unref'd timer for api timeouts When an internal api needs a timeout, they should use timers._unrefActive since that won't hold the loop open. This solves the problem where you might have unref'd the socket handle but the timeout for the socket was still active. --- lib/timers.js | 108 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/lib/timers.js b/lib/timers.js index 708f0af16e2..076503e6017 100644 --- a/lib/timers.js +++ b/lib/timers.js @@ -373,3 +373,111 @@ exports.clearImmediate = function(immediate) { process._needImmediateCallback = false; } }; + + +// Internal APIs that need timeouts should use timers._unrefActive isntead of +// timers.active as internal timeouts shouldn't hold the loop open + +var unrefList, unrefTimer; + + +function unrefTimeout() { + var now = Date.now(); + + debug('unrefTimer fired'); + + var first; + while (first = L.peek(unrefList)) { + var diff = now - first._idleStart; + + if (diff < first._idleTimeout) { + diff = first._idleTimeout - diff; + unrefTimer.start(diff, 0); + unrefTimer.when = now + diff; + debug('unrefTimer rescheudling for later'); + return; + } + + L.remove(first); + + var domain = first.domain; + + if (!first._onTimeout) continue; + if (domain && domain._disposed) continue; + + try { + if (domain) domain.enter(); + var threw = true; + debug('unreftimer firing timeout'); + first._onTimeout(); + threw = false; + if (domain) domain.exit(); + } finally { + if (threw) process.nextTick(unrefTimeout); + } + } + + debug('unrefList is empty'); + unrefTimer.when = -1; +} + + +exports._unrefActive = function(item) { + var msecs = item._idleTimeout; + if (!msecs || msecs < 0) return; + assert(msecs >= 0); + + L.remove(item); + + if (!unrefList) { + debug('unrefList initialized'); + unrefList = {}; + L.init(unrefList); + + debug('unrefTimer initialized'); + unrefTimer = new Timer(); + unrefTimer.unref(); + unrefTimer.when = -1; + unrefTimer.ontimeout = unrefTimeout; + } + + var now = Date.now(); + item._idleStart = now; + + if (L.isEmpty(unrefList)) { + debug('unrefList empty'); + L.append(unrefList, item); + + unrefTimer.start(msecs, 0); + unrefTimer.when = now + msecs; + debug('unrefTimer scheduled'); + return; + } + + var when = now + msecs; + + debug('unrefList find where we can insert'); + + var cur, them; + + for (cur = unrefList._idlePrev; cur != unrefList; cur = cur._idlePrev) { + them = cur._idleStart + cur._idleTimeout; + + if (when < them) { + debug('unrefList inserting into middle of list'); + + L.append(cur, item); + + if (unrefTimer.when > when) { + debug('unrefTimer is scheduled to fire too late, reschedule'); + unrefTimer.start(msecs, 0); + unrefTimer.when = when; + } + + return; + } + } + + debug('unrefList append to end'); + L.append(unrefList, item); +};