Skip to content

Commit

Permalink
timers: truncate decimal values
Browse files Browse the repository at this point in the history
Reverts some timers behavior back to as it was before
2930bd1

That commit introduced an unintended change which allowed non-integer
timeouts to actually exist since the value is no longer converted to an
integer via a TimeWrap handle directly.

Even with the fix in
e9de435
non-integer timeouts are still indeterministic, because libuv does not
support them.

This fixes the issue by emulating the old behavior:
truncate the `_idleTimeout` before using it.

See comments in
#24214
for more background on this.
  • Loading branch information
Fishrock123 committed Jan 28, 2019
1 parent 4814987 commit 10e70b6
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 4 deletions.
4 changes: 2 additions & 2 deletions doc/api/timers.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ added: v0.0.1
Schedules repeated execution of `callback` every `delay` milliseconds.

When `delay` is larger than `2147483647` or less than `1`, the `delay` will be
set to `1`.
set to `1`. Non-integer delays are truncated to an integer.

If `callback` is not a function, a [`TypeError`][] will be thrown.

Expand All @@ -209,7 +209,7 @@ nor of their ordering. The callback will be called as close as possible to the
time specified.

When `delay` is larger than `2147483647` or less than `1`, the `delay`
will be set to `1`.
will be set to `1`. Non-integer delays are truncated to an integer.

If `callback` is not a function, a [`TypeError`][] will be thrown.

Expand Down
9 changes: 7 additions & 2 deletions lib/timers.js
Original file line number Diff line number Diff line change
Expand Up @@ -193,10 +193,13 @@ exports._unrefActive = function(item) {
// Appends a timer onto the end of an existing timers list, or creates a new
// list if one does not already exist for the specified timeout duration.
function insert(item, refed, start) {
const msecs = item._idleTimeout;
let msecs = item._idleTimeout;
if (msecs < 0 || msecs === undefined)
return;

// Truncate so that accuracy of sub-milisecond timers is not assumed.
msecs = Math.trunc(msecs);

item._idleStart = start;

// Use an existing list if there is one, otherwise we need to make a new one.
Expand Down Expand Up @@ -378,7 +381,9 @@ function unenroll(item) {
// clearTimeout that makes it clear that the list should not be deleted.
// That function could then be used by http and other similar modules.
if (item[kRefed]) {
const list = lists[item._idleTimeout];
// Compliment truncation during insert().
const msecs = Math.trunc(item._idleTimeout);
const list = lists[msecs];
if (list !== undefined && L.isEmpty(list)) {
debug('unenroll: list empty');
queue.removeAt(list.priorityQueuePosition);
Expand Down
35 changes: 35 additions & 0 deletions test/parallel/test-timers-non-integer-delay.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

'use strict';
const common = require('../common');
const assert = require('assert');

/*
* This test makes sure that non-integer timer delays do not make the process
Expand All @@ -47,3 +48,37 @@ const interval = setInterval(common.mustCall(() => {
process.exit(0);
}
}, N), TIMEOUT_DELAY);

// Test non-integer delay ordering
{
const ordering = [];

setTimeout(common.mustCall(() => {
ordering.push(1);
}), 1);

setTimeout(common.mustCall(() => {
ordering.push(2);
}), 1.8);

setTimeout(common.mustCall(() => {
ordering.push(3);
}), 1.1);

setTimeout(common.mustCall(() => {
ordering.push(4);
}), 1);

setTimeout(common.mustCall(() => {
const expected = [1, 2, 3, 4];

assert.deepStrictEqual(
ordering,
expected,
`Non-integer delay ordering should be ${expected}, but got ${ordering}`
);

// 2 should always be last of these delays due to ordering guarentees by
// the implementation.
}), 2);
}

0 comments on commit 10e70b6

Please sign in to comment.