Browse Source

process: flush stdout/stderr upon `process.exit()`

process-exit-stdio-flushing
Jeremiah Senkpiel 9 years ago
parent
commit
2f6420ba38
  1. 2
      deps/uv/include/uv.h
  2. 15
      deps/uv/src/unix/stream.c
  3. 62
      lib/internal/process.js
  4. 17
      src/stream_wrap.cc
  5. 1
      src/stream_wrap.h
  6. 1
      test/known_issues/known_issues.status
  7. 4
      test/parallel/test-stdout-buffer-flush-on-exit.js

2
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

15
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.

62
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) {

17
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);

1
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

1
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]

4
test/known_issues/test-stdout-buffer-flush-on-exit.js → 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}`);
});
Loading…
Cancel
Save