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

doc: WIP update timers/nextTick documentation #5950

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 31 additions & 21 deletions doc/api/process.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -456,14 +456,19 @@ This will generate:

* `callback` {Function}

Once the current event loop turn runs to completion, call the callback
function.
Schedule a callback to run immediately after all currently running synchronous
operations.

This is *not* a simple alias to `setTimeout(fn, 0)`, it's much more
efficient. It runs before any additional I/O events (including
timers) fire in subsequent ticks of the event loop.
This is subject to change in the future.
Copy link

Choose a reason for hiding this comment

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

What changes are planned? This should either be removed, or expressed as a Stability level on this section.

EDIT: That is, this specific line should definitely be removed, and perhaps expressed as a Stability level, if changes are planned.

Copy link
Author

Choose a reason for hiding this comment

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

/cc @bnoordhuis

Ben mentioned it would be good to put this in the docs.


The `nextTickQueue` are callbacks that will be called immediately after the
currently running callback, and before continuing with the event loop. Recursive
`nextTick` calls will then have the same effect as a `while(true);` loop.

console.log('start');
setImmediate(function() {
console.log('setImmediate callback');
});
process.nextTick(function() {
console.log('nextTick callback');
});
Expand All @@ -472,17 +477,20 @@ timers) fire in subsequent ticks of the event loop.
// start
// scheduled
// nextTick callback
// setImmediate callback

This is important in developing APIs where you want to give the user the
chance to assign event handlers after an object has been constructed,
but before any I/O has occurred.
`process.nextTick` is specifically important when developing an event based API

Choose a reason for hiding this comment

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

I would like to deemphasize this in favor of setImmediate it would be nice to call it out here that because of the starvation warning that it's strongly advised that users now use setImmediate

Copy link
Author

Choose a reason for hiding this comment

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

True that.

Choose a reason for hiding this comment

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

So, I used to know why to call .nextTick(), but then @bnoordhuis made
.setImmediate() much faster, and I think @trevnorris removed the
problems/limits on recursive call of .nextTick(), and I no longer
understand why you would use one vs the other.

Can someone explain why node shouldn't just do process.nextTick = process.setImmediate?

And make some clear statement in the docs about under what circumstances
one should be used, vs the other?

Most reasonable use for deferred callbacks is when returning an EE, and
wanting to give caller a chance to listen on events. Next most reasonable
is when an API is usually async, and you want to make sure it is always
async.

In those cases, is the prefered API now .setImmediate, or is it still
.nextTick?

Copy link

Choose a reason for hiding this comment

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

AFAIK setImmediate(f) is conceptually === setTimeout(f, 0)

The setImmediate queue is dispatched only once per event loop turn, like so:

1.- Grab the setImmediateQueue, call it currentSetImmediateQueue,
2.- Put a new, empty, setImmediateQueue in its place
3.- Call everything that's in the currentSetImmediateQueue

But the netTick queue is serviced more than once, as often as needed per event loop turn, like so:

while (nextTickQueue.length) nextTickQueue.shift()()

So:

1.- When you add an item to the nextTickQueue from a function in the nextTickQueue, it will simply make that while() loop above loop once more, but when you add an item to the setImmediateQueue, the newly added item won't run until the next turn of the event loop (and many other callbacks may have been called in-between, by then)
2.- Recursively adding to the nextTickQueue blocks the event loop.
3.- Recursively adding to the setImmediateQueue does not block the event loop.
4.- If you want to be totally sure that no other callback (*) runs before f, use .nextTick(f).

(*) Any other cb that was already in the nextTickQueue will still run before f, though.

Choose a reason for hiding this comment

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

what @xk says is right, I would like to add the emphasis that when you do queue with setImmediate you are sure to always get called on the next turn of the event loop, where as nextTick's queue can be cleared multiple times as people transition through MakeCallback.

The fact that this logic makes it nice to make sure your cb will fire "before" others, is a side effect and shouldn't be relied upon outside of core anyway, hence my insistence to deemphasize the use of nextTick in general.

(Also nextTick doesn't and likely won't take arguments to be passed to the function)

Choose a reason for hiding this comment

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

So is it safe to say we can just use setImmediate unless we want to make sure cb is called at the beginning of next tick(except reaching maxTickDepth)? And cb of setImmediate will be significantly delayed if long blocking code gets called by i/o events before immediates?

Choose a reason for hiding this comment

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

It's safe to say, you want to use setImmediate unless there is clear evidence that you need this callback to run before any other IO notification. Out side of core I am skeptical that that distinction means anything to anyone as you have no real visibility into what actually is happening, so it's just always preferred to use setImmediate

when you need to allow the developer time to setup the event handlers after an
object has been constructed, but perform a task before any other asynchronous
events have fired (e.g. I/O).
Copy link
Member

Choose a reason for hiding this comment

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

This is not strictly true. Consider:

var tick = false;
process.nextTick(function() { tick = true });
process.whatever();  // native function that MakeCallback()'s into JS land again somehow
assert(tick == true);  // passes

Copy link
Author

Choose a reason for hiding this comment

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

that shouldn't happen. There's an intick check to see if the queue is currently being processed. If it is then exits immediately.

Copy link
Author

Choose a reason for hiding this comment

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

i.e. the callback passed to MakeCallback may run immediately, but any callbacks added via nextTick during its execution won't run until the current callback has completed.

Copy link
Member

Choose a reason for hiding this comment

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

Ah, so there is. It's still making the call into JS land, isn't it? Future optimization.

Copy link
Author

Choose a reason for hiding this comment

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

good catch. made the patch: #5952


function MyThing(options) {
this.setupOptions(options);

var ts = this;
process.nextTick(function() {
this.startDoingStuff();
}.bind(this));
ts.startDoingStuff();
});
}

var thing = new MyThing();
Expand All @@ -494,39 +502,41 @@ It is very important for APIs to be either 100% synchronous or 100%
asynchronous. Consider this example:

// WARNING! DO NOT USE! BAD UNSAFE HAZARD!
function maybeSync(arg, cb) {
function maybeSync(path, arg, cb) {
if (arg) {
cb();
return;
}

fs.stat('file', cb);
fs.stat(path, cb);
}

This API is hazardous. If you do this:
The above now seems ambiguous whether the callback will be called before the
currently running script continues:

maybeSync(true, function() {
maybeSync('/path', /* maybe truthy */, function() {
foo();
});
bar();

then it's not clear whether `foo()` or `bar()` will be called first.
If the second `maybeSync()` argument is truthy then `foo()` will run before
`bar()`. Otherwise `bar()` will run before `foo()`. This type of design decision
creates unnecessary confusion, and should be avoided.

This approach is much better:

function definitelyAsync(arg, cb) {
function definitelyAsync(path, arg, cb) {
if (arg) {
process.nextTick(cb);
return;
}

fs.stat('file', cb);
fs.stat(path, cb);
}

Note: the nextTick queue is completely drained on each pass of the
event loop **before** additional I/O is processed. As a result,
recursively setting nextTick callbacks will block any I/O from
happening, just like a `while(true);` loop.
Note to module developers: Make sure to use the `MakeCallback` API when calling
a JS callback from C++ after an asynchronous operation. This is the only way to
drain the `nextTickQueue`.

Choose a reason for hiding this comment

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

Maybe be a bit more explicit and/or point to a place where we can also talk about domain handling from MakeCallback?

Copy link
Author

Choose a reason for hiding this comment

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

I'll add documentation on its use to the module doc and link to it from here.


## process.umask([mask])

Expand Down
32 changes: 26 additions & 6 deletions doc/api/timers.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,36 @@
All of the timer functions are globals. You do not need to `require()`
this module in order to use them.

Timers set to run in the same tick of the event loop will all run in succession:

Choose a reason for hiding this comment

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

It's strange to see it spelled out here, only to be immediately contradicted in the following graphs, given that we explicitly don't guarantee the firing ordering. This description is true right now, but because that's implementation, however in the future that may not be the case. The developer should only care that node has made the best effort to delay before this function is called, not about the ordering of others.

Copy link
Author

Choose a reason for hiding this comment

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

The use of "succession" was not to imply a specific ordering, but that all callbacks will run immediately after each other (in some undefined order) without continuing though the event loop. Poor wording if you've found it confusing.

Choose a reason for hiding this comment

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

It might be easier to talk about the implementation detail of the buckets in that regard then


function cb(arg) {
return function() {
console.log(arg);
process.nextTick(function() {
console.log('nextTick - ' + arg);
});
}
}

setTimeout(cb('0'), 100);
setTimeout(cb('1'), 100);

// output:
// 0
// 1
// nextTick - 0
// nextTick - 1

## setTimeout(callback, delay, [arg], [...])

To schedule execution of a one-time `callback` after `delay` milliseconds. Returns a
`timeoutId` for possible use with `clearTimeout()`. Optionally you can
To schedule execution of a one-time `callback` after `delay` milliseconds.
Returns a `timeoutId` for possible use with `clearTimeout()`. Optionally you can
also pass arguments to the callback.

It is important to note that your callback will probably not be called in exactly
`delay` milliseconds - Node.js makes no guarantees about the exact timing of when
the callback will fire, nor of the ordering things will fire in. The callback will
be called as close as possible to the time specified.
It is important to note that your callback will probably not be called in
exactly `delay` milliseconds - Node.js makes no guarantees about the exact
timing of when the callback will fire, nor of the ordering things will fire in.
The callback will be called as close as possible to the time specified.

Choose a reason for hiding this comment

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

maybe "near" instead of "close" at first glance today I thought this was close as in the closing of a file descriptor :)

Copy link
Author

Choose a reason for hiding this comment

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

Heh. Sounds good.


## clearTimeout(timeoutId)

Expand Down