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

doc: topic blocking vs non-blocking #5326

Closed
wants to merge 4 commits into from
Closed
Changes from 1 commit
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
140 changes: 140 additions & 0 deletions doc/topics/blocking-vs-non-blocking.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
# Overview of Blocking vs Non-Blocking

This overview covers the difference between blocking and non-blocking calls in

Choose a reason for hiding this comment

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

I'd prefer to highlight, blocking and non-blocking to blocking and non-blocking . That way it draws the readers eyes.

Copy link
Contributor

Choose a reason for hiding this comment

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

+1

Copy link
Member

Choose a reason for hiding this comment

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

hmm, we usually reserve the code highlighting for code or literal values. It may be confusing. Perhaps just use bold?

Copy link
Contributor

Choose a reason for hiding this comment

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

Okay. Might be enough.

Choose a reason for hiding this comment

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

@jasnell +1

Perhaps just use bold?

Node.js. This overview will refer to the event loop and libuv but no prior
knowledge of those topics is required. Readers are assumed to have a basic
understanding of the JavaScript language and Node.js callback pattern.

> In this overview, "I/O" refers primarily to interaction with the system's disk
Copy link
Contributor

Choose a reason for hiding this comment

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

minor nit: avoid duplciation of "This overview". (am no native speaker)

Copy link
Member

Choose a reason for hiding this comment

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

+1

and network supported by [libuv](http://libuv.org/).


## Blocking

Blocking is when the execution of additional JavaScript in the Node.js process
must wait until an I/O operation completes. Blocking may occur when using any of
Copy link
Member

Choose a reason for hiding this comment

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

nit: it's not just limited to i/o operations

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Would it be more appropriate to just take I/O out of the sentence or is there a better way to explain this? My goal is to distinguish that the process is waiting not just because JavaScript is executing.

Copy link
Member

Choose a reason for hiding this comment

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

Leaving the I/O in there is fine since that's the most likely case. It may just be worthwhile indicating that non-I/O operations can cause it to bog down as well. crypto operations, for instance.

Copy link
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 you consider things like waiting for synchronization ( a lock for instance) I/O.

the synchronous I/O methods in the Node.js standard library that use libuv. Use
of blocking methods prevents the event loop from doing additional work while
Copy link
Member

Choose a reason for hiding this comment

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

technically any native module can do any sort of blocking - it doesn't have to be through libuv but I guess that absolute correctness isn't a goal here.

waiting for I/O to complete.

In Node.js, JavaScript that exhibits poor performance due to being CPU intensive
rather than I/O bound, isn't typically referred to as blocking.
Copy link
Member

Choose a reason for hiding this comment

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

There are two notions of what "blocking" is - one is "blocking the event loop" and one is "blocked as in the waiting thread state".

When people use "blocking" in node sometimes they mean one and sometimes they mean the other.


All of the I/O methods in the Node.js standard libraries provide asynchronous
Copy link
Contributor

Choose a reason for hiding this comment

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

standard library, right? Did you mean core modules?

versions, which are non-blocking, and accept callback functions. Some methods
also implement blocking methods, which usually have names that end with `Sync`.
Copy link
Member

Choose a reason for hiding this comment

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

"Some methods also implement" - the methods don't implement other methods. Maybe "Some methods also have blocking counterparts implemented"?



## Comparing Code

Choose a reason for hiding this comment

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

Could we highlight: put this in Bold this is a synchronous file read -> this is a synchronous file read
then highlight: equivalent asynchronous example to equivalent asynchronous example
I think it's easier to notice.


Using the File System module as an example, this is a synchronous file read:

```js
const fs = require('fs');
const data = fs.readFileSync('/file.md'); // blocks here until file is read
```

And here is an equivalent asynchronous example:

```js
const fs = require('fs');
fs.readFile('/file.md', (err, data) => {
if (err) throw err;
});
```

The first example appears simpler but it has the disadvantage of the second line
Copy link
Contributor

Choose a reason for hiding this comment

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

simpler

comparative degree used. Shouldn't this be

The first example appears simpler than the second

or simply

The first example appears simple

blocking the execution of any additional JavaScript until the entire file is
read. Note that in the synchronous version if an error is thrown it will need to
be caught or the process will crash. In the asynchronous version, it is up to
the author to decide whether an error should throw as shown.

Let's expand our example a little bit:

```js
const fs = require('fs');
const data = fs.readFileSync('/file.md'); // blocks here until file is read
console.log(data);
moreWork(); // will run after console.log
```

And here is a similar, but not equivalent asynchronous example:

```js
const fs = require('fs');
fs.readFile('/file.md', (err, data) => {
if (err) throw err;
console.log(data);
});
moreWork(); // will run before console.log
Copy link
Contributor

Choose a reason for hiding this comment

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

preferably do // moreWork() in order to keep the example runnable. I think this would be more consistent with the rest of the docs.

```

In the first example above, `console.log` will be called before `moreWork`. In
the second example `readFile` is non-blocking so JavaScript execution can
continue and `moreWork` will be called first. The ability to run `moreWork`
Copy link
Member

Choose a reason for hiding this comment

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

question - are we always guaranteed that readFile will execute its callback in a following turn of the event loop? I assume yes since it has to hit libuv at least once but I want to be sure.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I understand that to be the case except for situations where there is an immediate err callback, like when passing null as the filename. I think explaining that would be tangential here.

without waiting for the file read to complete is a key design choice that allows
for higher throughout.


## Concurrency and Throughput

The Node.js event loop is single threaded, so concurrency refers to the event
Copy link
Member

Choose a reason for hiding this comment

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

Maybe it's worth phrasing as JavaScript execution since the event loop itself is not single threaded.

loop's capacity to resume execution of a JavaScript callback function after
Copy link
Member

Choose a reason for hiding this comment

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

Technically it doesn't resume execution - it initiates it. JavaScript always runs synchronously to completion.

completing other work. Any code that is expected to process requests in a
concurrent manner depends on the ability of the event loop to continue running
as asynchronous I/O occurs.

As an example, let's consider a case where each request to a web server takes
50ms to complete and 45ms of that 50ms is database I/O that can be done
asychronously. Choosing non-blocking asynchronous operations frees up that 45ms
per request to handle other requests. This is an effective 90% difference in
capacity just by choosing to use non-blocking methods instead of blocking
methods.

The event loop is different than models in many other languages where additional
threads may be created to handle concurrent work. For an introduction to the
event loop see [Overview of the Event Loop, Timers, and
`process.nextTick()`](https://github.com/nodejs/node/pull/4936)
Copy link
Contributor

Choose a reason for hiding this comment

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

Well, PRs are WIP items. So quoting them is a not a good idea I think.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This shouldn't be merged with this like this, my issue is that there should be a reference to the other topic on nodejs.org but the PR hasn't even been merged into master yet. This was mentioned above when the PR was opened.



## Dangers of Mixing Blocking and Non-Blocking Code

There are some patterns that should be avoided when dealing with I/O. Let's look
at an example:

```js
const fs = require('fs');
fs.readFile('/file.md', (err, data) => {
if (err) throw err;
console.log(data);
});
fs.unlinkSync('/file.md');
```

In the above example, `unlinkSync` is likely to be run before `readFile`, which
would delete `file.md` before it is actually read. A better way to write this
that is completely non-blocking and guaranteed to execute in the correct order
is:


```js
const fs = require('fs');
fs.readFile('/file.md', (err, data) => {
if (err) throw err;
console.log(data);
fs.unlink('/file.md', err => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Use parens consistently with arrow functions.

if (err) throw err;
});
});
```

The above places a non-blocking call to `unlink` within the callback of
`readFile` which guarantees the correct order of operations.
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you refer here always to fs.unlink(), fs.readFile() for consistency?



## Additional Resources

- [libuv](http://libuv.org/)
- [Overview of the Event Loop, Timers, and
`process.nextTick()`](https://github.com/nodejs/node/pull/4936)
- [About Node.js](https://nodejs.org/en/about/)