From 2f6420ba38ab8ccaab3426c49b36484eafeb8e95 Mon Sep 17 00:00:00 2001
From: Jeremiah Senkpiel <fishrock123@rocketmail.com>
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                       | 62 +++++++++++++++++++
 src/stream_wrap.cc                            | 17 +++++
 src/stream_wrap.h                             |  1 +
 test/known_issues/known_issues.status         |  1 -
 .../test-stdout-buffer-flush-on-exit.js       |  4 +-
 7 files changed, 98 insertions(+), 4 deletions(-)
 rename test/{known_issues => parallel}/test-stdout-buffer-flush-on-exit.js (89%)

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..5b6e56fa9a37b1 100644
--- a/lib/internal/process.js
+++ b/lib/internal/process.js
@@ -145,7 +145,69 @@ 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) {
+
+      // 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');
+
+      const Buffer = require('buffer');
+
+      // 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<v8::FunctionTemplate> target,
                             int flags) {
+  env->SetProtoMethod(target, "flushSync", FlushSync);
   env->SetProtoMethod(target, "setBlocking", SetBlocking);
   StreamBase::AddMethods<StreamWrap>(env, target, flags);
 }
@@ -273,6 +274,22 @@ void StreamWrap::SetBlocking(const FunctionCallbackInfo<Value>& args) {
 }
 
 
+void StreamWrap::FlushSync(const FunctionCallbackInfo<Value>& args) {
+  StreamWrap* wrap = Unwrap<StreamWrap>(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<v8::Value>& args);
   static void SetBlocking(const v8::FunctionCallbackInfo<v8::Value>& 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 89%
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..791a9ccd154c84 100644
--- a/test/known_issues/test-stdout-buffer-flush-on-exit.js
+++ b/test/parallel/test-stdout-buffer-flush-on-exit.js
@@ -15,7 +15,7 @@ if (process.argv[2] === 'child') {
   process.exit();
 }
 
-[22, 21, 20, 19, 18, 17, 16, 16, 17, 18, 19, 20, 21, 22].forEach((exponent) => {
+[22, 16].forEach((exponent) => {
   const bigNum = Math.pow(2, exponent);
   const longLine = lineSeed.repeat(bigNum);
   const cmd = `${process.execPath} ${__filename} child ${exponent} ${bigNum}`;
@@ -23,5 +23,3 @@ if (process.argv[2] === 'child') {
 
   assert.strictEqual(stdout, longLine, `failed with exponent ${exponent}`);
 });
-
-