-
Notifications
You must be signed in to change notification settings - Fork 30.4k
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
Closed
Closed
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
'use strict'; | ||
|
||
/* | ||
* This is a regression test for https://github.com/joyent/node/issues/15447 | ||
* and https://github.com/joyent/node/issues/9333. | ||
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. more links still 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 didn't think to fix links in other people's code. :P |
||
* | ||
* When a timer is added in another timer's callback, its underlying timer | ||
* handle was started with a timeout that was actually incorrect. | ||
* | ||
* The reason was that the value that represents the current time was not | ||
* updated between the time the original callback was called and the time | ||
* the added timer was processed by timers.listOnTimeout. That lead the | ||
* logic in timers.listOnTimeout to do an incorrect computation that made | ||
* the added timer fire with a timeout of scheduledTimeout + | ||
* timeSpentInCallback. | ||
* | ||
* This test makes sure that a timer added by another timer's callback | ||
* fire with the expected timeout. | ||
* | ||
* It makes sure that it works when the timers list for a given timeout is | ||
* empty (see testAddingTimerToEmptyTimersList) and when the timers list | ||
* is not empty (see testAddingTimerToNonEmptyTimersList). | ||
*/ | ||
|
||
const assert = require('assert'); | ||
const common = require('../common'); | ||
const Timer = process.binding('timer_wrap').Timer; | ||
|
||
const TIMEOUT = 100; | ||
|
||
var nbBlockingCallbackCalls = 0; | ||
var latestDelay = 0; | ||
var timeCallbackScheduled = 0; | ||
|
||
function initTest() { | ||
nbBlockingCallbackCalls = 0; | ||
latestDelay = 0; | ||
timeCallbackScheduled = 0; | ||
} | ||
|
||
function blockingCallback(callback) { | ||
++nbBlockingCallbackCalls; | ||
|
||
if (nbBlockingCallbackCalls > 1) { | ||
latestDelay = Timer.now() - timeCallbackScheduled; | ||
// Even if timers can fire later than when they've been scheduled | ||
// to fire, they should more than 50% later with a timeout of | ||
// 100ms. Firing later than that would mean that we hit the regression | ||
// highlighted in | ||
// https://github.com/nodejs/node-v0.x-archive/issues/15447 and | ||
// https://github.com/nodejs/node-v0.x-archive/issues/9333.. | ||
assert(latestDelay < TIMEOUT * 1.5); | ||
if (callback) | ||
return callback(); | ||
} else { | ||
// block by busy-looping to trigger the issue | ||
common.busyLoop(TIMEOUT); | ||
|
||
timeCallbackScheduled = Timer.now(); | ||
setTimeout(blockingCallback, TIMEOUT); | ||
} | ||
} | ||
|
||
function testAddingTimerToEmptyTimersList(callback) { | ||
initTest(); | ||
// Call setTimeout just once to make sure the timers list is | ||
// empty when blockingCallback is called. | ||
setTimeout(blockingCallback.bind(null, callback), TIMEOUT); | ||
} | ||
|
||
function testAddingTimerToNonEmptyTimersList() { | ||
initTest(); | ||
// Call setTimeout twice with the same timeout to make | ||
// sure the timers list is not empty when blockingCallback is called. | ||
setTimeout(blockingCallback, TIMEOUT); | ||
setTimeout(blockingCallback, TIMEOUT); | ||
} | ||
|
||
// Run the test for the empty timers list case, and then for the non-empty | ||
// timers list one | ||
testAddingTimerToEmptyTimersList(testAddingTimerToNonEmptyTimersList); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
'use strict'; | ||
|
||
const assert = require('assert'); | ||
const common = require('../common'); | ||
|
||
// Make sure we test 0ms timers, since they would had always wanted to run on | ||
// the current tick, and greater than 0ms timers, for scenarios where the | ||
// outer timer takes longer to complete than the delay of the nested timer. | ||
// Since the process of recreating this is identical regardless of the timer | ||
// delay, these scenarios are in one test. | ||
const scenarios = [0, 100]; | ||
|
||
scenarios.forEach(function(delay) { | ||
var nestedCalled = false; | ||
|
||
setTimeout(function A() { | ||
// Create the nested timer with the same delay as the outer timer so that it | ||
// gets added to the current list of timers being processed by | ||
// listOnTimeout. | ||
setTimeout(function B() { | ||
nestedCalled = true; | ||
}, delay); | ||
|
||
// Busy loop for the same timeout used for the nested timer to ensure that | ||
// we are in fact expiring the nested timer. | ||
common.busyLoop(delay); | ||
|
||
// The purpose of running this assert in nextTick is to make sure it runs | ||
// after A but before the next iteration of the libuv event loop. | ||
process.nextTick(function() { | ||
assert.ok(!nestedCalled); | ||
}); | ||
|
||
// Ensure that the nested callback is indeed called prior to process exit. | ||
process.on('exit', function onExit() { | ||
assert.ok(nestedCalled); | ||
}); | ||
}, delay); | ||
}); |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back 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.
@whitlockjc Did you still want to put some of those comments here?
I'd prefer if we tried to make them a bit more concise.. but if that's not possible, it's not a huge deal
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 thought about it but since this is now that the code fixing this issue is not on its own, it didn't seem like a decent place to do it. But we could mention the issue(s) where this block is addressing bugs.
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've thought about this a little more and now that the current PR code just makes timers work properly without any explicit handling for specific bugs, it would be hard to throw in extra comments without it becoming confusing (in my opinion). I defer to your judgement but that's my opinion.
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.
A little comment on what is going on here would be preferred then