Skip to content

Commit

Permalink
tty: use blocking mode on OS X
Browse files Browse the repository at this point in the history
OS X has a tiny 1kb hard-coded buffer size for stdout / stderr to
TTYs (terminals). Output larger than that causes chunking, which ends
up having some (very small but existent) delay past the first chunk.
That causes two problems:

1. When output is written to stdout and stderr at similar times, the
two can become mixed together (interleaved). This is especially
problematic when using control characters, such as \r. With
interleaving, chunked output will often have lines or characters erased
unintentionally, or in the wrong spots, leading to broken output.
CLI apps often extensively use such characters for things such as
progress bars.

2. Output can be lost if the process is exited before chunked writes
are finished flushing. This usually happens in applications that use
`process.exit()`, which isn't infrequent.

See #6980 for more info.

This became an issue as result of the Libuv 1.9.0 upgrade. A fix to
an unrelated issue broke a hack previously required for the OS X
implementation. This resulted in an unexpected behavior change in node.
The 1.9.0 upgrade was done in c3cec1e,
which was included in v6.0.0.
Full details of the Libuv issue that induced this are at
#6456 (comment)

Refs: #1771
Refs: #6456
Refs: #6773
Refs: #6816
PR-URL: #6895
Reviewed-By: Rod Vagg <[email protected]>
Reviewed-By: Anna Henningsen <[email protected]>
  • Loading branch information
Fishrock123 authored and rvagg committed Jun 2, 2016
1 parent 99c05a1 commit 4d3a759
Show file tree
Hide file tree
Showing 3 changed files with 22 additions and 2 deletions.
6 changes: 5 additions & 1 deletion doc/api/console.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,15 @@ duplicate the browser's functionality exactly.

## Asynchronous vs Synchronous Consoles

The console functions are asynchronous unless the destination is a file.
The console functions are usually asynchronous unless the destination is a file.
Disks are fast and operating systems normally employ write-back caching;
it should be a very rare occurrence indeed that a write blocks, but it
is possible.

Additionally, console functions are blocking when outputting to TTYs
(terminals) on OS X as a workaround for the OS's very small, 1kb buffer size.
This is to prevent interleaving between `stdout` and `stderr`.

## Class: Console

<!--type=class-->
Expand Down
11 changes: 10 additions & 1 deletion doc/api/process.md
Original file line number Diff line number Diff line change
Expand Up @@ -833,7 +833,8 @@ if (someConditionNotMet()) {
```

The reason this is problematic is because writes to `process.stdout` in Node.js
are *non-blocking* and may occur over multiple ticks of the Node.js event loop.
are usually *non-blocking* and may occur over multiple ticks of the Node.js
event loop.
Calling `process.exit()`, however, forces the process to exit *before* those
additional writes to `stdout` can be performed.

Expand Down Expand Up @@ -1367,6 +1368,10 @@ event and that writes can block when output is redirected to a file (although
disks are fast and operating systems normally employ write-back caching so it
should be a very rare occurrence indeed.)

Additionally, `process.stderr` and `process.stdout` are blocking when outputting
to TTYs (terminals) on OS X as a workaround for the OS's very small, 1kb
buffer size. This is to prevent interleaving between `stdout` and `stderr`.

## process.stdin

A `Readable Stream` for stdin (on fd `0`).
Expand Down Expand Up @@ -1417,6 +1422,10 @@ event and that writes can block when output is redirected to a file (although
disks are fast and operating systems normally employ write-back caching so it
should be a very rare occurrence indeed.)

Additionally, `process.stderr` and `process.stdout` are blocking when outputting
to TTYs (terminals) on OS X as a workaround for the OS's very small, 1kb
buffer size. This is to prevent interleaving between `stdout` and `stderr`.

To check if Node.js is being run in a TTY context, read the `isTTY` property
on `process.stderr`, `process.stdout`, or `process.stdin`:

Expand Down
7 changes: 7 additions & 0 deletions lib/tty.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@ function WriteStream(fd) {
writable: true
});

// Prevents interleaved stdout/stderr output in OS X terminals.
// As noted in the following reference, local TTYs tend to be quite fast and
// this behaviour has become expected due historical functionality on OS X,
// even though it was originally intended to change in v1.0.2 (Libuv 1.2.1).
// Ref: https://github.com/nodejs/node/pull/1771#issuecomment-119351671
if (process.platform === 'darwin') this._handle.setBlocking(true);

var winSize = [];
var err = this._handle.getWindowSize(winSize);
if (!err) {
Expand Down

0 comments on commit 4d3a759

Please sign in to comment.