Skip to content
Merged
Changes from 1 commit
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
46f0702
doc: add topic - event loop, timers, `nextTick()`
techjeffharris Jan 28, 2016
5a28415
doc: add topic - event loop, timers, `nextTick()`
techjeffharris Jan 28, 2016
dc1b8a5
corrections suggested in the GitHub PR
techjeffharris Jan 29, 2016
bb5b682
Merge branch 'doc-topic-event-loop-timers-nextTick' of github.com:tec…
techjeffharris Jan 29, 2016
ba98380
removed file without .md extension
techjeffharris Jan 29, 2016
936bf17
add details to explanation of timers
techjeffharris Jan 29, 2016
35cf726
update to address comments on PR
techjeffharris Feb 17, 2016
f80d7cc
fixed typo, added example as per @trevnorris
techjeffharris Feb 24, 2016
254694b
fixed styling nits identified by @mscdex
techjeffharris Feb 24, 2016
45fb2fe
fixes suggested by @silverwind and @fishrock123
techjeffharris Feb 26, 2016
d6d76f5
addressed comments made on GH issue
techjeffharris Mar 25, 2016
c133caf
updated `setImmediate()` vs `setTimeout()` section
techjeffharris Mar 25, 2016
f425164
update overview, phase detail headings, wrap at 72
techjeffharris Mar 29, 2016
1bd3e6c
docs: minor nits on the libuv phases.
mcollina Mar 31, 2016
7574d4b
Removed second timer phase.
mcollina Mar 31, 2016
8dc6ecb
Merge pull request #1 from mcollina/doc-topic-event-loop-timers-nextTick
techjeffharris Mar 31, 2016
d82a7f1
fix nits presented by @ajafff
techjeffharris Mar 31, 2016
1dc26f6
fix backticks on line 205
techjeffharris Mar 31, 2016
82d0fb8
Improve wording `setTimeout()` vs `setImmediate()`
techjeffharris Apr 7, 2016
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
Prev Previous commit
Next Next commit
update to address comments on PR
  • Loading branch information
techjeffharris committed Feb 17, 2016
commit 35cf726b79bb2e22a2d0a632fb4d899a532d13f4
273 changes: 224 additions & 49 deletions doc/topics/the-event-loop-timers-and-nexttick.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,23 @@
# Overview of the Event Loop, Timers, and `process.nextTick()`
# The Node.js Event Loop, Timers, and `process.nextTick()`

## What is the Event Loop?

The event loop is what allows Node.js to perform non-blocking I/O
operations — despite the fact that JavaScript is single-threaded — by
offloading operations to the system kernel whenever possible.

Since most modern kernels are multi-threaded, they can handle
multiple operations executing in the background. When one of these
operations completes, the kernel tells Node.js so that the appropriate callback
may added to the `poll` queue to eventually be executed. We'll explain
this in further detail later in this topic.

## Event Loop Explained

When Node.js starts, it initializes the event loop, processes the provided
input script (or drops into the REPL, which is not covered in this document)
which may make async API calls, schedule timers, or call `process.nextTick()`,
then begins processing the event loop.

The Following diagram shows a simplified overview of the event loop's
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

lowercase following

order of operations.
Expand All @@ -18,54 +37,146 @@ order of operations.

*note: each box will be referred to as a "phase" of the event loop.*

*There is a slight discrepancy between the Windows and the Unix/Linux
implementation, but that's not important for this demonstration. The most
important parts are here. There are actually seven or eight steps, but the
ones we care about — ones that Node.js actually uses are these four.*
Each phase has a FIFO queue of callbacks to execute. While each phase is
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

double space after phase

special in its own way, generally, when the event loop enters a given phase,
it will perform any operations specific to that phase, then execute callbacks
in that phase's queue until the queue has been exhausted or the maximum number
of callbacks have executed. When the queue has been exhausted or the callback
limit is reached, the event loop will move to the next phase, and so on.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks like there are many of these mid-sentence double spaces, please clean them up 😉

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hmmm. I suspect that's due to my auto-wrap settings for sublime text..

Thank you for bringing this to my attention!


Since any of these operations may schedule _more_ operations and new events
processed in the `poll` phase are queued by the kernel, poll events can be
queued while polling events are being processed. As a result, long running
callbacks can allow the poll phase to run much longer than a timer's
threshold. See the [`timers`](#timers) and [`poll`](#poll) sections for more
details.

_**NOTE:** There is a slight discrepancy between the Windows and the
Unix/Linux implementation, but that's not important for this demonstration.
The most important parts are here. There are actually seven or eight steps,
but the ones we care about — ones that Node.js actually uses are these four._


## Phases Overview:

* `timers`: this phase executes callbacks scheduled by `setTimeout()`
and `setInterval()`.
* `pending callbacks`: this phase executes callbacks for specific types of TCP
errors, for example.
* `poll`: this is the phase in which the event loop either processes
its queue of callbacks, or sits and waits for new callbacks for incoming
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

poll phase doesn't do anything initially on its own. (cc @bnoordhuis) It will sit and wait to be notified on a file descriptor. At which time libuv will call the corresponding callback.

connections, completion of async file I/O, DNS operations, etc. to be added
to the queue. Ideally, most scripts spend most of their time here. If not,
it might be time consider refactoring or scaling to more processes.
* `setImmediate`: This phase allows a person to execute callbacks
immediately after the `poll` phase has completed.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Possibly make a note that when a setImmediate is schedules, the poll phase will still run and check for any waiting notifications. Which will then be processed. If not then execution will proceed immediately to calling the setImmediate.


## Phases in Detail

### timers

Contrary to what a person might expect, a timer specifies the **threshold**
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

node behaviour is what any software developer not on a high-end real-time OS/hardware would expect. Nothing happens at exact time in a computer, its all aproximate. You get either before or after semantics, and after is what anyone would expect. I think this text makes it sound like node is doing something odd here, when its how every commodity OS works.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Well to be fair @sam-github, people ask a lot about why their timers and timings aren't accurate. It might be worth mentioning that it's not node-specific though:

Contrary to what a person might expect and like other server runtimes,

Or something like that.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Lets not quibble about the thoughfulness of peoples expectations, then :-), but just say "Timers callbacks will run as early as they can be scheduled after the specified amount of time has passed. Operating System scheduling or the running of other callbacks may delay them."

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Lets not quibble about the thoughfulness of peoples expectations, then :-)

Sounds good to me! Why be presumptuous?

"Timers callbacks will run as early as they can be scheduled after the specified amount of time has passed. Operating System scheduling or the running of other callbacks may delay them."

I'm going to go with this ^^

_after which_ the provided callback _may be executed_ rather than the
**exact** time a person _wants it to be executed_.

_**Note**: Technically, the [`poll` phase](#poll) controls when timers are
executed. While waiting for the minimum threshold specified when scheduling
the timer, the `poll` phase will iterate through its queue of callbacks as
usual. Collectively, these callbacks **may or may not** take longer to
complete than the minimum threshold specified by the timer. The actual amount
of time depends on how many callbacks end up being executed and how long they
take._

For example, say you schedule a timeout to execute after a 100 ms threshold,
then your script starts asynchronously reading a file which takes 95 ms:

```js

var fs = require('fs');

function someAsyncOperation (callback) {

// let's assume this takes 95ms to complete
fs.readFile('/path/to/file', callback);

}

var timeoutScheduled = Date.now();

## timers
setTimeout(function () {

This phase executes callbacks scheduled by `setTimeout()` and `setInterval()`.
var delay = Date.now() - timeoutScheduled;

When you create a timer, you make a call to `setTimeout()`. Then, when
the poll phase of the event loop is entered, the number of ms before the
soonest timer is to be called is set as the poll's timeout. Meaning the
poll phase will return after "timeout" ms. After that many
milliseconds, the `poll` phase will return and wrap back around to the
timers phase where those callbacks can be processed.
console.log(delay + "ms have passed since I was scheduled");
}, 100);

Take note that the poll phase can only return while idle; execution of
a callback is allowed to run to completion, and can cause unexpected
delay running the timer.

*Note: The `poll` phase technically controls when timers are called due to its
ability to cause a thread to sit idly without burning CPU in order to stall the
event loop so the timer can execute.*
// do someAsyncOperation which takes 95 ms to complete
someAsyncOperation(function () {

## pending callbacks:
var startCallback = Date.now();

This phase executes callbacks for specific types of TCP errors, for example.
// do something that will take 10ms...
while (Date.now() - startCallback < 10) {
; // do nothing
}

## poll:
});
```

When the event loop enters the `poll` phase, it has an empty queue
(`fs.readFile()` has not completed) so it will wait for the number of ms
remaining until the soonest timer's threshold is reached. While it is
waiting 95 ms pass, `fs.readFile()` finishes reading the file and its
callback which takes 10 ms to complete is added to the `poll` queue and
executed. When the callback finishes, there are no more callbacks in the
queue, so the event loop will see that the threshold of the soonest timer has
been reached then wrap back to the `timers` phase to execute the timer's
callback. In this example, you will see that the total delay between
the timer being scheduled and its callback being executed will be
105ms.

Note: To prevent the `poll` phase from starving the event loop, libuv also has
a hard maximum (system dependent) before it stops poll'ing for more events.

### pending callbacks:

This phase executes callbacks for some system operations such as types of TCP
errors. For example if a TCP socket receives `ECONNREFUSED` when
attempting to connect, some *nix systems want to wait to report the
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Might need to escape the * to not confuse markdown parsers.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

indeed, thank you.

error. This will be queued to execute in the `pending callbacks` phase.

This is the phase in which the event loop sits and waits for incoming
connections to be received. Ideally, most scripts spend most of their time
here.
### poll:

## `setImmediate()`:
When the event loop enters the `poll` phase _and there are no timers
scheduled_, one of two things will happend:

* _if the `poll` queue **is not empty**_, the event loop will iterate through
its queue of callbacks executing them synchronously until either the queue has
been exhausted, or the system-dependent hard limit is reached.

* _if the `poll` queue is **empty**_, the event loop will wait for a
callback
to be added to the queue, then execute it immediately.

Once the `poll` queue is empty the event loop will check for timers _whose
time thresholds have been reached_. If one or more timers are ready, the
event loop will wrap back to the timers phase to execute those timers'
callbacks.

### `setImmediate()`:

`setImmediate()` is actually a special timer that runs in a separate phase of
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm not sure that I would call it a timer really since it has no "timeout" value per se ...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I just used @trevnorris' words from the presentation link in the OP, but I am open so suggestions as per his approval.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

its a timer? really? that surprises me, its behaviour seems unrelated to time.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm not sure timer is good terminology for it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

TBH, I do not have enough intimate knowledge of the codebase to argue for or against whether or not it is a timer. I just said its a special form of timer because that's what I got from the presentation which was the basis. I'm hoping to get this landed ASAP, so that EVERYONE can discuss and refine this doc

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

setImmediate is not a timer, see #4936 (comment). However if we do not clarify that there is a step for executing setImmediate tasks, we have no way of clarifying the difference.

the event loop. It uses a libuv API that schedules callbacks to execute after
the poll phase has completed.
the `poll` phase has completed.

Generally, as the code is executed, the event loop will eventually hit the
`poll` phase where it will wait for an incoming connection, request, etc.
However, after a callback has been scheduled with `setImmediate()`, at the
start of the poll phase, a check will be run to see if there are any callbacks
waiting. If there are none waiting, the poll phase will end and continue to
the `setImmediate` callback phase.
However, after a callback has been scheduled with `setImmediate()`,
then the `poll` phase becomes idle, it will end and continue to the
`setImmediate` phase rather than waiting for `poll` events.

### `setImmediate()` vs `setTimeout()`
## `setImmediate()` vs `setTimeout()`

How quickly a `setImmediate()` callback is executed is only limited by how
quickly the event loop can be processed whereas a timer won't fire until the
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

until "at least" the

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

fixed

Expand All @@ -83,14 +194,15 @@ microsecond scale (1 ms = 1000 µs.)

You may have noticed that `process.nextTick()` was not displayed in the
diagram, even though its a part of the asynchronous API. This is because
`process.nextTick()` is not technically part of the event loop. Instead, it
is executed at the end of each phase of the event loop.
`process.nextTick()` is not technically part of the event loop. Instead, the
nextTickQueue will be processed after the current operation completes,
regardless of the current `phase` of the event loop.

Looking back at our diagram, any time you call `process.nextTick()` in a given
phase, all callbacks passed to `process.nextTick()` will be resolved before
the event loop continues. This can create some bad situations because **it
allows you to "starve" your I/O by making recursive `process.nextTick()`
calls.** which prevents the event loop from reaching the poll phase.
calls.** which prevents the event loop from reaching the `poll` phase.

### Why would that be allowed?

Expand All @@ -112,10 +224,13 @@ arguments to `process.nextTick()` allowing it to take any arguments passed
after the callback to be propagated as the arguments to the callback so you
don't have to nest functions.

What we're doing is passing an error back to the user. As far as the _event
loop_ is concerned, this happens **synchronously**. However, as far as the
_user_ is concerned, it occurs **asynchronously**: `apiCall()` always runs its
callback *after* the rest of the user's code.
What we're doing is passing an error back to the user but only *after* we have
allowed the rest of the user's code to execute. By using `process.nextTick()`
we guarantee that `apiCall()` always runs its callback *after* the rest of the
user's code and *before* the event loop is allowed to proceed. To acheive
this, the JS call stack is allowed to unwind then immediately execute the
provided callback which allows a person to make recursive calls to nextTick
without reaching a `RangeError: Maximum call stack size exceeded from v8`.

This philosophy can lead to some potentially problematic situations. Take
this snippet for example:
Expand Down Expand Up @@ -169,15 +284,75 @@ names themselves won't change.
to reason about (and it leads to code that's compatible with a wider
variety of environments, like browser JS.)*

## Two reasons to use `process.nextTick()`:
## Why use `process.nextTick()`?

There are two main reasons:

1. Allow users to handle errors, cleanup any then unneeded resources, or
perhaps try the request again before the event loop continues.

2. If you were to run a function constructor that was to, say, inherit from
`EventEmitter` and it wanted to call an event within the constructor. You
can't emit an event from the constructor immediately because the script
will not have processed to the point where the user assigns a callback to
that event. So, within the constructor itself, you can use
`process.nextTick()` to set a callback to emit the event after the
constructor has finished, which provides the expected results.
perhaps try the request again before the event loop continues.

2. At times it's necessary to allow a callback to run after the call
stack has unwound but before the event loop continues.

One example is to match the user's expectations. Simple example:

```js
var server = net.createServer();
server.on('connection', function(conn) { });

server.listen(8080);
server.on('listening', function() { });
```

Say that listen() is run at the beginning of the event loop, but the
listening callback is placed in a setImmediate. Now, unless a hostname
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All instances of setImmediate should probably be styled like:

`setImmediate()`

is passed binding to the port will happen immediately. Now for the
event loop to proceed it must hit the `poll` phase, which means there
is a non-zero chance that a connection could have been received allowing
the connection event to be fired before the listening event.

Another example is running a function constructor that was to, say,
inherit from `EventEmitter` and it wanted to call an event within the
constructor:

```js
const EventEmitter = require('events');
const util = require('util');

function MyEmitter() {
EventEmitter.call(this);
this.emit('event');
}
util.inherits(MyEmitter, EventEmitter);

const myEmitter = new MyEmitter();
myEmitter.on('event', function() {
console.log('an event occurred!');
});
```

You can't emit an event from the constructor immediately
because the script will not have processed to the point where the user
assigns a callback to that event. So, within the constructor itself,
you can use `process.nextTick()` to set a callback to emit the event
after the constructor has finished, which provides the expected results:

```js
const EventEmitter = require('events');
const util = require('util');

function MyEmitter() {
EventEmitter.call(this);

// use nextTick to emit the event once a handler is assigned
process.nextTick(() => {
this.emit('event');
}.bind(this));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is the bind here necessary with using an arrow function?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@evanlucas good catch. Arrow functions can't have their this hard-bound http://blog.getify.com/arrow-this/

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks @evanlucas ! replaced the arrow function with a plain-old anonymous function.

}
util.inherits(MyEmitter, EventEmitter);

const myEmitter = new MyEmitter();
myEmitter.on('event', function() {
console.log('an event occurred!');
});
```