From 4d3a7594a5627212c06a4d95aec6e003c2460690 Mon Sep 17 00:00:00 2001 From: Jeremiah Senkpiel Date: Fri, 20 May 2016 10:20:08 -0400 Subject: [PATCH] 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 c3cec1eefc9f3b55a3fb7bd623b3d921f493870d, 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 Reviewed-By: Anna Henningsen --- doc/api/console.md | 6 +++++- doc/api/process.md | 11 ++++++++++- lib/tty.js | 7 +++++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/doc/api/console.md b/doc/api/console.md index bbb96cb3d3e75d..ac0ce25a37c7a6 100644 --- a/doc/api/console.md +++ b/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 diff --git a/doc/api/process.md b/doc/api/process.md index cb53dc3f5b6682..b0e85746cf3548 100644 --- a/doc/api/process.md +++ b/doc/api/process.md @@ -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. @@ -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`). @@ -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`: diff --git a/lib/tty.js b/lib/tty.js index 72efcd078b2892..fe0cc2e5219966 100644 --- a/lib/tty.js +++ b/lib/tty.js @@ -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) {