-
Notifications
You must be signed in to change notification settings - Fork 29.8k
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 processing of nested same delay timers #3063
Conversation
@@ -445,3 +446,11 @@ exports.fileExists = function(pathname) { | |||
return false; | |||
} | |||
}; | |||
|
|||
exports.busyLoop = function busyLoop(time) { |
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.
Why add this for just one test?
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.
Good question. :) I'm glad to remove it until others need something similar. My only worry is removing it means people will probably write their own stuff not knowing that we have a common need for a busy loop. Your call.
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.
Yes, if you could move it to a function in the bottom of the test file that would be better for now.
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.
Edit, maybe don't pending #2232
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.
Just let me know.
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.
yeah this should go into the test itself imo
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.
If this PR is still up for consideration, I'll update this. I'm not sure where it stands so I defer to your judgement.
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.
Seems like there is an actual bug here, so yeas, please update 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.
Will do. Thanks for your help.
/cc @bnoordhuis @trevnorris ? |
Does #2232 need to land first, or does this incorporate that? Can we just port the test maybe? We should probably preserve julien's commit though. |
@misterdjules should comment. Last I talked to Julien I was asked to port this over but if there is already work to port things and this is superfluous, no big deal to me. Just let me know how I can help. |
common.busyLoop(delay); | ||
|
||
process.nextTick(function() { | ||
assert.ok(!nestedCalled); |
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'd suggest adding a comment that this assert
is done within a nextTick
callback so that it runs after A
completed, but before the next turn of libuv's event loop. Otherwise it's not clear for anyone but who wrote the code why it's done this way.
This PR fixes one issue with #2232 by basically refactoring it, so most of the changes in #2232 are superseded by this PR. However, we need |
d7d5236
to
d848924
Compare
I have made the changes @misterdjules requested and the PR is updated with those changes. Let me know if there is more to be done and thanks for your help. |
I don't see these changes in this PR, do you have some time to update this PR with these changes? Then we'll close github.com//pull/2232 in favor of this one. |
Sure thing. I am mobile right now but when I get back to the laptop, I will update this PR with those changes and resolve conflicts. |
I'll take care of this today. I didn't manage to get laptop time over the holiday. |
// | ||
// We want to make sure that newly added timer fires in the next turn of the | ||
// event loop at the earliest. So even if it's already expired now, | ||
// reschedule it to fire later. |
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'm not sure this is actually a bug. Shouldn't the timer be scheduled in setImmediate()
if you want to guarantee this?
Also, how does this not only apply to 0ms timeout timers?
setTimeout(function foo() { setTimeout(function bar() {}, 500) }, 500);
In this example, the 2nd timeout should be caught by https://github.com/nodejs/node/blob/master/lib/timers.js#L68
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'm not sure this is actually a bug.
It is indeed a grey area. However, this is how node v4.x and later behaves now. It is also how node v0.10.x and v0.12.x used to behave before nodejs/node-v0.x-archive@d38e865, which is a regression that is fixed by nodejs/node-v0.x-archive#25763.
Shouldn't the timer be scheduled in setImmediate() if you want to guarantee this?
Scheduling the timer with setImmediate would basically make it not a timer anymore, as immediates are scheduled and ran in a completely different way.
Also, how does this not only apply to 0ms timeout timers?
setTimeout(function foo() { setTimeout(function bar() {}, 500) }, 500);
The comment in this PR should be clearer and mention that it happens with non-0 delay timers if the callback of the outer timer blocks the JS thread for a duration that is greater or equal to the timer delay. So the snippet should be:
setTimeout(function foo() { setTimeout(function bar() {}, 500); codeThatBlocksFor500ms(); }, 500);
In this example, the 2nd timeout should be caught by https://github.com/nodejs/node/blob/master/lib/timers.js#L68
Except that then we'd hit the bug fixed by nodejs/node-v0.x-archive@d38e865.
That bug does not have a big impact for timers with short delays, but it can be a problem for timers with large delays that are scheduled before code that blocks for a long time, as it can make the timer fire with a delay of originalDelay + timeSpentBlockingInOutterTimer
.
Sorry, after figuring out how timers work fully for #4007 I'm not convinced this is the correct behavior, or necessary? |
Have we ran |
I think the included test isn't actually valid, so it doesn't really matter if it is flaky or not. :s |
It seemed to work with the original sources. ;) I guess that doesn't matter now. |
@whitlockjc I think you may be misunderstanding. It may work but that doesn't mean it is correct. :/ |
Is this PR just trying to make sure the callback is always Async per-se? |
Seeing as I'm not always right and I love to learn, I'd love to hear more about why it's not correct. At the time the issue was reported, the test was capable of reproducing the reported issue and when the code to fix the issue was in place, the test passed showing the issue had been addressed. Instead of just saying it's not right, why not provide a little context? Worst case someone learns something. |
Sorry, I put some reasoning into #3063 (comment) -- basically I'm not sure we guarantee a callback will actually be called at least next tick, and timers by nature timeout as soon as possible. |
I suppose users may expect it to be async. Would it also be possible to do this by always calling |
It is at least how it behaves now. Having nested timers that expire when their outer timer's callback is done fire asynchronously has the nice side effect of not starving the event loop too.
I don't think we should use setImmediate for rescheduling timers for the reason I mentioned in #3063 (comment):
Using |
// expire. | ||
// | ||
// See: https://github.com/joyent/node/issues/25607 | ||
if (now <= first._idleStart) { |
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 don't really understand how this won't be hit when there are just timers scheduled for a later date?
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.
first._idleStart
is the time at which a timer was scheduled. Since now
is updated to be the time at the start of listOnTimeout
execution, now
is always > first._idleStart
, unless first
was scheduled from within the call to listOnTimeout
.
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.
Oh duh, this is _idleStart
not _idleTimeout
, I'm sorry.
Whenever a timer is scheduled within another timer, there are a few known issues that we are fixing: * Whenever the timer being scheduled has the same timeout value as the outer timer, the newly created timer can fire on the same tick of the event loop instead of during the next tick of the event loop * Whenever a timer is added in another timer's callback, its underlying timer handle will be started with a timeout that is actually incorrect This commit consists of nodejs/node-v0.x-archive#17203 and nodejs/node-v0.x-archive#25763. Fixes: nodejs/node-v0.x-archive#9333 Fixes: nodejs/node-v0.x-archive#15447 Fixes: nodejs/node-v0.x-archive#25607 Fixes: #5426 PR-URL: #3063
For the record: #7866 is caused by this PR although it it may not be a regression in a strict sense. |
I'll take a peek. |
@bnoordhuis Thanks for the heads up, I responded in #7866. |
Not 100% if this should land in It seems like there is a non zero chance this could break production code, even if the production code itself is wrong. As Adding |
@thealphanerd This should be backported along with any fixes. I'm not sure how it could break, but the new behavior is definitely the expected one. (And inferred by docs too) |
Here's another issue about this problem: #8354 (comment) @thealphanerd lmk if you need any help backporting |
@nodejs/lts how does everyone feel about backporting this given the above information @Fishrock123 is this commit dependent on your other timers changes? |
I don't think so? |
@Fishrock123 will need help with the backport if you can |
This is a port of https://github.com/nodejs/nodejs/node-v0.x-archive/pull/17203 and nodejs/node-v0.x-archive#25763 which fixes nodejs/node-v0.x-archive#9333, nodejs/node-v0.x-archive#15447, nodejs/node-v0.x-archive#25607 and #5426.
/cc @Fishrock123, @misterdjules, @nodejs/collaborators and @nodejs/tsc