From 543c2e00dad8e96ad9394a1c682ad736495f3e02 Mon Sep 17 00:00:00 2001 From: Jeremiah Senkpiel Date: Sun, 15 May 2016 12:23:03 -0400 Subject: [PATCH] process: flush stdout/stderr upon `process.exit()` --- deps/uv/include/uv.h | 2 + deps/uv/src/unix/stream.c | 15 +++++ lib/internal/process.js | 60 +++++++++++++++++++ src/stream_wrap.cc | 17 ++++++ src/stream_wrap.h | 1 + test/known_issues/known_issues.status | 1 - .../test-stdout-buffer-flush-on-exit.js | 2 - 7 files changed, 95 insertions(+), 3 deletions(-) rename test/{known_issues => parallel}/test-stdout-buffer-flush-on-exit.js (99%) diff --git a/deps/uv/include/uv.h b/deps/uv/include/uv.h index baa0b28124ba59..fdaebc6a0c3ef2 100644 --- a/deps/uv/include/uv.h +++ b/deps/uv/include/uv.h @@ -483,6 +483,8 @@ UV_EXTERN int uv_try_write(uv_stream_t* handle, const uv_buf_t bufs[], unsigned int nbufs); +UV_EXTERN int uv_flush_sync(uv_stream_t* stream); + /* uv_write_t is a subclass of uv_req_t. */ struct uv_write_s { UV_REQ_FIELDS diff --git a/deps/uv/src/unix/stream.c b/deps/uv/src/unix/stream.c index 9043664dfcaf9a..7172ad4dd56429 100644 --- a/deps/uv/src/unix/stream.c +++ b/deps/uv/src/unix/stream.c @@ -1625,6 +1625,21 @@ void uv__stream_close(uv_stream_t* handle) { } +/* Have stream block and then synchronously flush queued writes. + * This function works without an event loop. + * Intended to be used just prior to exit(). + * Returns 0 on success, non-zero on failure. + */ +int uv_flush_sync(uv_stream_t* stream) { + int rc = uv_stream_set_blocking(stream, 1); + if (rc == 0) { + uv__write(stream); + rc = (int)stream->write_queue_size; + } + return rc; +} + + int uv_stream_set_blocking(uv_stream_t* handle, int blocking) { /* Don't need to check the file descriptor, uv__nonblock() * will fail with EBADF if it's not valid. diff --git a/lib/internal/process.js b/lib/internal/process.js index c435c2e8712a03..18ce7113efb3ee 100644 --- a/lib/internal/process.js +++ b/lib/internal/process.js @@ -145,7 +145,67 @@ function setupKillAndExit() { process._exiting = true; process.emit('exit', process.exitCode || 0); } + + // Flush stdio streams prior to exit. + // `flushSync` not present if stream redirected to file in shell. + flushSync(process.stdout); + flushSync(process.stderr); + process.reallyExit(process.exitCode || 0); + + function flushSync(stream) { + + // FIXME: Behavior of this function outside of process.exit() is + // undefined due to the following factors: + // * Stream fd may be blocking after this call. + // * In the event of an incomplete flush pending buffered write + // requests may be truncated. + // * No return code. + + if (stream._writev) + return; + + var handle = stream._handle; + if (!handle || !handle.flushSync) + return; + + var fd = handle.fd; + if (typeof fd !== 'number' || fd < 0) + return; + + // FIXME: late module resolution avoids cross require problem + const fs = require('fs'); + + // Queued libuv writes must be flushed first. + // Note: fd will set to blocking after handle.flushSync() + if (handle.flushSync() !== 0) { + // bad fd or write queue for libuv stream not entirely flushed + return; + } + + // then the queued stream chunks can be flushed + var state = stream._writableState; + var entry = state.bufferedRequest; + while (entry) { + var chunk = entry.chunk; + if (!(chunk instanceof Buffer)) { + chunk = Buffer.from(chunk, entry.encoding); + } + // Note: fd is blocking at this point + var written = fs.writeSync(fd, chunk, 0, chunk.length); + if (written !== chunk.length) { + // stream chunk not flushed entirely - stop writing. + // FIXME: buffered request queue should be repaired here + // rather than being truncated after loop break + break; + } + entry = entry.next; + } + + state.bufferedRequestCount = 0; + state.bufferedRequest = null; + state.lastBufferedRequest = null; + } }; process.kill = function(pid, sig) { diff --git a/src/stream_wrap.cc b/src/stream_wrap.cc index 695e917e027604..10a7110812bfd8 100644 --- a/src/stream_wrap.cc +++ b/src/stream_wrap.cc @@ -77,6 +77,7 @@ StreamWrap::StreamWrap(Environment* env, void StreamWrap::AddMethods(Environment* env, v8::Local target, int flags) { + env->SetProtoMethod(target, "flushSync", FlushSync); env->SetProtoMethod(target, "setBlocking", SetBlocking); StreamBase::AddMethods(env, target, flags); } @@ -273,6 +274,22 @@ void StreamWrap::SetBlocking(const FunctionCallbackInfo& args) { } +void StreamWrap::FlushSync(const FunctionCallbackInfo& args) { + StreamWrap* wrap = Unwrap(args.Holder()); + + if (!wrap->IsAlive()) + return args.GetReturnValue().Set(UV_EINVAL); + +#if defined(_WIN32) + int rc = 0; +#else + int rc = uv_flush_sync(wrap->stream()); +#endif + + args.GetReturnValue().Set(rc); +} + + int StreamWrap::DoShutdown(ShutdownWrap* req_wrap) { int err; err = uv_shutdown(&req_wrap->req_, stream(), AfterShutdown); diff --git a/src/stream_wrap.h b/src/stream_wrap.h index b0d9986db50c5d..9c60a6c75599a7 100644 --- a/src/stream_wrap.h +++ b/src/stream_wrap.h @@ -72,6 +72,7 @@ class StreamWrap : public HandleWrap, public StreamBase { int flags = StreamBase::kFlagNone); private: + static void FlushSync(const v8::FunctionCallbackInfo& args); static void SetBlocking(const v8::FunctionCallbackInfo& args); // Callbacks for libuv diff --git a/test/known_issues/known_issues.status b/test/known_issues/known_issues.status index 12e4b10d06d578..e21913e232c03f 100644 --- a/test/known_issues/known_issues.status +++ b/test/known_issues/known_issues.status @@ -7,7 +7,6 @@ prefix known_issues [true] # This section applies to all platforms [$system==win32] -test-stdout-buffer-flush-on-exit: SKIP [$system==linux] diff --git a/test/known_issues/test-stdout-buffer-flush-on-exit.js b/test/parallel/test-stdout-buffer-flush-on-exit.js similarity index 99% rename from test/known_issues/test-stdout-buffer-flush-on-exit.js rename to test/parallel/test-stdout-buffer-flush-on-exit.js index 709928693eeb9a..e966d02e324ba7 100644 --- a/test/known_issues/test-stdout-buffer-flush-on-exit.js +++ b/test/parallel/test-stdout-buffer-flush-on-exit.js @@ -23,5 +23,3 @@ if (process.argv[2] === 'child') { assert.strictEqual(stdout, longLine, `failed with exponent ${exponent}`); }); - -