Skip to content

Commit 543c2e0

Browse files
committed
process: flush stdout/stderr upon process.exit()
1 parent f4f6c6e commit 543c2e0

File tree

7 files changed

+95
-3
lines changed

7 files changed

+95
-3
lines changed

deps/uv/include/uv.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,8 @@ UV_EXTERN int uv_try_write(uv_stream_t* handle,
483483
const uv_buf_t bufs[],
484484
unsigned int nbufs);
485485

486+
UV_EXTERN int uv_flush_sync(uv_stream_t* stream);
487+
486488
/* uv_write_t is a subclass of uv_req_t. */
487489
struct uv_write_s {
488490
UV_REQ_FIELDS

deps/uv/src/unix/stream.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1625,6 +1625,21 @@ void uv__stream_close(uv_stream_t* handle) {
16251625
}
16261626

16271627

1628+
/* Have stream block and then synchronously flush queued writes.
1629+
* This function works without an event loop.
1630+
* Intended to be used just prior to exit().
1631+
* Returns 0 on success, non-zero on failure.
1632+
*/
1633+
int uv_flush_sync(uv_stream_t* stream) {
1634+
int rc = uv_stream_set_blocking(stream, 1);
1635+
if (rc == 0) {
1636+
uv__write(stream);
1637+
rc = (int)stream->write_queue_size;
1638+
}
1639+
return rc;
1640+
}
1641+
1642+
16281643
int uv_stream_set_blocking(uv_stream_t* handle, int blocking) {
16291644
/* Don't need to check the file descriptor, uv__nonblock()
16301645
* will fail with EBADF if it's not valid.

lib/internal/process.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,67 @@ function setupKillAndExit() {
145145
process._exiting = true;
146146
process.emit('exit', process.exitCode || 0);
147147
}
148+
149+
// Flush stdio streams prior to exit.
150+
// `flushSync` not present if stream redirected to file in shell.
151+
flushSync(process.stdout);
152+
flushSync(process.stderr);
153+
148154
process.reallyExit(process.exitCode || 0);
155+
156+
function flushSync(stream) {
157+
158+
// FIXME: Behavior of this function outside of process.exit() is
159+
// undefined due to the following factors:
160+
// * Stream fd may be blocking after this call.
161+
// * In the event of an incomplete flush pending buffered write
162+
// requests may be truncated.
163+
// * No return code.
164+
165+
if (stream._writev)
166+
return;
167+
168+
var handle = stream._handle;
169+
if (!handle || !handle.flushSync)
170+
return;
171+
172+
var fd = handle.fd;
173+
if (typeof fd !== 'number' || fd < 0)
174+
return;
175+
176+
// FIXME: late module resolution avoids cross require problem
177+
const fs = require('fs');
178+
179+
// Queued libuv writes must be flushed first.
180+
// Note: fd will set to blocking after handle.flushSync()
181+
if (handle.flushSync() !== 0) {
182+
// bad fd or write queue for libuv stream not entirely flushed
183+
return;
184+
}
185+
186+
// then the queued stream chunks can be flushed
187+
var state = stream._writableState;
188+
var entry = state.bufferedRequest;
189+
while (entry) {
190+
var chunk = entry.chunk;
191+
if (!(chunk instanceof Buffer)) {
192+
chunk = Buffer.from(chunk, entry.encoding);
193+
}
194+
// Note: fd is blocking at this point
195+
var written = fs.writeSync(fd, chunk, 0, chunk.length);
196+
if (written !== chunk.length) {
197+
// stream chunk not flushed entirely - stop writing.
198+
// FIXME: buffered request queue should be repaired here
199+
// rather than being truncated after loop break
200+
break;
201+
}
202+
entry = entry.next;
203+
}
204+
205+
state.bufferedRequestCount = 0;
206+
state.bufferedRequest = null;
207+
state.lastBufferedRequest = null;
208+
}
149209
};
150210

151211
process.kill = function(pid, sig) {

src/stream_wrap.cc

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ StreamWrap::StreamWrap(Environment* env,
7777
void StreamWrap::AddMethods(Environment* env,
7878
v8::Local<v8::FunctionTemplate> target,
7979
int flags) {
80+
env->SetProtoMethod(target, "flushSync", FlushSync);
8081
env->SetProtoMethod(target, "setBlocking", SetBlocking);
8182
StreamBase::AddMethods<StreamWrap>(env, target, flags);
8283
}
@@ -273,6 +274,22 @@ void StreamWrap::SetBlocking(const FunctionCallbackInfo<Value>& args) {
273274
}
274275

275276

277+
void StreamWrap::FlushSync(const FunctionCallbackInfo<Value>& args) {
278+
StreamWrap* wrap = Unwrap<StreamWrap>(args.Holder());
279+
280+
if (!wrap->IsAlive())
281+
return args.GetReturnValue().Set(UV_EINVAL);
282+
283+
#if defined(_WIN32)
284+
int rc = 0;
285+
#else
286+
int rc = uv_flush_sync(wrap->stream());
287+
#endif
288+
289+
args.GetReturnValue().Set(rc);
290+
}
291+
292+
276293
int StreamWrap::DoShutdown(ShutdownWrap* req_wrap) {
277294
int err;
278295
err = uv_shutdown(&req_wrap->req_, stream(), AfterShutdown);

src/stream_wrap.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ class StreamWrap : public HandleWrap, public StreamBase {
7272
int flags = StreamBase::kFlagNone);
7373

7474
private:
75+
static void FlushSync(const v8::FunctionCallbackInfo<v8::Value>& args);
7576
static void SetBlocking(const v8::FunctionCallbackInfo<v8::Value>& args);
7677

7778
// Callbacks for libuv

test/known_issues/known_issues.status

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ prefix known_issues
77
[true] # This section applies to all platforms
88

99
[$system==win32]
10-
test-stdout-buffer-flush-on-exit: SKIP
1110

1211
[$system==linux]
1312

test/known_issues/test-stdout-buffer-flush-on-exit.js renamed to test/parallel/test-stdout-buffer-flush-on-exit.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,3 @@ if (process.argv[2] === 'child') {
2323

2424
assert.strictEqual(stdout, longLine, `failed with exponent ${exponent}`);
2525
});
26-
27-

0 commit comments

Comments
 (0)