7fa683eac534f86edb81f2a7c2ba1e61ebf25f71
[platform/upstream/nodejs.git] / lib / timers.js
1 // Copyright Joyent, Inc. and other Node contributors.
2 //
3 // Permission is hereby granted, free of charge, to any person obtaining a
4 // copy of this software and associated documentation files (the
5 // "Software"), to deal in the Software without restriction, including
6 // without limitation the rights to use, copy, modify, merge, publish,
7 // distribute, sublicense, and/or sell copies of the Software, and to permit
8 // persons to whom the Software is furnished to do so, subject to the
9 // following conditions:
10 //
11 // The above copyright notice and this permission notice shall be included
12 // in all copies or substantial portions of the Software.
13 //
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15 // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17 // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18 // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19 // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20 // USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22 'use strict';
23
24 var Timer = process.binding('timer_wrap').Timer;
25 var L = require('_linklist');
26 var assert = require('assert').ok;
27
28 var kOnTimeout = Timer.kOnTimeout | 0;
29
30 // Timeout values > TIMEOUT_MAX are set to 1.
31 var TIMEOUT_MAX = 2147483647; // 2^31-1
32
33 var debug = require('util').debuglog('timer');
34
35
36 // IDLE TIMEOUTS
37 //
38 // Because often many sockets will have the same idle timeout we will not
39 // use one timeout watcher per item. It is too much overhead.  Instead
40 // we'll use a single watcher for all sockets with the same timeout value
41 // and a linked list. This technique is described in the libev manual:
42 // http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod#Be_smart_about_timeouts
43
44 // Object containing all lists, timers
45 // key = time in milliseconds
46 // value = list
47 var lists = {};
48
49 // the main function - creates lists on demand and the watchers associated
50 // with them.
51 function insert(item, msecs) {
52   item._idleStart = Timer.now();
53   item._idleTimeout = msecs;
54
55   if (msecs < 0) return;
56
57   var list;
58
59   if (lists[msecs]) {
60     list = lists[msecs];
61   } else {
62     list = new Timer();
63     list.start(msecs, 0);
64
65     L.init(list);
66
67     lists[msecs] = list;
68     list.msecs = msecs;
69     list[kOnTimeout] = listOnTimeout;
70   }
71
72   L.append(list, item);
73   assert(!L.isEmpty(list)); // list is not empty
74 }
75
76 function listOnTimeout() {
77   var msecs = this.msecs;
78   var list = this;
79
80   debug('timeout callback %d', msecs);
81
82   var now = Timer.now();
83   debug('now: %s', now);
84
85   var diff, first, threw;
86   while (first = L.peek(list)) {
87     diff = now - first._idleStart;
88     if (diff < msecs) {
89       list.start(msecs - diff, 0);
90       debug('%d list wait because diff is %d', msecs, diff);
91       return;
92     } else {
93       L.remove(first);
94       assert(first !== L.peek(list));
95
96       if (!first._onTimeout) continue;
97
98       // v0.4 compatibility: if the timer callback throws and the
99       // domain or uncaughtException handler ignore the exception,
100       // other timers that expire on this tick should still run.
101       //
102       // https://github.com/joyent/node/issues/2631
103       var domain = first.domain;
104       if (domain && domain._disposed)
105         continue;
106
107       try {
108         if (domain)
109           domain.enter();
110         threw = true;
111         first._onTimeout();
112         if (domain)
113           domain.exit();
114         threw = false;
115       } finally {
116         if (threw) {
117           // We need to continue processing after domain error handling
118           // is complete, but not by using whatever domain was left over
119           // when the timeout threw its exception.
120           var oldDomain = process.domain;
121           process.domain = null;
122           process.nextTick(function() {
123             list[kOnTimeout]();
124           });
125           process.domain = oldDomain;
126         }
127       }
128     }
129   }
130
131   debug('%d list empty', msecs);
132   assert(L.isEmpty(list));
133   list.close();
134   delete lists[msecs];
135 }
136
137
138 var unenroll = exports.unenroll = function(item) {
139   L.remove(item);
140
141   var list = lists[item._idleTimeout];
142   // if empty then stop the watcher
143   debug('unenroll');
144   if (list && L.isEmpty(list)) {
145     debug('unenroll: list empty');
146     list.close();
147     delete lists[item._idleTimeout];
148   }
149   // if active is called later, then we want to make sure not to insert again
150   item._idleTimeout = -1;
151 };
152
153
154 // Does not start the time, just sets up the members needed.
155 exports.enroll = function(item, msecs) {
156   // if this item was already in a list somewhere
157   // then we should unenroll it from that
158   if (item._idleNext) unenroll(item);
159
160   // Ensure that msecs fits into signed int32
161   if (msecs > 0x7fffffff) {
162     msecs = 0x7fffffff;
163   }
164
165   item._idleTimeout = msecs;
166   L.init(item);
167 };
168
169
170 // call this whenever the item is active (not idle)
171 // it will reset its timeout.
172 exports.active = function(item) {
173   var msecs = item._idleTimeout;
174   if (msecs >= 0) {
175     var list = lists[msecs];
176     if (!list || L.isEmpty(list)) {
177       insert(item, msecs);
178     } else {
179       item._idleStart = Timer.now();
180       L.append(list, item);
181     }
182   }
183 };
184
185
186 /*
187  * DOM-style timers
188  */
189
190
191 exports.setTimeout = function(callback, after) {
192   var timer;
193
194   after *= 1; // coalesce to number or NaN
195
196   if (!(after >= 1 && after <= TIMEOUT_MAX)) {
197     after = 1; // schedule on next tick, follows browser behaviour
198   }
199
200   timer = new Timeout(after);
201
202   if (arguments.length <= 2) {
203     timer._onTimeout = callback;
204   } else {
205     /*
206      * Sometimes setTimeout is called with arguments, EG
207      *
208      *   setTimeout(callback, 2000, "hello", "world")
209      *
210      * If that's the case we need to call the callback with
211      * those args. The overhead of an extra closure is not
212      * desired in the normal case.
213      */
214     var args = Array.prototype.slice.call(arguments, 2);
215     timer._onTimeout = function() {
216       callback.apply(timer, args);
217     }
218   }
219
220   if (process.domain) timer.domain = process.domain;
221
222   exports.active(timer);
223
224   return timer;
225 };
226
227
228 exports.clearTimeout = function(timer) {
229   if (timer && (timer[kOnTimeout] || timer._onTimeout)) {
230     timer[kOnTimeout] = timer._onTimeout = null;
231     if (timer instanceof Timeout) {
232       timer.close(); // for after === 0
233     } else {
234       exports.unenroll(timer);
235     }
236   }
237 };
238
239
240 exports.setInterval = function(callback, repeat) {
241   repeat *= 1; // coalesce to number or NaN
242
243   if (!(repeat >= 1 && repeat <= TIMEOUT_MAX)) {
244     repeat = 1; // schedule on next tick, follows browser behaviour
245   }
246
247   var timer = new Timeout(repeat);
248   var args = Array.prototype.slice.call(arguments, 2);
249   timer._onTimeout = wrapper;
250   timer._repeat = true;
251
252   if (process.domain) timer.domain = process.domain;
253   exports.active(timer);
254
255   return timer;
256
257   function wrapper() {
258     callback.apply(this, args);
259     // If callback called clearInterval().
260     if (timer._repeat === false) return;
261     // If timer is unref'd (or was - it's permanently removed from the list.)
262     if (this._handle) {
263       this._handle.start(repeat, 0);
264     } else {
265       timer._idleTimeout = repeat;
266       exports.active(timer);
267     }
268   }
269 };
270
271
272 exports.clearInterval = function(timer) {
273   if (timer && timer._repeat) {
274     timer._repeat = false;
275     clearTimeout(timer);
276   }
277 };
278
279
280 var Timeout = function(after) {
281   this._idleTimeout = after;
282   this._idlePrev = this;
283   this._idleNext = this;
284   this._idleStart = null;
285   this._onTimeout = null;
286   this._repeat = false;
287 };
288
289 Timeout.prototype.unref = function() {
290   if (!this._handle) {
291     var now = Timer.now();
292     if (!this._idleStart) this._idleStart = now;
293     var delay = this._idleStart + this._idleTimeout - now;
294     if (delay < 0) delay = 0;
295     exports.unenroll(this);
296     this._handle = new Timer();
297     this._handle[kOnTimeout] = this._onTimeout;
298     this._handle.start(delay, 0);
299     this._handle.domain = this.domain;
300     this._handle.unref();
301   } else {
302     this._handle.unref();
303   }
304 };
305
306 Timeout.prototype.ref = function() {
307   if (this._handle)
308     this._handle.ref();
309 };
310
311 Timeout.prototype.close = function() {
312   this._onTimeout = null;
313   if (this._handle) {
314     this._handle[kOnTimeout] = null;
315     this._handle.close();
316   } else {
317     exports.unenroll(this);
318   }
319 };
320
321
322 var immediateQueue = {};
323 L.init(immediateQueue);
324
325
326 function processImmediate() {
327   var queue = immediateQueue;
328   var domain, immediate;
329
330   immediateQueue = {};
331   L.init(immediateQueue);
332
333   while (L.isEmpty(queue) === false) {
334     immediate = L.shift(queue);
335     domain = immediate.domain;
336
337     if (domain)
338       domain.enter();
339
340     var threw = true;
341     try {
342       immediate._onImmediate();
343       threw = false;
344     } finally {
345       if (threw) {
346         if (!L.isEmpty(queue)) {
347           // Handle any remaining on next tick, assuming we're still
348           // alive to do so.
349           while (!L.isEmpty(immediateQueue)) {
350             L.append(queue, L.shift(immediateQueue));
351           }
352           immediateQueue = queue;
353           process.nextTick(processImmediate);
354         }
355       }
356     }
357
358     if (domain)
359       domain.exit();
360   }
361
362   // Only round-trip to C++ land if we have to. Calling clearImmediate() on an
363   // immediate that's in |queue| is okay. Worst case is we make a superfluous
364   // call to NeedImmediateCallbackSetter().
365   if (L.isEmpty(immediateQueue)) {
366     process._needImmediateCallback = false;
367   }
368 }
369
370
371 function Immediate() { }
372
373 Immediate.prototype.domain = undefined;
374 Immediate.prototype._onImmediate = undefined;
375 Immediate.prototype._idleNext = undefined;
376 Immediate.prototype._idlePrev = undefined;
377
378
379 exports.setImmediate = function(callback) {
380   var immediate = new Immediate();
381   var args, index;
382
383   L.init(immediate);
384
385   immediate._onImmediate = callback;
386
387   if (arguments.length > 1) {
388     args = [];
389     for (index = 1; index < arguments.length; index++)
390       args.push(arguments[index]);
391
392     immediate._onImmediate = function() {
393       callback.apply(immediate, args);
394     };
395   }
396
397   if (!process._needImmediateCallback) {
398     process._needImmediateCallback = true;
399     process._immediateCallback = processImmediate;
400   }
401
402   if (process.domain)
403     immediate.domain = process.domain;
404
405   L.append(immediateQueue, immediate);
406
407   return immediate;
408 };
409
410
411 exports.clearImmediate = function(immediate) {
412   if (!immediate) return;
413
414   immediate._onImmediate = undefined;
415
416   L.remove(immediate);
417
418   if (L.isEmpty(immediateQueue)) {
419     process._needImmediateCallback = false;
420   }
421 };
422
423
424 // Internal APIs that need timeouts should use timers._unrefActive instead of
425 // timers.active as internal timeouts shouldn't hold the loop open
426
427 var unrefList, unrefTimer;
428
429
430 function unrefTimeout() {
431   var now = Timer.now();
432
433   debug('unrefTimer fired');
434
435   var diff, domain, first, threw;
436   while (first = L.peek(unrefList)) {
437     diff = now - first._idleStart;
438
439     if (diff < first._idleTimeout) {
440       diff = first._idleTimeout - diff;
441       unrefTimer.start(diff, 0);
442       unrefTimer.when = now + diff;
443       debug('unrefTimer rescheudling for later');
444       return;
445     }
446
447     L.remove(first);
448
449     domain = first.domain;
450
451     if (!first._onTimeout) continue;
452     if (domain && domain._disposed) continue;
453
454     try {
455       if (domain) domain.enter();
456       threw = true;
457       debug('unreftimer firing timeout');
458       first._onTimeout();
459       threw = false;
460       if (domain)
461         domain.exit();
462     } finally {
463       if (threw) process.nextTick(unrefTimeout);
464     }
465   }
466
467   debug('unrefList is empty');
468   unrefTimer.when = -1;
469 }
470
471
472 exports._unrefActive = function(item) {
473   var msecs = item._idleTimeout;
474   if (!msecs || msecs < 0) return;
475   assert(msecs >= 0);
476
477   L.remove(item);
478
479   if (!unrefList) {
480     debug('unrefList initialized');
481     unrefList = {};
482     L.init(unrefList);
483
484     debug('unrefTimer initialized');
485     unrefTimer = new Timer();
486     unrefTimer.unref();
487     unrefTimer.when = -1;
488     unrefTimer[kOnTimeout] = unrefTimeout;
489   }
490
491   var now = Timer.now();
492   item._idleStart = now;
493
494   if (L.isEmpty(unrefList)) {
495     debug('unrefList empty');
496     L.append(unrefList, item);
497
498     unrefTimer.start(msecs, 0);
499     unrefTimer.when = now + msecs;
500     debug('unrefTimer scheduled');
501     return;
502   }
503
504   var when = now + msecs;
505
506   debug('unrefList find where we can insert');
507
508   var cur, them;
509
510   for (cur = unrefList._idlePrev; cur != unrefList; cur = cur._idlePrev) {
511     them = cur._idleStart + cur._idleTimeout;
512
513     if (when < them) {
514       debug('unrefList inserting into middle of list');
515
516       L.append(cur, item);
517
518       if (unrefTimer.when > when) {
519         debug('unrefTimer is scheduled to fire too late, reschedule');
520         unrefTimer.start(msecs, 0);
521         unrefTimer.when = when;
522       }
523
524       return;
525     }
526   }
527
528   debug('unrefList append to end');
529   L.append(unrefList, item);
530 };