• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
24const {
25  MathTrunc,
26  ObjectCreate,
27  ObjectDefineProperty,
28  SymbolDispose,
29  SymbolToPrimitive,
30} = primordials;
31
32const {
33  immediateInfo,
34  toggleImmediateRef,
35} = internalBinding('timers');
36const L = require('internal/linkedlist');
37const {
38  async_id_symbol,
39  Timeout,
40  Immediate,
41  decRefCount,
42  immediateInfoFields: {
43    kCount,
44    kRefCount,
45  },
46  kRefed,
47  kHasPrimitive,
48  getTimerDuration,
49  timerListMap,
50  timerListQueue,
51  immediateQueue,
52  active,
53  unrefActive,
54  insert,
55} = require('internal/timers');
56const {
57  promisify: { custom: customPromisify },
58  deprecate,
59} = require('internal/util');
60let debug = require('internal/util/debuglog').debuglog('timer', (fn) => {
61  debug = fn;
62});
63const { validateFunction } = require('internal/validators');
64
65let timersPromises;
66
67const {
68  destroyHooksExist,
69  // The needed emit*() functions.
70  emitDestroy,
71} = require('internal/async_hooks');
72
73// This stores all the known timer async ids to allow users to clearTimeout and
74// clearInterval using those ids, to match the spec and the rest of the web
75// platform.
76const knownTimersById = ObjectCreate(null);
77
78// Remove a timer. Cancels the timeout and resets the relevant timer properties.
79function unenroll(item) {
80  if (item._destroyed)
81    return;
82
83  item._destroyed = true;
84
85  if (item[kHasPrimitive])
86    delete knownTimersById[item[async_id_symbol]];
87
88  // Fewer checks may be possible, but these cover everything.
89  if (destroyHooksExist() && item[async_id_symbol] !== undefined)
90    emitDestroy(item[async_id_symbol]);
91
92  L.remove(item);
93
94  // We only delete refed lists because unrefed ones are incredibly likely
95  // to come from http and be recreated shortly after.
96  // TODO: Long-term this could instead be handled by creating an internal
97  // clearTimeout that makes it clear that the list should not be deleted.
98  // That function could then be used by http and other similar modules.
99  if (item[kRefed]) {
100    // Compliment truncation during insert().
101    const msecs = MathTrunc(item._idleTimeout);
102    const list = timerListMap[msecs];
103    if (list !== undefined && L.isEmpty(list)) {
104      debug('unenroll: list empty');
105      timerListQueue.removeAt(list.priorityQueuePosition);
106      delete timerListMap[list.msecs];
107    }
108
109    decRefCount();
110  }
111
112  // If active is called later, then we want to make sure not to insert again
113  item._idleTimeout = -1;
114}
115
116// Make a regular object able to act as a timer by setting some properties.
117// This function does not start the timer, see `active()`.
118// Using existing objects as timers slightly reduces object overhead.
119function enroll(item, msecs) {
120  msecs = getTimerDuration(msecs, 'msecs');
121
122  // If this item was already in a list somewhere
123  // then we should unenroll it from that
124  if (item._idleNext) unenroll(item);
125
126  L.init(item);
127  item._idleTimeout = msecs;
128}
129
130
131/**
132 * Schedules the execution of a one-time `callback`
133 * after `after` milliseconds.
134 * @param {Function} callback
135 * @param {number} [after]
136 * @param {any} [arg1]
137 * @param {any} [arg2]
138 * @param {any} [arg3]
139 * @returns {Timeout}
140 */
141function setTimeout(callback, after, arg1, arg2, arg3) {
142  validateFunction(callback, 'callback');
143
144  let i, args;
145  switch (arguments.length) {
146    // fast cases
147    case 1:
148    case 2:
149      break;
150    case 3:
151      args = [arg1];
152      break;
153    case 4:
154      args = [arg1, arg2];
155      break;
156    default:
157      args = [arg1, arg2, arg3];
158      for (i = 5; i < arguments.length; i++) {
159        // Extend array dynamically, makes .apply run much faster in v6.0.0
160        args[i - 2] = arguments[i];
161      }
162      break;
163  }
164
165  const timeout = new Timeout(callback, after, args, false, true);
166  insert(timeout, timeout._idleTimeout);
167
168  return timeout;
169}
170
171ObjectDefineProperty(setTimeout, customPromisify, {
172  __proto__: null,
173  enumerable: true,
174  get() {
175    if (!timersPromises)
176      timersPromises = require('timers/promises');
177    return timersPromises.setTimeout;
178  },
179});
180
181/**
182 * Cancels a timeout.
183 * @param {Timeout | string | number} timer
184 * @returns {void}
185 */
186function clearTimeout(timer) {
187  if (timer && timer._onTimeout) {
188    timer._onTimeout = null;
189    unenroll(timer);
190    return;
191  }
192  if (typeof timer === 'number' || typeof timer === 'string') {
193    const timerInstance = knownTimersById[timer];
194    if (timerInstance !== undefined) {
195      timerInstance._onTimeout = null;
196      unenroll(timerInstance);
197    }
198  }
199}
200
201/**
202 * Schedules repeated execution of `callback`
203 * every `repeat` milliseconds.
204 * @param {Function} callback
205 * @param {number} [repeat]
206 * @param {any} [arg1]
207 * @param {any} [arg2]
208 * @param {any} [arg3]
209 * @returns {Timeout}
210 */
211function setInterval(callback, repeat, arg1, arg2, arg3) {
212  validateFunction(callback, 'callback');
213
214  let i, args;
215  switch (arguments.length) {
216    // fast cases
217    case 1:
218    case 2:
219      break;
220    case 3:
221      args = [arg1];
222      break;
223    case 4:
224      args = [arg1, arg2];
225      break;
226    default:
227      args = [arg1, arg2, arg3];
228      for (i = 5; i < arguments.length; i++) {
229        // Extend array dynamically, makes .apply run much faster in v6.0.0
230        args[i - 2] = arguments[i];
231      }
232      break;
233  }
234
235  const timeout = new Timeout(callback, repeat, args, true, true);
236  insert(timeout, timeout._idleTimeout);
237
238  return timeout;
239}
240
241/**
242 * Cancels an interval.
243 * @param {Timeout | string | number} timer
244 * @returns {void}
245 */
246function clearInterval(timer) {
247  // clearTimeout and clearInterval can be used to clear timers created from
248  // both setTimeout and setInterval, as specified by HTML Living Standard:
249  // https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#dom-setinterval
250  clearTimeout(timer);
251}
252
253Timeout.prototype.close = function() {
254  clearTimeout(this);
255  return this;
256};
257
258Timeout.prototype[SymbolDispose] = function() {
259  clearTimeout(this);
260};
261
262/**
263 * Coerces a `Timeout` to a primitive.
264 * @returns {number}
265 */
266Timeout.prototype[SymbolToPrimitive] = function() {
267  const id = this[async_id_symbol];
268  if (!this[kHasPrimitive]) {
269    this[kHasPrimitive] = true;
270    knownTimersById[id] = this;
271  }
272  return id;
273};
274
275/**
276 * Schedules the immediate execution of `callback`
277 * after I/O events' callbacks.
278 * @param {Function} callback
279 * @param {any} [arg1]
280 * @param {any} [arg2]
281 * @param {any} [arg3]
282 * @returns {Immediate}
283 */
284function setImmediate(callback, arg1, arg2, arg3) {
285  validateFunction(callback, 'callback');
286
287  let i, args;
288  switch (arguments.length) {
289    // fast cases
290    case 1:
291      break;
292    case 2:
293      args = [arg1];
294      break;
295    case 3:
296      args = [arg1, arg2];
297      break;
298    default:
299      args = [arg1, arg2, arg3];
300      for (i = 4; i < arguments.length; i++) {
301        // Extend array dynamically, makes .apply run much faster in v6.0.0
302        args[i - 1] = arguments[i];
303      }
304      break;
305  }
306
307  return new Immediate(callback, args);
308}
309
310ObjectDefineProperty(setImmediate, customPromisify, {
311  __proto__: null,
312  enumerable: true,
313  get() {
314    if (!timersPromises)
315      timersPromises = require('timers/promises');
316    return timersPromises.setImmediate;
317  },
318});
319
320/**
321 * Cancels an immediate.
322 * @param {Immediate} immediate
323 * @returns {void}
324 */
325function clearImmediate(immediate) {
326  if (!immediate || immediate._destroyed)
327    return;
328
329  immediateInfo[kCount]--;
330  immediate._destroyed = true;
331
332  if (immediate[kRefed] && --immediateInfo[kRefCount] === 0)
333    toggleImmediateRef(false);
334  immediate[kRefed] = null;
335
336  if (destroyHooksExist() && immediate[async_id_symbol] !== undefined) {
337    emitDestroy(immediate[async_id_symbol]);
338  }
339
340  immediate._onImmediate = null;
341
342  immediateQueue.remove(immediate);
343}
344
345Immediate.prototype[SymbolDispose] = function() {
346  clearImmediate(this);
347};
348
349module.exports = {
350  setTimeout,
351  clearTimeout,
352  setImmediate,
353  clearImmediate,
354  setInterval,
355  clearInterval,
356  _unrefActive: deprecate(
357    unrefActive,
358    'timers._unrefActive() is deprecated.' +
359    ' Please use timeout.refresh() instead.',
360    'DEP0127'),
361  active: deprecate(
362    active,
363    'timers.active() is deprecated. Please use timeout.refresh() instead.',
364    'DEP0126'),
365  unenroll: deprecate(
366    unenroll,
367    'timers.unenroll() is deprecated. Please use clearTimeout instead.',
368    'DEP0096'),
369  enroll: deprecate(
370    enroll,
371    'timers.enroll() is deprecated. Please use setTimeout instead.',
372    'DEP0095'),
373};
374