Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

timers: fix setTimeout expiration logic #24214

Closed
wants to merge 1 commit into from

Conversation

suguru03
Copy link
Contributor

@suguru03 suguru03 commented Nov 7, 2018

I fixed the setTimeout issue.
I changed the logic to be the same logic as v10.30.0 following the lines. https://github.com/nodejs/node/blob/v10.13.0/lib/timers.js#L240-L243

Fixes: #24203

Checklist
  • make -j4 test (UNIX), or vcbuild test (Windows) passes
  • tests and/or benchmarks are included
  • commit message follows commit guidelines

@addaleax addaleax added the timers Issues and PRs related to the timers subsystem / setImmediate, setInterval, setTimeout. label Nov 7, 2018
@addaleax
Copy link
Member

addaleax commented Nov 7, 2018

/cc @nodejs/timers

@addaleax
Copy link
Member

addaleax commented Nov 7, 2018

lib/timers.js Outdated
list.expiry = timer._idleStart + msecs;
if (list.expiry <= prev)
list.expiry++;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please add a comment here explaining this is done to avoid floating point rounding errors? It's clear in the context of this PR but less so when seen in isolation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, but it is a little difficult to explain just a few lines for me.. I would like you to help it.

node/lib/timers.js

Lines 206 to 207 in af6d262

const expiry = start + msecs;
lists[msecs] = list = new TimersList(expiry, msecs);

const msecs = 1.00000000000001;
console.log(126 + msecs); // 127.00000000000001
console.log(127 + msecs); // 128

In this case, when start is turning to 127, floating point precison is changed. So it will be integer.

Then,

node/lib/timers.js

Lines 259 to 263 in af6d262

while (list = queue.peek()) {
if (list.expiry > now) {
nextExpiry = list.expiry;
return refCount > 0 ? nextExpiry : -nextExpiry;
}

when now is 128, it will be false.

After that, listOnTimeout will be called and check the differences here.

node/lib/timers.js

Lines 285 to 288 in af6d262

if (diff < msecs) {
list.expiry = timer._idleStart + msecs;
list.id = timerListId++;
queue.percolateDown(1);

In this time, the diff will be 1 but msecs is 1.00000000000001.
So the if state will be true and queue.percolateDown(1) will be called.
Finally, the list will be peeked and goes into an infinite loop .

I could change list.expiry > now to list.expiry >= now, but I'm afraid it may break something else.

Copy link
Member

@benjamingr benjamingr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you 🙏

@benjamingr
Copy link
Member

Also @suguru03 nice seeing you around, thanks for the patch and hope you stick around and collaborate some more :)

If you have any questions about the collaboration process please let us know and we'll do our best to answer them.

@suguru03
Copy link
Contributor Author

suguru03 commented Nov 7, 2018

@benjamingr Thank you very much for the comment, I really appreciate it!
I will contribute more if there is any chance. 😄

@Fishrock123
Copy link
Contributor

Uh, floating point / decimal numbers should be rounded to an integer IIRC.

Are you saying that no longer happens on Node 11?

@Fishrock123 Fishrock123 self-requested a review November 7, 2018 20:37
@suguru03
Copy link
Contributor Author

suguru03 commented Nov 7, 2018

@Fishrock123
I also checked this line on Node 10, but it seems not to be rounded.

const msecs = list.msecs;

https://github.com/nodejs/node/blob/v10.13.0/lib/timers.js#L228

@apapirovski
Copy link
Member

apapirovski commented Nov 9, 2018

Uh, floating point / decimal numbers should be rounded to an integer IIRC.

They're not. They probably should be. That's likely a better fix for this issue. (Previously this only really worked because of implicit conversion when creating handles.)

Edit: not sure it's actually a better fix after thinking about it.

Copy link
Member

@apapirovski apapirovski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small change that I think makes this easier to understand but other than that LGTM. Thank you for fixing!

lib/timers.js Outdated
@@ -283,7 +283,10 @@ function listOnTimeout(list, now) {
// Check if this loop iteration is too early for the next timer.
// This happens if there are more timers scheduled for later in the list.
if (diff < msecs) {
const prev = list.expiry;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can all be replaced by list.expiry = Math.max(timer._idleStart + msecs, now + 1);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review!
I'll fix it 😄

@TimothyGu
Copy link
Member

TimothyGu commented Nov 9, 2018

I agree with @Fishrock123 that converting to integer _when setTimeout() is called would be a better fix. It would also agree with browsers. Specifically,

https://github.com/nodejs/node/blob/f6703a007d921a6bb5cda7325a6a0228453500c8/lib/internal/timers.js#L57

would probably instead be after |= 0 after = Math.trunc(after).

@suguru03
Copy link
Contributor Author

@TimothyGu Thanks for the comment!
I agree to convert it to an integer there.
But I'm not sure if I should use Math.trunc, because the current behaviour is like Math.ceil.

Also, it is for fixing the bug, I think it is better to keep the current handling.
If I need to change it on this PR, I will do it. I will follow the members' decision. 🙂

@Trott
Copy link
Member

Trott commented Nov 14, 2018

So, uh, should this land as-is to fix the bug and then someone can submit a PR to align more with browsers, truncate/floor instead of ceil, etc.? Or should those fixes happen here and now?

My opinion: This has been quiet for 4 days, and it would be nice to get the bug fixed. So I'm inclined to land this or to suggest that anyone who thinks it should be different should get in there and change it and push a second commit to this branch. Thoughts?

In the meantime, CI: https://ci.nodejs.org/job/node-test-pull-request/18608/

@suguru03
Copy link
Contributor Author

I rebased onto the latest master.

@apapirovski If I need to change the logic on this PR, please let me know 😄

@suguru03 suguru03 force-pushed the fix-timer branch 2 times, most recently from 8a4aff1 to f0e35ff Compare November 16, 2018 21:05
Fix the timer logic to be the same as v10.30.0.

Fixes: nodejs#24203
@apapirovski
Copy link
Member

@Trott this should just land as is. I'll land later today if no one else does.

@Trott
Copy link
Member

Trott commented Nov 25, 2018

@Trott this should just land as is. I'll land later today if no one else does.

Unfortunately, CI is locked down right now and changes have been force-pushed since the last CI run, so this will have to wait until the release on Tuesday....

@suguru03
Copy link
Contributor Author

@Trott Thanks for the update!
If I need to do something, please let me know. 🙂

@Trott
Copy link
Member

Trott commented Nov 30, 2018

@Trott
Copy link
Member

Trott commented Dec 1, 2018

Copy link
Contributor

@Fishrock123 Fishrock123 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ll follow up separately with the other ideal changes

@Trott
Copy link
Member

Trott commented Dec 1, 2018

@Trott
Copy link
Member

Trott commented Dec 1, 2018

I've taken the problematic ubuntu1804-64 host offline so hopefully we get a non-problematic one this time.

Resume Build CI: https://ci.nodejs.org/job/node-test-pull-request/19119/

Trott pushed a commit to Trott/io.js that referenced this pull request Dec 1, 2018
Fix the timer logic to be the same as v10.30.0.

Fixes: nodejs#24203

PR-URL: nodejs#24214
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Gus Caplan <me@gus.host>
Reviewed-By: Anatoli Papirovski <apapirovski@mac.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com>
@Trott
Copy link
Member

Trott commented Dec 1, 2018

Landed in e9de435.

Thanks for the contribution! 🎉

@Trott Trott closed this Dec 1, 2018
@suguru03
Copy link
Contributor Author

suguru03 commented Dec 2, 2018

@Trott Thank you very much! 😄

@suguru03 suguru03 deleted the fix-timer branch December 2, 2018 02:15
@Fishrock123 Fishrock123 mentioned this pull request Dec 4, 2018
4 tasks
BridgeAR pushed a commit that referenced this pull request Dec 5, 2018
Fix the timer logic to be the same as v10.30.0.

Fixes: #24203

PR-URL: #24214
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Gus Caplan <me@gus.host>
Reviewed-By: Anatoli Papirovski <apapirovski@mac.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com>
@BridgeAR BridgeAR mentioned this pull request Dec 5, 2018
4 tasks
refack pushed a commit to refack/node that referenced this pull request Jan 14, 2019
Fix the timer logic to be the same as v10.30.0.

Fixes: nodejs#24203

PR-URL: nodejs#24214
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Gus Caplan <me@gus.host>
Reviewed-By: Anatoli Papirovski <apapirovski@mac.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com>
Fishrock123 added a commit to Fishrock123/node that referenced this pull request Jan 28, 2019
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
nodejs#24214
for more background on this.
Fishrock123 added a commit that referenced this pull request Jan 29, 2019
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.

PR-URL: #24819
Reviewed-By: Anatoli Papirovski <apapirovski@mac.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
targos pushed a commit that referenced this pull request Jan 29, 2019
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.

PR-URL: #24819
Reviewed-By: Anatoli Papirovski <apapirovski@mac.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
author ready PRs that have at least one approval, no pending requests for changes, and a CI started. timers Issues and PRs related to the timers subsystem / setImmediate, setInterval, setTimeout.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

setTimeout is not working properly with non-integer in Node 11
10 participants