diff --git a/lib/internal/console/global.js b/lib/internal/console/global.js index 614941eba6da2d..608d33e88c6d90 100644 --- a/lib/internal/console/global.js +++ b/lib/internal/console/global.js @@ -34,7 +34,25 @@ for (const prop of Reflect.ownKeys(Console.prototype)) { Reflect.defineProperty(globalConsole, prop, desc); } -globalConsole[kBindStreamsLazy](process); +function makeSync(stream) { + // This function is only called twice, we are not reusing the require call. + const SyncWriteStream = require('internal/fs/sync_write_stream'); + + if (stream.isTTY || stream instanceof SyncWriteStream) { + return stream; + } else if (stream.fd >= 0) { + return new SyncWriteStream(stream.fd); + } else { + // We cannot do much more, the stream will be async. + // TODO(mcollina) verify if such a case is possible + return stream; + } +} + +globalConsole[kBindStreamsLazy]({ + get stdout() { return makeSync(process.stdout); }, + get stderr() { return makeSync(process.stderr); } +}); globalConsole[kBindProperties](true, 'auto'); // This is a legacy feature - the Console constructor is exposed on diff --git a/lib/internal/fs/sync_write_stream.js b/lib/internal/fs/sync_write_stream.js index 1e7c6a50a96d6d..5b40afb48838a9 100644 --- a/lib/internal/fs/sync_write_stream.js +++ b/lib/internal/fs/sync_write_stream.js @@ -19,7 +19,21 @@ Object.setPrototypeOf(SyncWriteStream.prototype, Writable.prototype); Object.setPrototypeOf(SyncWriteStream, Writable); SyncWriteStream.prototype._write = function(chunk, encoding, cb) { - writeSync(this.fd, chunk, 0, chunk.length); + while (true) { + try { + const n = writeSync(this.fd, chunk, 0, chunk.length); + if (n !== chunk.length) { + chunk = chunk.slice(0, n); + } else { + break; + } + } catch (err) { + if (err.code !== 'EAGAIN') { + cb(err); + break; + } + } + } cb(); return true; }; diff --git a/test/parallel/test-console-block-when-piped.js b/test/parallel/test-console-block-when-piped.js new file mode 100644 index 00000000000000..949eab388c34e2 --- /dev/null +++ b/test/parallel/test-console-block-when-piped.js @@ -0,0 +1,34 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const cp = require('child_process'); +const KB = 1024; +const MB = KB * KB; +const expected = KB * MB + MB; // 1GB + 1MB of newlines + +// console.log must synchronously write to stdout if +// if the process is piped to. The expected behavior is that +// console.log will block the main thread if the consumer +// cannot keep up. +// See https://github.com/nodejs/node/issues/24992 for more +// details. + +if (process.argv[2] === 'child') { + const data = Buffer.alloc(KB).fill('x').toString(); + for (let i = 0; i < MB; i++) + console.log(data); + process.exit(0); +} else { + const child = cp.spawn(process.execPath, [__filename, 'child'], { + stdio: ['pipe', 'pipe', 'inherit'] + }); + let count = 0; + child.stdout.on('data', (c) => count += c.length); + child.stdout.on('end', common.mustCall(() => { + assert.strictEqual(count, expected); + })); + child.on('exit', common.mustCall((code) => { + assert.strictEqual(code, 0); + })); +}