Skip to content

Commit

Permalink
process: flush stdout/stderr upon process.exit()
Browse files Browse the repository at this point in the history
  • Loading branch information
Fishrock123 committed May 16, 2016
1 parent f4f6c6e commit 2f6420b
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 4 deletions.
2 changes: 2 additions & 0 deletions deps/uv/include/uv.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 15 additions & 0 deletions deps/uv/src/unix/stream.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
62 changes: 62 additions & 0 deletions lib/internal/process.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
17 changes: 17 additions & 0 deletions src/stream_wrap.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions src/stream_wrap.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion test/known_issues/known_issues.status
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,11 @@ 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}`;
const stdout = execSync(cmd).toString().trim();

assert.strictEqual(stdout, longLine, `failed with exponent ${exponent}`);
});


0 comments on commit 2f6420b

Please sign in to comment.