diff --git a/doc/api/timers.md b/doc/api/timers.md index df645c104a286d..7f3c0a3027b088 100644 --- a/doc/api/timers.md +++ b/doc/api/timers.md @@ -178,7 +178,7 @@ added: v0.0.1 --> * `callback` {Function} The function to call when the timer elapses. -* `delay` {number} The number of milliseconds to wait before calling the +* `delay` {number|bigint} The number of milliseconds to wait before calling the `callback`. * `...args` {any} Optional arguments to pass when the `callback` is called. * Returns: {Timeout} for use with [`clearInterval()`][] @@ -196,7 +196,7 @@ added: v0.0.1 --> * `callback` {Function} The function to call when the timer elapses. -* `delay` {number} The number of milliseconds to wait before calling the +* `delay` {number|bigint} The number of milliseconds to wait before calling the `callback`. * `...args` {any} Optional arguments to pass when the `callback` is called. * Returns: {Timeout} for use with [`clearTimeout()`][] diff --git a/lib/internal/timers.js b/lib/internal/timers.js index c7d9d1750e9821..9c699c2e9fcf56 100644 --- a/lib/internal/timers.js +++ b/lib/internal/timers.js @@ -54,7 +54,10 @@ function initAsyncResource(resource, type) { // Timer constructor function. // The entire prototype is defined in lib/timers.js function Timeout(callback, after, args, isRepeat) { - after *= 1; // coalesce to number or NaN + if (typeof after !== 'bigint') { // eslint-disable-line valid-typeof + after *= 1; // coalesce to number or NaN + } + if (!(after >= 1 && after <= TIMEOUT_MAX)) { if (after > TIMEOUT_MAX) { process.emitWarning(`${after} does not fit into` + @@ -65,6 +68,11 @@ function Timeout(callback, after, args, isRepeat) { after = 1; // schedule on next tick, follows browser behavior } + // at this point after is either a number or bigint and in both + // cases its value is in the limit of signed int so it is safe to + // convert after to number + after = Number(after); + this._idleTimeout = after; this._idlePrev = this; this._idleNext = this; @@ -126,11 +134,14 @@ function setUnrefTimeout(callback, after, arg1, arg2, arg3) { // Type checking used by timers.enroll() and Socket#setTimeout() function validateTimerDuration(msecs) { - if (typeof msecs !== 'number') { - throw new ERR_INVALID_ARG_TYPE('msecs', 'number', msecs); + if ((typeof msecs !== 'number') && + (typeof msecs !== 'bigint')) { // eslint-disable-line valid-typeof + throw new ERR_INVALID_ARG_TYPE('msecs', ['number', 'bigint'], msecs); } - if (msecs < 0 || !isFinite(msecs)) { + // ensure that msecs is non-negative and finite + // note: bigint is always a finite (typeof Infinity and NaN is 'number') + if (msecs < 0 || (typeof msecs === 'number' && !isFinite(msecs))) { throw new ERR_OUT_OF_RANGE('msecs', 'a non-negative finite number', msecs); } @@ -142,5 +153,5 @@ function validateTimerDuration(msecs) { return TIMEOUT_MAX; } - return msecs; + return Number(msecs); } diff --git a/test/parallel/test-http2-timeouts.js b/test/parallel/test-http2-timeouts.js index db5822776aea5e..b92b456ed378fe 100644 --- a/test/parallel/test-http2-timeouts.js +++ b/test/parallel/test-http2-timeouts.js @@ -21,7 +21,7 @@ server.on('stream', common.mustCall((stream) => { code: 'ERR_INVALID_ARG_TYPE', type: TypeError, message: - 'The "msecs" argument must be of type number. Received type string' + 'The "msecs" argument must be one of type number or bigint. Received type string' } ); common.expectsError( diff --git a/test/parallel/test-timers-max-duration-warning.js b/test/parallel/test-timers-max-duration-warning.js index c978cf29c3fe4d..f54a86c6a496c9 100644 --- a/test/parallel/test-timers-max-duration-warning.js +++ b/test/parallel/test-timers-max-duration-warning.js @@ -5,6 +5,7 @@ const assert = require('assert'); const timers = require('timers'); const OVERFLOW = Math.pow(2, 31); // TIMEOUT_MAX is 2^31-1 +const TEST_MATRIX = [OVERFLOW, BigInt(OVERFLOW)]; function timerNotCanceled() { assert.fail('Timer should be canceled'); @@ -19,24 +20,25 @@ process.on('warning', common.mustCall((warning) => { assert.strictEqual(lines[0], `${OVERFLOW} does not fit into a 32-bit signed` + ' integer.'); assert.strictEqual(lines.length, 2); -}, 5)); - - -{ - const timeout = setTimeout(timerNotCanceled, OVERFLOW); - clearTimeout(timeout); -} - -{ - const interval = setInterval(timerNotCanceled, OVERFLOW); - clearInterval(interval); -} - -{ - const timer = { - _onTimeout: timerNotCanceled - }; - timers.enroll(timer, OVERFLOW); - timers.active(timer); - timers.unenroll(timer); +}, 8)); + +for (let i = 0; i < TEST_MATRIX.length; i++) { + { + const timeout = setTimeout(timerNotCanceled, TEST_MATRIX[i]); + clearTimeout(timeout); + } + + { + const interval = setInterval(timerNotCanceled, TEST_MATRIX[i]); + clearInterval(interval); + } + + { + const timer = { + _onTimeout: timerNotCanceled + }; + timers.enroll(timer, TEST_MATRIX[i]); + timers.active(timer); + timers.unenroll(timer); + } } diff --git a/test/parallel/test-timers.js b/test/parallel/test-timers.js index 1aa5d351a4d4bd..e18ef4e4a924fb 100644 --- a/test/parallel/test-timers.js +++ b/test/parallel/test-timers.js @@ -48,7 +48,15 @@ const inputs = [ 1, 1.0, 2147483648, // browser behavior: timeouts > 2^31-1 run on next tick - 12345678901234 // ditto + 12345678901234, // ditto + BigInt(2 ** 72), // ditto + BigInt(0), + BigInt(-10), + BigInt(-1), + BigInt(true), + BigInt(false), + BigInt(1), + BigInt('') ]; const timeouts = [];