├── .gitignore ├── LICENSE ├── package.json ├── index.js ├── ChangeLog ├── test └── test-qtimers.js ├── Readme.md └── lib └── qtimers.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2013-2014 Andras Radics 2 | andras at andrasq dot com 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "qtimers", 3 | "version": "1.4.10", 4 | "description": "fast setImmediate, setTimeout, etc replacements", 5 | "license": "Apache-2.0", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/andrasq/node-qtimers" 9 | }, 10 | "main": "index.js", 11 | "author": { 12 | "name": "Andras", 13 | "url": "http://github.com/andrasq" 14 | }, 15 | "engines": { 16 | "node": "*" 17 | }, 18 | "dependencies": { 19 | "qheap": "1.3.0", 20 | "qlist": "0.12.0" 21 | }, 22 | "devDependencies": { 23 | "qnit": "0.11.0" 24 | }, 25 | "scripts": { 26 | "test": "qnit test/test-*" 27 | }, 28 | "keywords": [ 29 | "Andras", 30 | "quick", 31 | "fast", 32 | "timers", 33 | "timers.js", 34 | "setImmediate", 35 | "setTimeout", 36 | "setInterval" 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Andras Radics 3 | * Licensed under the Apache License, Version 2.0 4 | */ 5 | 6 | module.exports = require('./lib/qtimers.js'); 7 | 8 | 9 | existingCalls = { 10 | setImmediate: global.setImmediate || false, 11 | clearImmediate: global.clearImmediate || false, 12 | setTimeout: global.setTimeout || false, 13 | clearTimeout: global.clearTimeout || false, 14 | setInterval: global.setInterval || false, 15 | clearInterval: global.clearInterval || false, 16 | currentTimestamp: global.currentTimestamp || false, 17 | }; 18 | 19 | module.exports.install = function( ) { 20 | var i; 21 | for (i in existingCalls) global[i] = module.exports[i]; 22 | }; 23 | 24 | module.exports.uninstall = function( ) { 25 | var i; 26 | for (i in existingCalls) global[i] = existingCalls[i]; 27 | }; 28 | 29 | // the default is to install our calls in place of the builtins 30 | module.exports.install(); 31 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | 1.4.10 2 | - string hash keys are faster than numbers 3 | - faster setImmediate for 1-2 args 4 | - faster callbacks via _runCallbackItem 5 | - upgrade qheap, qlist 6 | 7 | 1.4.8 8 | 9 | - fix mis-keyed func name 10 | - upgrade to qheap 1.1.0 (speedup) 11 | - test with qnit 0.11.0 12 | 13 | 1.4.7 14 | 15 | - huge speedup by saving callback arguments list in an array, not a closure 16 | 17 | 1.4.6 18 | 19 | - task domain and error handling fixes 20 | 21 | 1.4.5 22 | - fully switch to scheduleTimer() instead of timeoutTimer and idleTimer 23 | - test with qnit 24 | - accelerate up to 3 arguments 25 | - faster setTimeout 26 | - faster arguments transcription 27 | 28 | 1.4.2 29 | - small speedup to timeout and interval timers 30 | - do not rely on array.shift() when processing timers 31 | - process all timeout tasks as soon as they have come due, do not yield between batches 32 | - bump to qlist 0.9.2 for a bugfix 33 | 34 | 1.4.0 35 | - make load under node v0.8 (untested) 36 | 37 | 1.3.1 38 | - doc updates 39 | 40 | 1.3.0 41 | - define semantics for maxTickDepth = 0 (all) and < 0 (all + -maxTickDepth more) 42 | 43 | 1.2.0 44 | - export currentTimestamp() as a global 45 | - speedup: no point counting currentTimestamp() calls 46 | 47 | 1.1.1 48 | - speedup: ref/unref is slow, run an idleTimer alongside instead 49 | 50 | 1.1.0 51 | - belated version bump 52 | 53 | 1.0.7 54 | - documentation 55 | 56 | 1.0.6 57 | - export currentTimestamp() 58 | - bugfix: never shut down the timeoutTimer 59 | 60 | 1.0.5 61 | - bugfix: use qlist@0.9.0 62 | 63 | 1.0.3 64 | - use qheap@1.0.4 for speedup 65 | 66 | 1.0.2 67 | - bugfix: use qheap@1.0.3 68 | - use qheap@1.0.0 for speedup 69 | 70 | 1.0.1 71 | - refactor; speedup 72 | 73 | 1.0.0 74 | - unit tests, Readme, refactor 75 | 76 | 0.1.0 77 | - initial checkin 78 | -------------------------------------------------------------------------------- /test/test-qtimers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Andras Radics 3 | * Licensed under the Apache License, Version 2.0 4 | */ 5 | 6 | assert = require('assert'); 7 | 8 | var timers = require('../index'); 9 | timers.install(); 10 | 11 | module.exports = { 12 | 'package.json should parse': function(t) { 13 | t.expect(1); 14 | var json = require('../package.json'); 15 | t.equal('qtimers', json.name); 16 | t.done(); 17 | }, 18 | 19 | 'should expose globals': function(t) { 20 | var calls = [ 21 | 'setImmediate', 'clearImmediate', 22 | 'setTimeout', 'clearTimeout', 23 | 'setInterval', 'clearInterval', 24 | 'currentTimestamp', 25 | ]; 26 | for (var i=0; i= 10) { clearInterval(int); t.ok(1); t.done(); } }, 1); 198 | }, 199 | }, 200 | }; 201 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | qtimers 2 | ======= 3 | 4 | a fast drop-in replacement for `require('timers')` with support for 5 | setImmediate, setTimeout, etc. 6 | 7 | QTimers uses faster internal data structures than the builtin nodejs 8 | timers.js, and (optionally) relaxes some nodejs limitations. The speedups are 9 | from a combination of: 10 | 11 | - fast circular buffer for immediate tasks, not a linked list 12 | - better v8 optimization (eg not passing `arguments` to array.slice) 13 | - fast-path single-argument callbacks too 14 | - multiple immediate calls before checking event loop 15 | - better reuse of timestamps 16 | 17 | Development was primarily with node-v0.10.29. Actual runtimes vary, but 18 | substantial speedups were seen with node-v0.12, node-v0.11.13, and iojs 19 | as well. 20 | 21 | In addition to being faster, qtimers invokes all callbacks in a well-defined 22 | order: immediates in queueing order and timeouts in expiration order. Node 23 | v0.10.29 sometimes calls timeout functions out of sequence (running a later 24 | function before the earlier), which can result in very subtle and hard-to-find 25 | glitches. 26 | 27 | 28 | Examples 29 | -------- 30 | 31 | Install QTimers, exit in 1 second: 32 | 33 | require('qtimers'); 34 | setTimeout(process.exit, 1000); 35 | 36 | Exit immediately: 37 | 38 | require('qtimers'); 39 | t = setTimeout(process.exit, 1000); 40 | t.unref(); 41 | 42 | Write 100 dots: 43 | 44 | require('qtimers'); 45 | for (i=0; i<100; i++) setImmediate(function(){ process.stdout.write(".") }); 46 | 47 | Loop a million times, quickly: 48 | 49 | require('qtimers'); 50 | nleft = 1000000; 51 | function loop() { if (--nleft > 0) setImmediate(loop); } 52 | loop(); 53 | // maxTickDepth = 100: 0.12 sec 54 | // maxTickDepth = 10: 0.20 sec 55 | // maxTickDepth = 1: 0.91 sec 56 | // node-v0.10.29: 1.77 sec 57 | // node-v0.12: 2.25 sec 58 | 59 | Loop a million times, less quickly: 60 | 61 | require('qtimers'); 62 | function loop(nleft) { if (--nleft > 0) setImmediate(loop, nleft); } 63 | loop(1000000); 64 | // maxTickDepth = 100: 0.42 sec 65 | // maxTickDepth = 10: 0.50 sec 66 | // maxTickDepth = 1: 1.3 sec 67 | 68 | Loop a million times, slowly: 69 | 70 | function loop(nleft) { if (--nleft > 0) setImmediate(loop, nleft); } 71 | loop(1000000); 72 | // node-v0.10.29: 14.4 sec 73 | // node-v0.12: 3.4 sec 74 | 75 | 76 | Timer Calls 77 | ----------- 78 | 79 | QTimers supports all the functionality provided by the node built-in. 80 | 81 | ### setImmediate( fn, [arg1, ...] ) 82 | 83 | Arrange for fn() to be called with the provided arguments after the current 84 | thread exits. Returns an opaque immediateObject that can be used to cancel 85 | the call. SetImmediate functions are run in the order added. Up to 86 | `setImmediate.maxTickDepth` functions are run before checking the event loop 87 | (default -10). 88 | 89 | Note that node v0.10 ran only 1 immediate callback between checks of the event 90 | loop. Node v0.12 runs all queued callbacks. QTimers can mimic both these 91 | behaviors (set `maxTickDepth` below), but defaults to running a fixed count, 92 | regardless of whether queued already or queued by the immediate callback that 93 | just ran. This affords control over the tradeoff between not starving events 94 | vs minimizing the immediate queue overhead, which is substantial. 95 | 96 | ### clearImmediate( immediateObject ) 97 | 98 | Cancel the immediate callback. 99 | 100 | ### setTimeout( fn, ms, [arg1, ...]) 101 | 102 | Arrange for fn() to be called after a delay of `ms` milliseconds. Returns a 103 | timeoutObject. 104 | 105 | ### clearTimeout( timeoutObject ) 106 | 107 | Cancel the timeout callback. 108 | 109 | ### setInterval( fn, ms, [arg1, ...]) 110 | 111 | Arrange for fn() to be called after a delay of `ms` milliseconds and every 112 | `ms` thereafter. Returns an intervalObject. 113 | 114 | ### clearInterval( intervalObject ) 115 | 116 | Cancel the interval callback. 117 | 118 | The opaque timer objects returned by setTimeout and setInterval provide a 119 | method `unref()`. Calling unref will prevent that timer from keeping the 120 | program running if there are no other events left pending. The `ref()` method 121 | will undo an unref. 122 | 123 | ### timeoutObject.unref( ), intervalObject.unref( ) 124 | 125 | Do not stop the program from exiting just because this timer is active. 126 | 127 | ### timeoutObject.ref( ), intervalObject.ref( ) 128 | 129 | Disable unref, do not exit the program as long as this timer is active. 130 | Timeout timers deactivate when run; interval timers remain active until 131 | canceled. 132 | 133 | Timer Extras 134 | ------------ 135 | 136 | In addition, QTimers allows adjusting some internal parameters and provides 137 | additional functionality: 138 | 139 | ### setTimeout.MIN_TIMEOUT = 1 140 | 141 | The shortest permitted timeout for setTimeout and setInterval functions, in 142 | milliseconds. Default 1. Note that setting MIN_TIMEOUT to 0 is not the same 143 | as using setImmediate: timeouts are quantized to millisecond intervals, so 144 | the function will get called after a period of delay of as much as 1 ms. 145 | 146 | ### setTimeout.MAX_TIMEOUT = (2**31 - 1) 147 | 148 | The longest permitted timeout for setTimeout and setInterval, in milliseconds. 149 | Default (2 ^ 31) - 1, is the largest positive 32-bit twos-complement integer. 150 | Note that setting MAX_TIMEOUT low is not the same as capping the delay: if 151 | the timeout delay is outside the valid range, a delay of MIN_TIMEOUT (1 ms) is 152 | used instead. 153 | 154 | ### setImmediate.maxTickDepth = -10 155 | 156 | The number of setImmediate callbacks to call before checking the event loop. 157 | Any callbacks not run will be handled after the event loop has been processed. 158 | 159 | QTimers runs immediate callbacks by count, not by when queued. The app can tune 160 | the number of callbacks to run at a time: a fixed count (even those queued 161 | during the processing of the immediate queue), those already on the queue, or 162 | a fixed count more than already queued. 163 | 164 | The QTimers behavior is configured by setting maxTickDepth appropriately: 165 | 166 | - v0.10 compatible: `maxTickDepth = 1` runs one immediate call at a time 167 | - v0.12 compatible: `maxTickDepth = 0` runs the whole immediate list 168 | - qtimers: `maxTickDepth = 10` runs 10 immediate calls at a time 169 | - qtimers hybrid: `maxTickDepth = -10` runs the whole immediate list + up to 10 newly queued calls 170 | 171 | QTimers has just one immediate list. A call to setImmediate from inside an 172 | immediate function will append to same list currently being processed. If the 173 | configured maxTickDepth is greater than length of the queue, the loop will run 174 | some callbacks added during the loop, too. Running just-queued callbacks can 175 | greatly speed up some usage patterns, e.g. tail-recursive setImmediate. A 176 | `maxTickDepth = 10` is 9 x faster than nodejs v0.10; ` = 100` is 16x faster. 177 | 178 | The nodejs v0.10 spec (Stability: 5, Locked) requires maxTickDepth to be 1, ie 179 | run only one setImmediate call per loop. With maxTickDepth = 1, a QTimers 180 | setImmediate loop is still 85% faster than node-v0.10.29. 181 | 182 | The nodejs v0.12 spec (Stability: 5, Locked) requires maxTickDepth to be 0, 183 | the length of the immediate list, ie run only the already queued immediate 184 | calls and no more. 185 | 186 | Apparently "5, Locked" is not sufficient for the semantics not to change. 187 | Configure QTimers to to match a flavor of the spec, or tune it for speed. 188 | The default -10 is very fast on all versions of node. 189 | 190 | ### uninstall( ) 191 | 192 | When loaded, qtimers install themselves to replace the built-in timers 193 | functions. This can be un-done with `uninstall()`. It is safe to call 194 | `uninstall` more than once. 195 | 196 | ### install( ) 197 | 198 | Explicit call to install (or reinstall) qtimers as the timers package to use. 199 | It is safe to call `install` more than once. 200 | 201 | ### currentTimestamp( ) 202 | 203 | returns the millisecond timestamp that setTimeout uses internally. This is a 204 | scheduling timestamp and not the current time of day, but unless there are lot 205 | of long-running blocking computations, the two should be the same. 206 | 207 | The timestamp is updated by an event timer every millisecond at the beginning 208 | and end of each timeout interval. Each timeout function in that interval will 209 | see the same timestamp; setImmediate functions will see the timestamp set at 210 | the end of the last timeout interval. 211 | 212 | Normally the timestamp will stay in sync with Date.now(), but long-running 213 | blocking functions could introduce a lag. The timestamp is updated every 214 | millisecond while there is active work to do, and every 10 milliseconds when 215 | only unreferenced timers are left. 216 | 217 | 218 | TODO 219 | ---- 220 | 221 | - refactor into a singleton for better testability 222 | - tune setTimeout 223 | - track down why node-v0.12 is slower than v0.10.29 224 | - allow sub-millisecond resolution timeouts and intervals (1/10 ms, say) 225 | - maybe rename currentTimestamp() to getTimeoutTimestamp() ? 226 | - qtimers uses 0.75% cpu at idle (vm), see if can be reduced 227 | -------------------------------------------------------------------------------- /lib/qtimers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * nodejs require('timers') work-alike with setImmediate, setTimeout, etc 3 | * 4 | * Copyright (C) 2014-2015 Andras Radics 5 | * Licensed under the Apache License, Version 2.0 6 | */ 7 | 8 | 'use strict'; 9 | 10 | var Timer = process.binding('timer_wrap').Timer; 11 | var Heap = require('qheap'); 12 | var List = require('qlist'); 13 | 14 | module.exports.setImmediate = setImmediate; 15 | module.exports.clearImmediate = clearImmediate; 16 | module.exports.setTimeout = setTimeout; 17 | module.exports.clearTimeout = clearTimeout; 18 | module.exports.setInterval = setInterval; 19 | module.exports.clearInterval = clearInterval; 20 | module.exports.currentTimestamp = function() { return getTimestamp() }; 21 | 22 | // setImmediate tasks are queued on the immediateList 23 | var immediateList = new List(); 24 | 25 | // timeoutHeap stores the timeout timestamps in sorted order 26 | var timeoutHeap = new Heap({compar: function(a,b) { return a < b ? -1 : 1}}); 27 | 28 | // timeoutHash has the timeout queues by timeout timestamp 29 | var timeoutHash = new Object(); 30 | 31 | // pendingTimeouts counts the number of referenced timeout tasks 32 | var pendingTimeouts = 0; 33 | 34 | function createTimer( callback ) { 35 | var timer = new Timer(); 36 | if (['0.4.', '0.6.', '0.8.', '0.9.', '0.10'].indexOf(process.versions.node.slice(0, 4)) >= 0) { 37 | // set the node v0.10.29 callback field 38 | timer.ontimeout = callback; 39 | } 40 | else { 41 | // set the node v0.11.13 callback field 42 | timer[0] = callback; 43 | } 44 | return timer; 45 | } 46 | 47 | /* 48 | * the timeout timer processes setTimeout and setInterval tasks 49 | */ 50 | var timeoutTimer = createTimer(_processTimeoutTasks); 51 | 52 | /* 53 | * the idle timer processes unref-d tasks when the system is otherwise idle 54 | * It only runs if the system is idle and timeoutTimer has been stopped. 55 | */ 56 | var idleTimer = createTimer(_processTimeoutTasks); 57 | if (idleTimer.unref) idleTimer.unref(); 58 | // TODO: node-v0.8 cannot unref, needs an alternate way to know when ok to exit 59 | 60 | /* 61 | * reset the appropriate timer for the next expiration, 62 | * or turn off both timers when not needed 63 | */ 64 | function scheduleTimer( when, now ) { 65 | idleTimer.stop(); 66 | timeoutTimer.stop(); 67 | if (when > 0) { 68 | // If more ref-d tasks left, schedule the next timeoutTimer wakeup. 69 | // If only unrefd tasks left, stop the timeout timer so program can exit; 70 | // instead, use the idle timer, which is not sticky. 71 | var timer = pendingTimeouts ? timeoutTimer : idleTimer; 72 | timer.start(when > now ? when - now : 0, 1); 73 | } 74 | } 75 | 76 | /* 77 | * timestamp holds the current time and is updated every ms by timeoutTimer. 78 | * Calls queued by setTimeout use 79 | * if a call runs longer than a ms, setTimeout calls queued by calls that 80 | * timed out that millisecond will 81 | */ 82 | var timestamp; 83 | function setTimestamp( ) { timestamp = Date.now(); return timestamp; } 84 | function getTimestamp( ) { if (timestamp) return timestamp; else return setTimestamp(); } 85 | 86 | 87 | /* 88 | * the canonical immediate and timeout items and factories 89 | */ 90 | function ImmediateItem( ) { } 91 | ImmediateItem.prototype = { _callback: null, _argv: null, _domain: null }; 92 | function immediateItemTemplate( fn, av ) { 93 | return {_callback: fn, _argv: av, _domain: null}; }; 94 | 95 | function TimeoutItem( fn, av ) { this._callback = fn; this._argv = av; } 96 | TimeoutItem.prototype = {_callback: null, _argv: null, _func: null, _domain: null, _isref: 1, _when: 0, _interval: 0, ref: refItem, unref: unrefItem}; 97 | function timeoutItemTemplate( fn, av ) { 98 | return {_callback: fn, _argv: av, _func: null, _domain: null, _isref: 1, _when: 0, _interval: 0, ref: refItem, unref: unrefItem}; } 99 | 100 | function refItem( ) { 101 | if (!this._isref && this._callback) { this._isref = 1; pendingTimeouts += 1; } } 102 | function unrefItem( ) { 103 | if (this._isref && this._callback) { this._isref = 0; pendingTimeouts -= 1; } } 104 | 105 | 106 | function setImmediate( fn ) { 107 | var args; 108 | switch (arguments.length) { 109 | case 0: case 1: break; 110 | case 2: args = [arguments[1]]; break; 111 | case 3: args = [arguments[1], arguments[2]]; break; 112 | case 4: args = [arguments[1], arguments[2], arguments[3]]; break; 113 | default: 114 | args = new Array(arguments.length - 1); 115 | for (var i=1; i= setTimeout.MIN_TIMEOUT && ms <= setTimeout.MAX_TIMEOUT) ? ms : setTimeout.MIN_TIMEOUT); 148 | 149 | if (process.domain) item._domain = process.domain; 150 | 151 | return item; 152 | } 153 | 154 | function _scheduleTimeoutItem( item, when ) { 155 | // first bump pending count, only then start timer (else race condition in _processTimeoutTasks) 156 | if (item._isref) pendingTimeouts += 1; 157 | 158 | // much much faster to index a hash by a string 159 | var whenKey = when + ''; 160 | if (timeoutHash[whenKey]) { 161 | timeoutHash[whenKey].push(item); 162 | } 163 | else { 164 | timeoutHash[whenKey] = [ item ]; 165 | timeoutHeap.push(when); 166 | scheduleTimer(1, 0); 167 | } 168 | 169 | return item; 170 | } 171 | 172 | function setTimeout( fn, ms, a, b, c ) { 173 | var args; 174 | switch (arguments.length) { 175 | case 0: case 1: case 2: break; 176 | case 3: args = [a]; break; 177 | case 4: args = [a, b]; break; 178 | case 5: args = [a, b, c]; break; 179 | default: 180 | args = new Array(arguments.length - 2); 181 | for (var i=2; i setTimeout.MAX_TIMEOUT) ms = setTimeout.MIN_TIMEOUT; 213 | item._interval = ms; 214 | 215 | // TODO: 216 | // TODO: for the timeout _callback, always save a static function that receives the item 217 | // and make _processTimeoutTasks pass the item (actually, runCallbackItem already does, as `this`) 218 | // Then no need for per-recurring timeout closure, just re-queue it when it triggers! (see _interval) 219 | // BUT this needs timeouts to be invoked with `this` set to the timer object like immediates (work in progress) 220 | // (so the callback will call the use-specified repeat func from inside runCallback) 221 | // eg: 222 | // item._callback = function rep() { this._callback = this._func; _runCallbackItem(this); this._callback = rep; 223 | // this._when = ...; _scheduleTimeoutItem(this, this._when); }; 224 | // item._func = fn; 225 | // item._argv = args; 226 | 227 | var userCallback = item._callback; 228 | item._callback = function recurringCallback() { 229 | _runCallback(userCallback, item._argv); 230 | if (item._interval) { 231 | // like node, schedule the next activation from the current time 232 | // unlike node, getTimestamp() returns the start of the timeout event loop epoch 233 | item._when = getTimestamp() + item._interval; 234 | _scheduleTimeoutItem(item, item._when); 235 | } 236 | }; 237 | _scheduleTimeoutItem(item, item._when); 238 | 239 | return item; 240 | } 241 | 242 | function clearInterval( item ) { 243 | clearTimeout(item); 244 | } 245 | 246 | 247 | // run the callback, return any error thrown 248 | function _runCallback( cb, av ) { 249 | if (av) switch (av.length) { 250 | case 0: cb(); break; 251 | case 1: cb(av[0]); break; 252 | case 2: cb(av[0], av[1]); break; 253 | case 3: cb(av[0], av[1], av[2]); break; 254 | default: cb.apply(null, av); break; 255 | } 256 | else cb(); 257 | } 258 | 259 | function _runCallbackItem( item ) { 260 | var argv = item._argv; 261 | var argc = argv ? argv.length : 0; 262 | switch (argc) { 263 | case 0: item._callback(); break; 264 | case 1: item._callback(argv[0]); break; 265 | case 2: item._callback(argv[0], argv[1]); break; 266 | case 3: item._callback(argv[0], argv[1], argv[2]); break; 267 | default: item._callback.apply(item, argv); break; 268 | } 269 | } 270 | 271 | function _tryCallback( cb, av ) { 272 | try { _runCallback(cb, av) } 273 | catch (err) { return err } 274 | } 275 | 276 | function _tryCallbackItem( item ) { 277 | try { _runCallbackItem(item); } 278 | catch (err) { return err; } 279 | } 280 | 281 | function _invokeCallbackItem( item ) { 282 | if (item._domain) if (item._domain.disposed) return; else item._domain.enter(); 283 | 284 | var err = _tryCallbackItem(item); 285 | 286 | // return on error without exiting domain, let caller re-throw in the called domain 287 | if (err) return err; 288 | 289 | if (item._domain) item._domain.exit(); 290 | } 291 | 292 | function _nextTickEmptyDomain( fn ) { 293 | var domain = process.domain; 294 | process.domain = null; 295 | process.nextTick(fn); 296 | process.domain = domain; 297 | } 298 | 299 | function _processTasklist( list ) { 300 | var refCount = 0; 301 | var err; 302 | 303 | var item, err; 304 | for (var i=0; i 0 ? limit : immediateList.length() + -limit; 336 | // FIXME: as implemented, counts list entries, not runnable tasks 337 | } 338 | 339 | /* 340 | * called as needed from setImmediate to run immediate tasks 341 | * Runs some or all waiting tasks, optionally some newly queued ones too. 342 | */ 343 | function _processImmediateTasks( limit ) { 344 | var list = immediateList, maxDepth = limit || _getImmediateLimit(); 345 | var item, err, n = 0; 346 | 347 | while (n < maxDepth && (item = list.shift())) { 348 | n++; 349 | if (!item._callback) continue; 350 | 351 | if (item._domain) if (item._domain.disposed) continue; else item._domain.enter(); 352 | 353 | var err = _tryCallbackItem(item); 354 | if (err) { 355 | // finish running the other immediate tasks if the error was not fatal 356 | if (n < maxDepth) _nextTickEmptyDomain(function(){ _processImmediateTasks(maxDepth - n); }); 357 | throw err; 358 | } 359 | 360 | if (item._domain) item._domain.exit(); 361 | } 362 | 363 | if (!item) process._needImmediateCallback = false; 364 | } 365 | 366 | 367 | // fetch the oldest list queued tasks with triggered timers 368 | function _fetchTimeoutTasklist( heap, timestamp ) { 369 | var when = heap.shift(); 370 | var whenKey = when + ''; 371 | var list = heap[whenKey]; 372 | delete heap[whenKey]; 373 | return list; 374 | } 375 | 376 | /* 377 | * called every ms by timeoutTimer via the event loop 378 | * Processes all timeouts whose trigger time has arrived. 379 | */ 380 | function _processTimeoutTasks( ) { 381 | var ts = setTimestamp(); 382 | 383 | while (timeoutHeap.peek() <= ts) { 384 | var when = timeoutHeap.shift(); 385 | var whenKey = when + ''; 386 | var list = timeoutHash[whenKey]; 387 | delete timeoutHash[whenKey]; 388 | //var list = _fetchTimeoutTasklist(timeoutHeap, ts); 389 | var err = _processTasklist(list); 390 | if (err) { 391 | // if a timeout threw, arrange to finish running the other tasks 392 | // and re-throw in the same domain as the error 393 | _nextTickEmptyDomain(_processTimeoutTasks); 394 | throw err; 395 | } 396 | ts = setTimestamp(); 397 | } 398 | 399 | scheduleTimer(timeoutHeap.peek(), ts); 400 | } 401 | 402 | 403 | // debug: 404 | module.exports._qt = { 405 | timeoutHeap: timeoutHeap, 406 | immediateList: immediateList, 407 | } 408 | --------------------------------------------------------------------------------