-
Notifications
You must be signed in to change notification settings - Fork 29.9k
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 not to close reused timer handle #11646
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
'use strict'; | ||
// Checks that setInterval timers keep running even when they're | ||
// unrefed within their callback. | ||
|
||
require('../common'); | ||
const assert = require('assert'); | ||
const net = require('net'); | ||
|
||
let counter1 = 0; | ||
let counter2 = 0; | ||
|
||
// Test1 checks that clearInterval works as expected for a timer | ||
// unrefed within its callback: it removes the timer and its callback | ||
// is not called anymore. Note that the only reason why this test is | ||
// robust is that: | ||
// 1. the repeated timer it creates has a delay of 1ms | ||
// 2. when this test is completed, another test starts that creates a | ||
// new repeated timer with the same delay (1ms) | ||
// 3. because of the way timers are implemented in libuv, if two | ||
// repeated timers A and B are created in that order with the same | ||
// delay, it is guaranteed that the first occurrence of timer A | ||
// will fire before the first occurrence of timer B | ||
// 4. as a result, when the timer created by Test2 fired 11 times, if | ||
// the timer created by Test1 hadn't been removed by clearInterval, | ||
// it would have fired 11 more times, and the assertion in the | ||
// process'exit event handler would fail. | ||
function Test1() { | ||
// server only for maintaining event loop | ||
const server = net.createServer().listen(0); | ||
|
||
const timer1 = setInterval(() => { | ||
timer1.unref(); | ||
if (counter1++ === 10) { | ||
clearInterval(timer1); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I intended to add the |
||
server.close(() => { | ||
Test2(); | ||
}); | ||
} | ||
}, 1); | ||
} | ||
|
||
|
||
// Test2 checks setInterval continues even if it is unrefed within | ||
// timer callback. counter2 continues to be incremented more than 11 | ||
// until server close completed. | ||
function Test2() { | ||
// server only for maintaining event loop | ||
const server = net.createServer().listen(0); | ||
|
||
const timer2 = setInterval(() => { | ||
timer2.unref(); | ||
if (counter2++ === 10) | ||
server.close(); | ||
}, 1); | ||
} | ||
|
||
process.on('exit', () => { | ||
assert.strictEqual(counter1, 11); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You should only need to do 3 intervals for either, regardless. That should be enough to prove it is functioning. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay, 10 is no meaning. Fixed to 3 intervals. |
||
}); | ||
|
||
Test1(); |
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.
Would it still work if
this.close()
was placed into both of theseif
cases? I think that may be a better and more robust patch.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.
It would work as far as I checked. I also made an additional test that has
require('timers')._unrefActive(timer1)
and found that the timer handle is not reused in that case. So it is okay.Honestly, I'm not sure whether the following two conditions are equivalent or not. I think the latter is clearly understandable.
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.
My thinking is that you only actually want to close the handle if the list was in a pool.
There are more obscure parts of the timers code imo so I don't think it is an issue.
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.
@Fishrock123
Do you mean that we want to close the TimerWrap handle only if the list is still in a pool?
Does that diff reflects the change you had in mind:
?
I would think that the
owner
property represents ownership for a specific timer handle better than the belonging of its timer list to a given timer lists pool, so I have a preference for: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 that if we check the closing condition weather the list was in a pool or not.
is better.
Otherwise, I prefer to check owner property.
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 very confused as to why what I originally suggested doesn't do the job just fine?
Keep in mind that
reuse()
is called before this if unrefing is actually re-using the handle and it already removed the list from the pool:node/lib/timers.js
Lines 274 to 293 in 75cdc89
Essentially I don't see why we'd bother to check
.owner()
if doing it in those conditionals is deterministic anyways?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.
@Fishrock123
For the reason I mentioned above:
In other words, it seems it would be more robust to check the
owner
property than rely on what seems to be an implementation detail.The solution you're suggesting also duplicates code, and doesn't include any documentation about why the underlying TimerWrap handle should be closed in some cases, and not in others.
What you're suggesting would indeed fix the problem, but it seems to me it would lead to a less robust solution that would be more difficult to understand.
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 agree this.
And it is the point that we don't have an agreement with @Fishrock123 from his comment of