Browse Source

tty: use blocking mode on OS X

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 https://github.com/nodejs/node/issues/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 c3cec1eefc,
which was included in v6.0.0.
Full details of the Libuv issue that induced this are at
https://github.com/nodejs/node/issues/6456#issuecomment-219974514

Refs: https://github.com/nodejs/node/pull/1771
Refs: https://github.com/nodejs/node/issues/6456
Refs: https://github.com/nodejs/node/pull/6773
Refs: https://github.com/nodejs/node/pull/6816
PR-URL: https://github.com/nodejs/node/pull/6895
Reviewed-By: Rod Vagg <rod@vagg.org>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
v4.x
Jeremiah Senkpiel 9 years ago
committed by Myles Borins
parent
commit
b07c3a6ea6
  1. 6
      doc/api/console.md
  2. 8
      doc/api/process.md
  3. 7
      lib/tty.js

6
doc/api/console.md

@ -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-->

8
doc/api/process.md

@ -957,6 +957,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`).
@ -1007,6 +1011,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`:

7
lib/tty.js

@ -59,6 +59,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) {

Loading…
Cancel
Save