-
Notifications
You must be signed in to change notification settings - Fork 29.6k
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: assure setTimeout callback only runs once #1231
Conversation
This fixes an edge case where running this.unref() during the callback caused the callback to get executed multiple times.
c8489f7
to
951168f
Compare
@@ -305,6 +306,9 @@ Timeout.prototype.unref = function() { | |||
if (this._handle) { | |||
this._handle.unref(); | |||
} else if (typeof(this._onTimeout) === 'function') { | |||
// Prevent running callback multiple times | |||
// when unref() is called during the callback | |||
if (this._called) return; |
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 it's probably best to just do this before if (this._handle) {
, but I'm not 100% sure it matters.
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, I was unsure here too, but since I only wanted to prevent the _onTimeout
call, this seemed right.
Was also trying fixes for this. I assumed this should also affect intervals, but it doesn't seem to.. |
Yeah, didn't see it on intervals either. Right now, I fail to even find where |
Also, I think it needs a |
Yeah, tests did pass. Will research later. |
@@ -85,6 +85,7 @@ function listOnTimeout() { | |||
if (domain) | |||
domain.enter(); | |||
threw = true; | |||
first._called = true; |
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.
Please add this._called = false
to the constructor.
Overall I think this is a decent fix for the apparent issue at hand. Timers should be revisited in more detail later to cleanup the implementation. Minus one comment, LGTM. |
Thanks for the review, I'll update with your suggestion once I'm sure this doesn't affect I agree timers should be cleaned up or be rewritten at some point. It's very hard to follow the code flow, and there's probably a few perf gains to be had too. |
This Failing test: var assert = require('assert');
setImmediate(function () {
var count = 0;
while (count++ < 1e7) {
Math.random()
}
});
var i = setTimeout(function () {
this.unref();
setImmediate(process.exit);
}, 10);
process.on('exit', function() {
assert.strictEqual(process._getActiveHandles().length, 0);
}); Edit: updated test asset |
That test still fails with my current patch. |
@silverwind hmm, don't let it hold this patch up though. It may or may not be related. |
Ah, wait, I derped the build appararently, retesting. edit: nope, linked test still failing with a clean build, so probably unrelated. |
@Fishrock123 turns out |
Alright, I'm pretty confident that this should solve the The added PTAL @Fishrock123 @trevnorris |
Doesn't fix everything, but does it does fix known bugs. LGTM. |
LGTM please land |
Here's a CI for good measure though: https://jenkins-iojs.nodesource.com/view/iojs/job/iojs+any-pr+multi/374/ |
CI is happier than it looks. Those errors have been resolved since this pr's base. |
Calling this.unref() during the callback of SetTimeout caused the callback to get executed twice because unref() didn't expect to be called during that time and did not stop the ref()ed Timeout but did start a new timer. This commit prevents the new timer creation when the callback was already called. Fixes: #1191 Reviewed-by: Trevor Norris <trev.norris@gmail.com> Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com> PR_URL: #1231
Thanks, landed in b0f8e30 |
Calling this.unref() during the callback of SetTimeout caused the callback to get executed twice because unref() didn't expect to be called during that time and did not stop the ref()ed Timeout but did start a new timer. This commit prevents the new timer creation when the callback was already called. Fixes: #1191 Reviewed-by: Trevor Norris <trev.norris@gmail.com> Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com> PR-URL: #1231
Notable changes: * fs: corruption can be caused by fs.writeFileSync() and append-mode fs.writeFile() and fs.writeFileSync() under certain circumstances, reported in #1058, fixed in #1063 (Olov Lassus). * iojs: an "internal modules" API has been introduced to allow core code to share JavaScript modules internally only without having to expose them as a public API, this feature is for core-only #848 (Vladimir Kurchatkin). * timers: two minor problems with timers have been fixed: - Timer#close() is now properly idempotent #1288 (Petka Antonov). - setTimeout() will only run the callback once now after an unref() during the callback #1231 (Roman Reiss). * Windows: a "delay-load hook" has been added for compiled add-ons on Windows that should alleviate some of the problems that Windows users may be experiencing with add-ons in io.js #1251 (Bert Belder). * V8: minor bug-fix upgrade for V8 to 4.1.0.27. * npm: upgrade npm to 2.7.4. See npm CHANGELOG.md for details.
This fixes #1191 by adding a property to track if the callback was run, and if so, prevents it from running again during a
.unref()
.I woul've loved to do it without the property, but couldn't find any way to find out whether the callback already ran.
r=@bnoordhuis @trevnorris