-
Notifications
You must be signed in to change notification settings - Fork 29.7k
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
Conversation
/cc @nodejs/timers |
lib/timers.js
Outdated
list.expiry = timer._idleStart + msecs; | ||
if (list.expiry <= prev) | ||
list.expiry++; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
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,
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.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you 🙏
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. |
@benjamingr Thank you very much for the comment, I really appreciate it! |
Uh, floating point / decimal numbers should be rounded to an integer IIRC. Are you saying that no longer happens on Node 11? |
@Fishrock123 Line 228 in ab4af08
https://github.com/nodejs/node/blob/v10.13.0/lib/timers.js#L228 |
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. |
There was a problem hiding this 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; |
There was a problem hiding this comment.
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);
There was a problem hiding this comment.
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 😄
I agree with @Fishrock123 that converting to integer _when would probably instead be |
@TimothyGu Thanks for the comment! Also, it is for fixing the bug, I think it is better to keep the current handling. |
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/ |
I rebased onto the latest master. @apapirovski If I need to change the logic on this PR, please let me know 😄 |
8a4aff1
to
f0e35ff
Compare
Fix the timer logic to be the same as v10.30.0. Fixes: nodejs#24203
@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.... |
@Trott Thanks for the update! |
Resume Build CI: https://ci.nodejs.org/job/node-test-pull-request/19104/ |
There was a problem hiding this 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
Resume Build again: https://ci.nodejs.org/job/node-test-pull-request/19113/ |
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/ |
Fix the timer logic to be the same as v10.30.0. Fixes: nodejs#24203 PR-URL: nodejs#24214 Reviewed-By: Benjamin Gruenbaum <[email protected]> Reviewed-By: Gus Caplan <[email protected]> Reviewed-By: Anatoli Papirovski <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Jeremiah Senkpiel <[email protected]>
Landed in e9de435. Thanks for the contribution! 🎉 |
@Trott Thank you very much! 😄 |
Fix the timer logic to be the same as v10.30.0. Fixes: #24203 PR-URL: #24214 Reviewed-By: Benjamin Gruenbaum <[email protected]> Reviewed-By: Gus Caplan <[email protected]> Reviewed-By: Anatoli Papirovski <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Jeremiah Senkpiel <[email protected]>
Fix the timer logic to be the same as v10.30.0. Fixes: nodejs#24203 PR-URL: nodejs#24214 Reviewed-By: Benjamin Gruenbaum <[email protected]> Reviewed-By: Gus Caplan <[email protected]> Reviewed-By: Anatoli Papirovski <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Jeremiah Senkpiel <[email protected]>
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.
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 <[email protected]> Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Ruben Bridgewater <[email protected]> Reviewed-By: Trivikram Kamat <[email protected]> Reviewed-By: James M Snell <[email protected]>
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 <[email protected]> Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Ruben Bridgewater <[email protected]> Reviewed-By: Trivikram Kamat <[email protected]> Reviewed-By: James M Snell <[email protected]>
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-L243Fixes: #24203
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passes