Skip to content
This repository has been archived by the owner on Apr 22, 2023. It is now read-only.

timers: fix timeout when added in timer's callback #17203

Closed
wants to merge 1 commit into from

Conversation

misterdjules
Copy link

When a timer is added in another timer's callback, its underlying timer
handle will be started with a timeout that is actually incorrect.

The reason is that the value that represents the current time is not
updated between the time the original callback is called and the time
the added timer is processed by timers.listOnTimeout. That leads the
logic in timers.listOnTimeout to do an incorrect computation that makes
the added timer fire with a timeout of scheduledTimeout +
timeSpentInCallback.

This change fixes that and make timers scheduled within other timers'
callbacks fire as expected.

Fixes #9333 and #15447.

@misterdjules
Copy link
Author

/cc @ilc-opensource, @obastemur, @hankduan, @joyent/node-coreteam.

var first;
while (first = L.peek(list)) {
var now = Timer.now();
Copy link
Member

Choose a reason for hiding this comment

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

Maybe we should do it only if the list was changed? I believe this will come with a performance loss for a many-timer case.

Copy link
Author

Choose a reason for hiding this comment

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

Good point, I added a _addedTimer property on timers lists to determine when a timer has been added to the list. There are other ways to do that, but this one seemed to be the more lightweight and less intrusive.

@misterdjules
Copy link
Author

@indutny @trevnorris I updated the PR with your review comments. I also took the opportunity to slightly refactor the included test:

  • busyLoop is now in test/common.js.
  • More importantly, the test make sure that it covers both cases of the timers list being empty and non-empty when the timer is added after a long-running timer callback.

Please let me know what you think. Thank you!

@indutny
Copy link
Member

indutny commented Apr 21, 2015

LGTM! Thank you!

@obastemur
Copy link

@misterdjules I don't think this is a better way in terms of performance. This implementation calls timer.now twice instead of one.

@trevnorris
Copy link

@obastemur I believe this is a bug fix. If it's comparable in performance then that's fine.

@indutny
Copy link
Member

indutny commented Apr 22, 2015

@obastemur I think it calls it only once...

@obastemur
Copy link

@trevnorris I was comparing this to original suggested fix. This is a different implementation since it tries to call timer.now less. IMHO timer.now doesn't cost anything serious. I had made run a perf. test on a range of devices. The most scary one takes 0.001 ms on a mips32r2 board which is still nothing. Yet, this implementation calls at least +1. If there is only one timeout. It calls twice.

@indutny it calls twice. First calls before the loop and second a timeout needs to be active to be there right?

@indutny
Copy link
Member

indutny commented Apr 22, 2015

@obastemur it should not invoke now() unless new timer is added to the same bucket.

@obastemur
Copy link

@indutny I don't think it's very hard to test and see a basic setTimeout case.

@indutny
Copy link
Member

indutny commented Apr 22, 2015

@obastemur could you please share a test case with us?

@obastemur
Copy link

setTimeout(function(){ console.log("Hello!");}, 1);

@indutny
Copy link
Member

indutny commented Apr 22, 2015

@obastemur

NODE_DEBUG=timer ./node 1
timeout callback 1
now: %d
54512505
Hello!
1 list empty

As you can see debug('now %d') is invoked only once.

@obastemur
Copy link

@indutny My bad. After you insisting it won't. I've checked it again. Seems like the result was coming from the original fix. Somehow https://github.com/misterdjules/node/tree/8b00d748aee6c1cc3f06faea51339b74b4763d8c didn't build on the test machine and results mixed.

This one calls at least 25% less (if system wasn't busy)

Sorry for the confusion.

@indutny
Copy link
Member

indutny commented Apr 22, 2015

No problem at all, @obastemur . I have seen this too, before @misterdjules fixed the PR.

@obastemur
Copy link

a minor detail. I've applied this patch to JXcore to run the tests on our test environment. After this fix, test/simple/test-timers-ordering is no longer a reliable test case. It can be very hard to reproduce on a single run but I'm running the test cases on multiple devices at once and it fails as expected. (bsd, osx, debian on mips..) This doesn't mean the fix is wrong though. As expected, if the system is busy, multiple timeouts may end-up inside the same cycle which this test case fails to measure.

obastemur added a commit to jxcore/jxcore that referenced this pull request Apr 22, 2015
@misterdjules
Copy link
Author

Thank you @indutny @obastemur and @trevnorris for your feedback!

Ran the tests suite and found no regression on Windows and on UNIX.

@misterdjules misterdjules added this to the 0.10.39 milestone Apr 22, 2015
@misterdjules
Copy link
Author

@joyent/node-coreteam Added to the 0.10.39 milestone as we may want to release that with the next release.

@misterdjules
Copy link
Author

@obastemur @trevnorris @indutny As I was reviewing this change one last time before landing it, I thought that its implementation could and should be simpler. This resulted in misterdjules@52c35bd that builds on top of the previous commit in the same PR.

If that looks better to you too, I'll squash both commits and land the resulting new commit.

Thank you!

@misterdjules
Copy link
Author

@indutny ping, did you have some time to read my latest comment?

@misterdjules
Copy link
Author

/cc @joyent/node-coreteam

@@ -83,10 +83,19 @@ function listOnTimeout() {
debug('timeout callback ' + msecs);

var now = Timer.now();
debug('now: ' + now);
debug('now: %d', now);

Choose a reason for hiding this comment

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

debug('now: %d', now); Is using %d appropriate? or should we use %s, now can also have double value right?

Copy link
Author

Choose a reason for hiding this comment

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

@saquibkhan now is a non negative 64 bits integer number that is stored as a double if its value is larger than a V8 SMI. Plus the %d format supports non-integer numbers since it uses the function Number to convert the parameter corresponding to this format to a number.

@indutny
Copy link
Member

indutny commented May 8, 2015

LGTM

@bootstraponline
Copy link

👍

@misterdjules
Copy link
Author

Still no regression on UNIX or Windows, landing.

When a timer is added in another timer's callback, its underlying timer
handle will be started with a timeout that is actually incorrect.

The reason is that  the value that represents the current time is not
updated between the time the original callback is called and the time
the added timer is processed by timers.listOnTimeout. That leads the
logic in timers.listOnTimeout to do an incorrect computation that makes
the added timer fire with a timeout of scheduledTimeout +
timeSpentInCallback.

This change fixes that and make timers scheduled within other timers'
callbacks fire as expected.

Fixes nodejs#9333 and nodejs#15447.
@misterdjules
Copy link
Author

@joyent/node-collaborators Squashed, finally landing in v0.10.

misterdjules pushed a commit to misterdjules/node that referenced this pull request Jun 17, 2015
When a timer is added in another timer's callback, its underlying timer
handle will be started with a timeout that is actually incorrect.

The reason is that  the value that represents the current time is not
updated between the time the original callback is called and the time
the added timer is processed by timers.listOnTimeout. That leads the
logic in timers.listOnTimeout to do an incorrect computation that makes
the added timer fire with a timeout of scheduledTimeout +
timeSpentInCallback.

This change fixes that and make timers scheduled within other timers'
callbacks fire as expected.

Fixes nodejs#9333 and nodejs#15447.

PR: nodejs#17203
PR-URL: nodejs#17203
Reviewed-By: Fedor Indutny <[email protected]>
@misterdjules
Copy link
Author

@joyent/node-collaborators Landed in d38e865, thank you @obastemur, @indutny and @trevnorris!

Fishrock123 pushed a commit to Fishrock123/node that referenced this pull request Jul 24, 2015
When a timer is added in another timer's callback, its underlying timer
handle will be started with a timeout that is actually incorrect.

The reason is that  the value that represents the current time is not
updated between the time the original callback is called and the time
the added timer is processed by timers.listOnTimeout. That leads the
logic in timers.listOnTimeout to do an incorrect computation that makes
the added timer fire with a timeout of scheduledTimeout +
timeSpentInCallback.

This change fixes that and make timers scheduled within other timers'
callbacks fire as expected.

Fixes: nodejs/node-v0.x-archive#9333
Fixes: nodejs/node-v0.x-archive#15447

PR: nodejs/node-v0.x-archive#17203
PR-URL: nodejs/node-v0.x-archive#17203
Reviewed-By: Fedor Indutny <[email protected]>

Conflicts:
	lib/timers.js
	test/common.js
whitlockjc added a commit to whitlockjc/node that referenced this pull request Jul 15, 2016
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: nodejs#5426
PR-URL: nodejs#3063
whitlockjc added a commit to nodejs/node that referenced this pull request Jul 15, 2016
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
evanlucas pushed a commit to nodejs/node that referenced this pull request Jul 19, 2016
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
evanlucas pushed a commit to nodejs/node that referenced this pull request Jul 20, 2016
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
jBarz pushed a commit to ibmruntimes/node that referenced this pull request Nov 4, 2016
When a timer is added in another timer's callback, its underlying timer
handle will be started with a timeout that is actually incorrect.

The reason is that  the value that represents the current time is not
updated between the time the original callback is called and the time
the added timer is processed by timers.listOnTimeout. That leads the
logic in timers.listOnTimeout to do an incorrect computation that makes
the added timer fire with a timeout of scheduledTimeout +
timeSpentInCallback.

This change fixes that and make timers scheduled within other timers'
callbacks fire as expected.

Fixes nodejs#9333 and nodejs#15447.

PR: nodejs#17203
PR-URL: nodejs#17203
Reviewed-By: Fedor Indutny <[email protected]>
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants