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

nextTick Now (Round 2) #3723

Closed
wants to merge 4 commits into from
Closed

nextTick Now (Round 2) #3723

wants to merge 4 commits into from

Conversation

isaacs
Copy link

@isaacs isaacs commented Jul 17, 2012

Passing all tests on all commits.

@bnoordhuis @piscisaureus

callback();
threw = false;
} finally {
if (threw) tickDone();
Copy link

Choose a reason for hiding this comment

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

This is probably a paranoia case that the following script causes a event loop starvation while one does not in node-v0.8.x.

var domain = require('domain');
var d  = domain.create();
d.on('error', function(e) {
  console.log(e.message);
  f();
});
function f() {
  process.nextTick(function() {
    d.run(function() {
      throw(new Error('d'));
    });
  });
}
f();
process.on('SIGINT', function() {
  console.log('SIGINT');
  process.exit();
});

Copy link
Author

Choose a reason for hiding this comment

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

No, that's a great catch, thanks!

Fixed on df33211e14d94cb9dee6b717ae93ad845699ce3a

When there is an error that is thrown in a nextTick function, which is
then handled by a domain or other process.on('uncaughtException')
handler, if the error handler *also* adds a nextTick and triggers
multiple MakeCallback events (ie, by doing some I/O), then it would
skip over the tickDepth check, resulting in an infinite spin.

Solution: Check the tickDepth at the start of the tick processing, and
preserve it when we are cleaning up in the error case or exiting early
in the re-entry case.

In order to make sure that tick callbacks are *eventually* handled, any
callback triggered by the underlying spinner in libuv will be processed
as if starting from a tick depth of 0.
@isaacs
Copy link
Author

isaacs commented Jul 17, 2012

Fixed properly in bb6033a.

@shigeki
Copy link

shigeki commented Jul 18, 2012

@isaacs Confirmed that it was fixed.
I would like to ask an another question if PrepareTick() and CheckTick() are still necessary after this change.
The nextTick() is always called first before setTimeout() as in the tests of test-next-tick-ordering*.js even without PrepareTick() and CheckTick(). I think that we no longer invoke Tick() before and after uv_poll().

@isaacs
Copy link
Author

isaacs commented Jul 19, 2012

Landed on master. Thanks for reviewing, everyone.

@isaacs isaacs closed this Jul 19, 2012
@shigeki
Copy link

shigeki commented Jul 20, 2012

@isaacs @bnoordhuis Do you think PrepareTick() and CheckTick() are still necessary?

@isaacs
Copy link
Author

isaacs commented Jul 20, 2012

Yes, I think they are still necessary. If the tickDepth reaches the maxTickDepth, then we yield to the uv tick spinner, which uses these functions.

@piscisaureus @bnoordhuis Is this accurate?

@piscisaureus
Copy link

I do not agree with this tick depth concept. It might be good to print a warning when the loop gets stuck in a nextTick "loop", but breaking out of it seems like sort of random behavior to me that has no merit. It also does not solve any real problems, if this tick depth thing kicks in then nextTick will be "almost" starving the event loop but not completely, which just leaves users confused because their code now suddenly seems very slow and they have to idea why.

@isaacs
Copy link
Author

isaacs commented Jul 21, 2012

@piscisaureus There could be another way to do it, but we're somewhat caught in a strange position here.

On the one hand, we frequently assume that a nextTick will occur before any other I/O. On the other hand, people are using nextTick loops in the wild to perform long-running jobs without blocking pending I/O.

Here are the options, as I see them:

  1. bite the bullet and say that nextTick loops don't ever yield (and perhaps introduce a new thing that does yield)
  2. stop assuming that nextTick will come before other I/O (and perhaps introduce a new thing that does)
  3. some sort of compromise that 99% of the time works the way 90% of people expect it to, but doesn't break the exceptions, and is tunable.

I'd actually prefer option 1, and that's what I originally proposed. The feedback was a mix of "Isn't that already how it works?" and "But if you do that, it'll break this program I have." Hence the compromise. 1000 iterations is fast enough to not make too big of a dent on pending I/O, but enough iterations to be way more than anyone will normally hit (if they're not using nextTick recursively forever anyway).

A better compromise might be a maximum amount of time that the tick processing loop is allowed to run (say, 1ms or something), but then we'd have to check the time repeatedly, which is significantly costlier than a counter.

In order to better serve the non-io-starving-work-queue use case, we should add a process.on('idle') mechanism that will occur whenever there is no pending IO.

@piscisaureus
Copy link

bite the bullet and say that nextTick loops don't ever yield

+1

introduce a new thing that does yield

+1

hence the compromise. 1000 iterations is fast enough to not make too big of a dent on pending I/O

Well,1000 empty iterations are fast enough, sure. But when people are using nextTick to yield then probably they're doing some computation in the callback. All of a sudden this computation will run 1000 times before yielding happens. That's not a nice thing to do.

@shigeki
Copy link

shigeki commented Jul 23, 2012

@isaacs @piscisaureus @bnoordhuis I wrote down a figure below to show where _tickCallback() are invoked in node-v0.9 along with one event loop in uv__run() This shows that at most four _tickCallback() are called and it seems to me that the ones in uv_idle() and uv__poll() are enough. Please collect me if I am wrong.

new semantics of process.nextTick() in node-v0.9.x

@bnoordhuis
Copy link
Member

I agree with @shigeki that PrepareTick() and CheckTick() are superfluous now. This snippet, for example, works as you would expect it to:

var i = 0;
function tick() {
  if (++i < 2000) process.nextTick(tick);
}
process.nextTick(tick);

@bnoordhuis
Copy link
Member

bnoordhuis/node@a1a9d21 in case anyone wants to try it. Passes all tests needless to say.

@shigeki
Copy link

shigeki commented Jul 26, 2012

@bnoordhuis Thanks for your check and approval. I've already tested it and found it works fine.

@isaacs
Copy link
Author

isaacs commented Jul 26, 2012

@bnoordhuis lgtm. Land it at will.

@bnoordhuis
Copy link
Member

Landed in 59b584c.

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.

None yet

4 participants