Skip to content
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 regression with clearImmediate() #9086

Closed

Conversation

mscdex
Copy link
Contributor

@mscdex mscdex commented Oct 13, 2016

Checklist
  • make -j8 test (UNIX), or vcbuild test nosign (Windows) passes
  • tests and/or benchmarks are included
  • commit message follows commit guidelines
Affected core subsystem(s)
  • timers
Description of change

This commit fixes a regression introduced in 0ed8839 that caused additional queued immediate callbacks to be ignored if clearImmediate(immediate) was called within the callback for immediate.

Fixes: #9084

CI: https://ci.nodejs.org/job/node-test-pull-request/4513/
CITGM: https://ci.nodejs.org/view/Node.js-citgm/job/citgm-smoker/415/

@mscdex mscdex added the timers Issues and PRs related to the timers subsystem / setImmediate, setInterval, setTimeout. label Oct 13, 2016
@mscdex mscdex force-pushed the timers-fix-setImmediate-regression branch from f1481ac to 2f4bc27 Compare October 13, 2016 22:08
@Trott
Copy link
Member

Trott commented Oct 13, 2016

I think the test can be sped up and simplified like this:

'use strict';
require('../common');
const assert = require('assert');

const jobs = 5;
var count = 0;

for (var i = 0; i < jobs; i++) {
  const timeout = setTimeout(() => {
    clearTimeout(timeout);

    (function retry() {
      var immediate = setImmediate(() => {
        clearImmediate(immediate);

        ++count;
      });
    }());
  }, 0);
}

process.on('exit', () => {
  assert.strictEqual(count, jobs);
});

This fails reliably for me with v6.8.0 and passes reliably with v6.7.0.

@mscdex mscdex force-pushed the timers-fix-setImmediate-regression branch from 2f4bc27 to 8d0d489 Compare October 13, 2016 22:43
@mscdex
Copy link
Contributor Author

mscdex commented Oct 13, 2016

I've reduced the test to something even simpler, as it really has nothing to do with setTimeout()/clearTimeout().

const assert = require('assert');

var count = 0;
const timestamp = Date.now();
Copy link
Member

Choose a reason for hiding this comment

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

This constant appears to be unused.

Copy link
Member

Choose a reason for hiding this comment

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

(And before anybody asks, I just checked, and yes, the linter catches it.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Too late, I already removed it ;-)

@mscdex mscdex force-pushed the timers-fix-setImmediate-regression branch from 8d0d489 to 46f9d1f Compare October 13, 2016 22:45
@Trott
Copy link
Member

Trott commented Oct 13, 2016

The removal of setTimeout() entirely also seems to resolve some non-determinancy I was seeing where the test would usually fail with n of 1, but every once in a while, n would be 2. 🎉

Copy link
Member

@Trott Trott left a comment

Choose a reason for hiding this comment

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

LGTM if there are no surprises lurking in CI.

Since this is a fix of a bug introduced in a change that brought a performance improvement, is it at all worthwhile to run the same benchmark that was run on the original change just to make sure there's no surprises there? (This change seems to me to be unlikely to cause a performance regression, but what do I know?)

next();

process.on('exit', () => {
assert.strictEqual(count, 3);
Copy link
Contributor

Choose a reason for hiding this comment

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

a failure description (3rd argument) would be nice

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added.

@@ -696,6 +705,7 @@ function createImmediate(args, callback) {


exports.clearImmediate = function(immediate) {
// TODO: also verify `immediate instanceof Immediate`?
Copy link
Contributor

Choose a reason for hiding this comment

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

the linkedlist remove should already be guarding against problems, seems unnecessary

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It only checks property names, which I think could be problematic if someone passes in something that isn't a value returned by setImmediate()? I've removed the comment for now anyway...

Copy link
Contributor

Choose a reason for hiding this comment

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

hmmm you might be right, probably not for this pr tho

@@ -575,12 +575,21 @@ function processImmediate() {
domain.enter();

immediate._callback = immediate._onImmediate;

// Save next in case `clearImmediate(immediate)` is called from callback
var next = immediate._idleNext;
Copy link
Contributor

Choose a reason for hiding this comment

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

const?

Copy link
Contributor Author

@mscdex mscdex Oct 13, 2016

Choose a reason for hiding this comment

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

I kept with var because it is still used throughout timers.js.

});
}
for (var i = 0; i < 3; ++i)
next();
Copy link
Contributor

Choose a reason for hiding this comment

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

wasn't the original test recursive? should this still be?

Copy link
Contributor Author

@mscdex mscdex Oct 13, 2016

Choose a reason for hiding this comment

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

It wasn't really recursive, just "restarting" an immediate timer. However, that's not necessary to reproduce the original issue. The issue just has to do with calling clearImmediate() from within an immediate callback when there are other items in the immediate queue.

@mscdex
Copy link
Contributor Author

mscdex commented Oct 13, 2016

@Trott I already checked, there is no performance regression with this fix.

@mscdex mscdex force-pushed the timers-fix-setImmediate-regression branch from 46f9d1f to 29a03ae Compare October 13, 2016 23:04
This commit fixes a regression introduced in 0ed8839 that caused
additional queued immediate callbacks to be ignored if
`clearImmediate(immediate)` was called within the callback for
`immediate`.

Fixes: nodejs#9084
@mscdex mscdex force-pushed the timers-fix-setImmediate-regression branch from 29a03ae to 6750abf Compare October 13, 2016 23:04
next();

process.on('exit', () => {
assert.strictEqual(count, N, `Expected ${N} immediate callback executions`);
Copy link
Member

Choose a reason for hiding this comment

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

Nit: Maybe even go a bit further?

'use strict';
const common = require('../common');

function next() {
  const immediate = setImmediate(common.mustCall(function callback() {
    clearImmediate(immediate);
  }));
}
for (var i = 0; i < 3; ++i)
  next();

One benefit is that we don't lose the information of what count was if the assertion fires.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

One benefit is that we don't lose the information of what count was if the assertion fires.

I don't understand what you mean here.

Copy link
Member

Choose a reason for hiding this comment

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

With the way the test is written now, when the test fails, we get:

AssertionError: Expected 3 immediate callback executions

So, we were expecting 3, but we got some other number. How many? 0? 1? 2? 4?

So I was looking at how to work count in there so that information would be preserved, but I realized we could just do away with the explicit exit listener entirely if we use common.mustCall(). So if you use the test as written in that previous comment and it fails, you get results of this format:

Mismatched callback function calls. Expected 1, actual 0.

And the test is a compact 10 lines to boot.

(As usual: It's a nit, so you know, ignore my suggestion if you don't like it.)

Copy link
Contributor Author

@mscdex mscdex Oct 14, 2016

Choose a reason for hiding this comment

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

@Fishrock123 Is this ok with you, since you previously requested that a custom assertion message be added?

Copy link
Contributor

@Fishrock123 Fishrock123 left a comment

Choose a reason for hiding this comment

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

lgtm

Copy link
Member

@jasnell jasnell left a comment

Choose a reason for hiding this comment

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

LGTM

Copy link
Contributor

@evanlucas evanlucas left a comment

Choose a reason for hiding this comment

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

LGTM

@evanlucas
Copy link
Contributor

Landed in 42158a0. Thanks!

@evanlucas evanlucas closed this Oct 14, 2016
evanlucas pushed a commit that referenced this pull request Oct 14, 2016
This commit fixes a regression introduced in 0ed8839 that caused
additional queued immediate callbacks to be ignored if
`clearImmediate(immediate)` was called within the callback for
`immediate`.

PR-URL: #9086
Fixes: #9084
Reviewed-By: Rich Trott <rtrott@gmail.com>
Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Evan Lucas <evanlucas@me.com>
evanlucas pushed a commit that referenced this pull request Oct 14, 2016
This commit fixes a regression introduced in 0ed8839 that caused
additional queued immediate callbacks to be ignored if
`clearImmediate(immediate)` was called within the callback for
`immediate`.

PR-URL: #9086
Fixes: #9084
Reviewed-By: Rich Trott <rtrott@gmail.com>
Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Evan Lucas <evanlucas@me.com>
@mscdex mscdex deleted the timers-fix-setImmediate-regression branch October 14, 2016 21:34
evanlucas pushed a commit that referenced this pull request Oct 14, 2016
This commit fixes a regression introduced in 0ed8839 that caused
additional queued immediate callbacks to be ignored if
`clearImmediate(immediate)` was called within the callback for
`immediate`.

PR-URL: #9086
Fixes: #9084
Reviewed-By: Rich Trott <rtrott@gmail.com>
Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Evan Lucas <evanlucas@me.com>
@evanlucas evanlucas mentioned this pull request Oct 14, 2016
evanlucas added a commit that referenced this pull request Oct 14, 2016
* build: Fix building with shared zlib. (Bradley T. Hughes) [#9077](#9077)
* stream: fix `Writable` subclass instanceof checks (Anna Henningsen) [#9088](#9088)
* timers: fix regression with clearImmediate() (Brian White) [#9086](#9086)

PR-URL: #9104
evanlucas added a commit that referenced this pull request Oct 15, 2016
* build: Fix building with shared zlib. (Bradley T. Hughes) [#9077](#9077)
* stream: fix `Writable` subclass instanceof checks (Anna Henningsen) [#9088](#9088)
* timers: fix regression with clearImmediate() (Brian White) [#9086](#9086)

PR-URL: #9104
jasnell pushed a commit that referenced this pull request Oct 17, 2016
This commit fixes a regression introduced in 0ed8839 that caused
additional queued immediate callbacks to be ignored if
`clearImmediate(immediate)` was called within the callback for
`immediate`.

PR-URL: #9086
Fixes: #9084
Reviewed-By: Rich Trott <rtrott@gmail.com>
Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Evan Lucas <evanlucas@me.com>
imyller added a commit to imyller/meta-nodejs that referenced this pull request Oct 17, 2016
    * build: Fix building with shared zlib. (Bradley T. Hughes) [#9077](nodejs/node#9077)
    * stream: fix `Writable` subclass instanceof checks (Anna Henningsen) [#9088](nodejs/node#9088)
    * timers: fix regression with clearImmediate() (Brian White) [#9086](nodejs/node#9086)

Signed-off-by: Ilkka Myller <ilkka.myller@nodefield.com>
imyller added a commit to imyller/meta-nodejs that referenced this pull request Oct 17, 2016
    * build: Fix building with shared zlib. (Bradley T. Hughes) [#9077](nodejs/node#9077)
    * stream: fix `Writable` subclass instanceof checks (Anna Henningsen) [#9088](nodejs/node#9088)
    * timers: fix regression with clearImmediate() (Brian White) [#9086](nodejs/node#9086)

Signed-off-by: Ilkka Myller <ilkka.myller@nodefield.com>
@MylesBorins
Copy link
Contributor

@Fishrock123 should this be bacporrted?

@mscdex
Copy link
Contributor Author

mscdex commented Nov 22, 2016

@thealphanerd The original PR (#8655) these changes depend on was never landed on v4.x, so I would say no.

However, the original PR is also marked as lts-watch-v4.x, so both would need to be backported if the original is backported.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
timers Issues and PRs related to the timers subsystem / setImmediate, setInterval, setTimeout.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

setImmediate regression in v6.8.0
6 participants