mirror of https://github.com/lukechilds/node.git
Browse Source
PR-URL: https://github.com/nodejs/node/pull/5103 Reviewed-By: Trevor Norris <trev.norris@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>process-exit-stdio-flushing
9 changed files with 618 additions and 588 deletions
@ -0,0 +1,186 @@ |
|||
'use strict'; |
|||
|
|||
var _lazyConstants = null; |
|||
|
|||
function lazyConstants() { |
|||
if (!_lazyConstants) { |
|||
_lazyConstants = process.binding('constants'); |
|||
} |
|||
return _lazyConstants; |
|||
} |
|||
|
|||
exports.setup_hrtime = setup_hrtime; |
|||
exports.setupConfig = setupConfig; |
|||
exports.setupKillAndExit = setupKillAndExit; |
|||
exports.setupSignalHandlers = setupSignalHandlers; |
|||
exports.setupChannel = setupChannel; |
|||
exports.setupRawDebug = setupRawDebug; |
|||
|
|||
|
|||
const assert = process.assert = function(x, msg) { |
|||
if (!x) throw new Error(msg || 'assertion error'); |
|||
}; |
|||
|
|||
|
|||
function setup_hrtime() { |
|||
const _hrtime = process.hrtime; |
|||
const hrValues = new Uint32Array(3); |
|||
|
|||
process.hrtime = function hrtime(ar) { |
|||
_hrtime(hrValues); |
|||
|
|||
if (typeof ar !== 'undefined') { |
|||
if (Array.isArray(ar)) { |
|||
const sec = (hrValues[0] * 0x100000000 + hrValues[1]) - ar[0]; |
|||
const nsec = hrValues[2] - ar[1]; |
|||
return [nsec < 0 ? sec - 1 : sec, nsec < 0 ? nsec + 1e9 : nsec]; |
|||
} |
|||
|
|||
throw new TypeError('process.hrtime() only accepts an Array tuple'); |
|||
} |
|||
|
|||
return [ |
|||
hrValues[0] * 0x100000000 + hrValues[1], |
|||
hrValues[2] |
|||
]; |
|||
}; |
|||
} |
|||
|
|||
|
|||
function setupConfig(_source) { |
|||
// NativeModule._source
|
|||
// used for `process.config`, but not a real module
|
|||
var config = _source.config; |
|||
delete _source.config; |
|||
|
|||
// strip the gyp comment line at the beginning
|
|||
config = config.split('\n') |
|||
.slice(1) |
|||
.join('\n') |
|||
.replace(/"/g, '\\"') |
|||
.replace(/'/g, '"'); |
|||
|
|||
process.config = JSON.parse(config, function(key, value) { |
|||
if (value === 'true') return true; |
|||
if (value === 'false') return false; |
|||
return value; |
|||
}); |
|||
} |
|||
|
|||
|
|||
function setupKillAndExit() { |
|||
|
|||
process.exit = function(code) { |
|||
if (code || code === 0) |
|||
process.exitCode = code; |
|||
|
|||
if (!process._exiting) { |
|||
process._exiting = true; |
|||
process.emit('exit', process.exitCode || 0); |
|||
} |
|||
process.reallyExit(process.exitCode || 0); |
|||
}; |
|||
|
|||
process.kill = function(pid, sig) { |
|||
var err; |
|||
|
|||
if (pid != (pid | 0)) { |
|||
throw new TypeError('invalid pid'); |
|||
} |
|||
|
|||
// preserve null signal
|
|||
if (0 === sig) { |
|||
err = process._kill(pid, 0); |
|||
} else { |
|||
sig = sig || 'SIGTERM'; |
|||
if (lazyConstants()[sig] && |
|||
sig.slice(0, 3) === 'SIG') { |
|||
err = process._kill(pid, lazyConstants()[sig]); |
|||
} else { |
|||
throw new Error(`Unknown signal: ${sig}`); |
|||
} |
|||
} |
|||
|
|||
if (err) { |
|||
var errnoException = require('util')._errnoException; |
|||
throw errnoException(err, 'kill'); |
|||
} |
|||
|
|||
return true; |
|||
}; |
|||
} |
|||
|
|||
|
|||
function setupSignalHandlers() { |
|||
// Load events module in order to access prototype elements on process like
|
|||
// process.addListener.
|
|||
var signalWraps = {}; |
|||
|
|||
function isSignal(event) { |
|||
return typeof event === 'string' && |
|||
event.slice(0, 3) === 'SIG' && |
|||
lazyConstants().hasOwnProperty(event); |
|||
} |
|||
|
|||
// Detect presence of a listener for the special signal types
|
|||
process.on('newListener', function(type, listener) { |
|||
if (isSignal(type) && |
|||
!signalWraps.hasOwnProperty(type)) { |
|||
var Signal = process.binding('signal_wrap').Signal; |
|||
var wrap = new Signal(); |
|||
|
|||
wrap.unref(); |
|||
|
|||
wrap.onsignal = function() { process.emit(type); }; |
|||
|
|||
var signum = lazyConstants()[type]; |
|||
var err = wrap.start(signum); |
|||
if (err) { |
|||
wrap.close(); |
|||
var errnoException = require('util')._errnoException; |
|||
throw errnoException(err, 'uv_signal_start'); |
|||
} |
|||
|
|||
signalWraps[type] = wrap; |
|||
} |
|||
}); |
|||
|
|||
process.on('removeListener', function(type, listener) { |
|||
if (signalWraps.hasOwnProperty(type) && this.listenerCount(type) === 0) { |
|||
signalWraps[type].close(); |
|||
delete signalWraps[type]; |
|||
} |
|||
}); |
|||
} |
|||
|
|||
|
|||
function setupChannel() { |
|||
// If we were spawned with env NODE_CHANNEL_FD then load that up and
|
|||
// start parsing data from that stream.
|
|||
if (process.env.NODE_CHANNEL_FD) { |
|||
var fd = parseInt(process.env.NODE_CHANNEL_FD, 10); |
|||
assert(fd >= 0); |
|||
|
|||
// Make sure it's not accidentally inherited by child processes.
|
|||
delete process.env.NODE_CHANNEL_FD; |
|||
|
|||
var cp = require('child_process'); |
|||
|
|||
// Load tcp_wrap to avoid situation where we might immediately receive
|
|||
// a message.
|
|||
// FIXME is this really necessary?
|
|||
process.binding('tcp_wrap'); |
|||
|
|||
cp._forkChild(fd); |
|||
assert(process.send); |
|||
} |
|||
} |
|||
|
|||
|
|||
function setupRawDebug() { |
|||
var format = require('util').format; |
|||
var rawDebug = process._rawDebug; |
|||
process._rawDebug = function() { |
|||
rawDebug(format.apply(null, arguments)); |
|||
}; |
|||
} |
@ -0,0 +1,157 @@ |
|||
'use strict'; |
|||
|
|||
exports.setup = setupNextTick; |
|||
|
|||
function setupNextTick() { |
|||
const promises = require('internal/process/promises'); |
|||
const emitPendingUnhandledRejections = promises.setup(scheduleMicrotasks); |
|||
var nextTickQueue = []; |
|||
var microtasksScheduled = false; |
|||
|
|||
// Used to run V8's micro task queue.
|
|||
var _runMicrotasks = {}; |
|||
|
|||
// *Must* match Environment::TickInfo::Fields in src/env.h.
|
|||
var kIndex = 0; |
|||
var kLength = 1; |
|||
|
|||
process.nextTick = nextTick; |
|||
// Needs to be accessible from beyond this scope.
|
|||
process._tickCallback = _tickCallback; |
|||
process._tickDomainCallback = _tickDomainCallback; |
|||
|
|||
// This tickInfo thing is used so that the C++ code in src/node.cc
|
|||
// can have easy access to our nextTick state, and avoid unnecessary
|
|||
// calls into JS land.
|
|||
const tickInfo = process._setupNextTick(_tickCallback, _runMicrotasks); |
|||
|
|||
_runMicrotasks = _runMicrotasks.runMicrotasks; |
|||
|
|||
function tickDone() { |
|||
if (tickInfo[kLength] !== 0) { |
|||
if (tickInfo[kLength] <= tickInfo[kIndex]) { |
|||
nextTickQueue = []; |
|||
tickInfo[kLength] = 0; |
|||
} else { |
|||
nextTickQueue.splice(0, tickInfo[kIndex]); |
|||
tickInfo[kLength] = nextTickQueue.length; |
|||
} |
|||
} |
|||
tickInfo[kIndex] = 0; |
|||
} |
|||
|
|||
function scheduleMicrotasks() { |
|||
if (microtasksScheduled) |
|||
return; |
|||
|
|||
nextTickQueue.push({ |
|||
callback: runMicrotasksCallback, |
|||
domain: null |
|||
}); |
|||
|
|||
tickInfo[kLength]++; |
|||
microtasksScheduled = true; |
|||
} |
|||
|
|||
function runMicrotasksCallback() { |
|||
microtasksScheduled = false; |
|||
_runMicrotasks(); |
|||
|
|||
if (tickInfo[kIndex] < tickInfo[kLength] || |
|||
emitPendingUnhandledRejections()) |
|||
scheduleMicrotasks(); |
|||
} |
|||
|
|||
function _combinedTickCallback(args, callback) { |
|||
if (args === undefined) { |
|||
callback(); |
|||
} else { |
|||
switch (args.length) { |
|||
case 1: |
|||
callback(args[0]); |
|||
break; |
|||
case 2: |
|||
callback(args[0], args[1]); |
|||
break; |
|||
case 3: |
|||
callback(args[0], args[1], args[2]); |
|||
break; |
|||
default: |
|||
callback.apply(null, args); |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Run callbacks that have no domain.
|
|||
// Using domains will cause this to be overridden.
|
|||
function _tickCallback() { |
|||
var callback, args, tock; |
|||
|
|||
do { |
|||
while (tickInfo[kIndex] < tickInfo[kLength]) { |
|||
tock = nextTickQueue[tickInfo[kIndex]++]; |
|||
callback = tock.callback; |
|||
args = tock.args; |
|||
// Using separate callback execution functions allows direct
|
|||
// callback invocation with small numbers of arguments to avoid the
|
|||
// performance hit associated with using `fn.apply()`
|
|||
_combinedTickCallback(args, callback); |
|||
if (1e4 < tickInfo[kIndex]) |
|||
tickDone(); |
|||
} |
|||
tickDone(); |
|||
_runMicrotasks(); |
|||
emitPendingUnhandledRejections(); |
|||
} while (tickInfo[kLength] !== 0); |
|||
} |
|||
|
|||
function _tickDomainCallback() { |
|||
var callback, domain, args, tock; |
|||
|
|||
do { |
|||
while (tickInfo[kIndex] < tickInfo[kLength]) { |
|||
tock = nextTickQueue[tickInfo[kIndex]++]; |
|||
callback = tock.callback; |
|||
domain = tock.domain; |
|||
args = tock.args; |
|||
if (domain) |
|||
domain.enter(); |
|||
// Using separate callback execution functions allows direct
|
|||
// callback invocation with small numbers of arguments to avoid the
|
|||
// performance hit associated with using `fn.apply()`
|
|||
_combinedTickCallback(args, callback); |
|||
if (1e4 < tickInfo[kIndex]) |
|||
tickDone(); |
|||
if (domain) |
|||
domain.exit(); |
|||
} |
|||
tickDone(); |
|||
_runMicrotasks(); |
|||
emitPendingUnhandledRejections(); |
|||
} while (tickInfo[kLength] !== 0); |
|||
} |
|||
|
|||
function TickObject(c, args) { |
|||
this.callback = c; |
|||
this.domain = process.domain || null; |
|||
this.args = args; |
|||
} |
|||
|
|||
function nextTick(callback) { |
|||
if (typeof callback !== 'function') |
|||
throw new TypeError('callback is not a function'); |
|||
// on the way out, don't bother. it won't get fired anyway.
|
|||
if (process._exiting) |
|||
return; |
|||
|
|||
var args; |
|||
if (arguments.length > 1) { |
|||
args = new Array(arguments.length - 1); |
|||
for (var i = 1; i < arguments.length; i++) |
|||
args[i - 1] = arguments[i]; |
|||
} |
|||
|
|||
nextTickQueue.push(new TickObject(callback, args)); |
|||
tickInfo[kLength]++; |
|||
} |
|||
} |
@ -0,0 +1,61 @@ |
|||
'use strict'; |
|||
|
|||
const promiseRejectEvent = process._promiseRejectEvent; |
|||
const hasBeenNotifiedProperty = new WeakMap(); |
|||
const pendingUnhandledRejections = []; |
|||
|
|||
exports.setup = setupPromises; |
|||
|
|||
function setupPromises(scheduleMicrotasks) { |
|||
process._setupPromises(function(event, promise, reason) { |
|||
if (event === promiseRejectEvent.unhandled) |
|||
unhandledRejection(promise, reason); |
|||
else if (event === promiseRejectEvent.handled) |
|||
rejectionHandled(promise); |
|||
else |
|||
require('assert').fail('unexpected PromiseRejectEvent'); |
|||
}); |
|||
|
|||
function unhandledRejection(promise, reason) { |
|||
hasBeenNotifiedProperty.set(promise, false); |
|||
addPendingUnhandledRejection(promise, reason); |
|||
} |
|||
|
|||
function rejectionHandled(promise) { |
|||
var hasBeenNotified = hasBeenNotifiedProperty.get(promise); |
|||
if (hasBeenNotified !== undefined) { |
|||
hasBeenNotifiedProperty.delete(promise); |
|||
if (hasBeenNotified === true) { |
|||
process.nextTick(function() { |
|||
process.emit('rejectionHandled', promise); |
|||
}); |
|||
} |
|||
|
|||
} |
|||
} |
|||
|
|||
function emitPendingUnhandledRejections() { |
|||
var hadListeners = false; |
|||
while (pendingUnhandledRejections.length > 0) { |
|||
var promise = pendingUnhandledRejections.shift(); |
|||
var reason = pendingUnhandledRejections.shift(); |
|||
if (hasBeenNotifiedProperty.get(promise) === false) { |
|||
hasBeenNotifiedProperty.set(promise, true); |
|||
if (!process.emit('unhandledRejection', reason, promise)) { |
|||
// Nobody is listening.
|
|||
// TODO(petkaantonov) Take some default action, see #830
|
|||
} else { |
|||
hadListeners = true; |
|||
} |
|||
} |
|||
} |
|||
return hadListeners; |
|||
} |
|||
|
|||
function addPendingUnhandledRejection(promise, reason) { |
|||
pendingUnhandledRejections.push(promise, reason); |
|||
scheduleMicrotasks(); |
|||
} |
|||
|
|||
return emitPendingUnhandledRejections; |
|||
} |
@ -0,0 +1,161 @@ |
|||
'use strict'; |
|||
|
|||
exports.setup = setupStdio; |
|||
|
|||
function setupStdio() { |
|||
var stdin, stdout, stderr; |
|||
|
|||
process.__defineGetter__('stdout', function() { |
|||
if (stdout) return stdout; |
|||
stdout = createWritableStdioStream(1); |
|||
stdout.destroy = stdout.destroySoon = function(er) { |
|||
er = er || new Error('process.stdout cannot be closed.'); |
|||
stdout.emit('error', er); |
|||
}; |
|||
if (stdout.isTTY) { |
|||
process.on('SIGWINCH', function() { |
|||
stdout._refreshSize(); |
|||
}); |
|||
} |
|||
return stdout; |
|||
}); |
|||
|
|||
process.__defineGetter__('stderr', function() { |
|||
if (stderr) return stderr; |
|||
stderr = createWritableStdioStream(2); |
|||
stderr.destroy = stderr.destroySoon = function(er) { |
|||
er = er || new Error('process.stderr cannot be closed.'); |
|||
stderr.emit('error', er); |
|||
}; |
|||
if (stderr.isTTY) { |
|||
process.on('SIGWINCH', function() { |
|||
stderr._refreshSize(); |
|||
}); |
|||
} |
|||
return stderr; |
|||
}); |
|||
|
|||
process.__defineGetter__('stdin', function() { |
|||
if (stdin) return stdin; |
|||
|
|||
var tty_wrap = process.binding('tty_wrap'); |
|||
var fd = 0; |
|||
|
|||
switch (tty_wrap.guessHandleType(fd)) { |
|||
case 'TTY': |
|||
var tty = require('tty'); |
|||
stdin = new tty.ReadStream(fd, { |
|||
highWaterMark: 0, |
|||
readable: true, |
|||
writable: false |
|||
}); |
|||
break; |
|||
|
|||
case 'FILE': |
|||
var fs = require('fs'); |
|||
stdin = new fs.ReadStream(null, { fd: fd, autoClose: false }); |
|||
break; |
|||
|
|||
case 'PIPE': |
|||
case 'TCP': |
|||
var net = require('net'); |
|||
|
|||
// It could be that process has been started with an IPC channel
|
|||
// sitting on fd=0, in such case the pipe for this fd is already
|
|||
// present and creating a new one will lead to the assertion failure
|
|||
// in libuv.
|
|||
if (process._channel && process._channel.fd === fd) { |
|||
stdin = new net.Socket({ |
|||
handle: process._channel, |
|||
readable: true, |
|||
writable: false |
|||
}); |
|||
} else { |
|||
stdin = new net.Socket({ |
|||
fd: fd, |
|||
readable: true, |
|||
writable: false |
|||
}); |
|||
} |
|||
// Make sure the stdin can't be `.end()`-ed
|
|||
stdin._writableState.ended = true; |
|||
break; |
|||
|
|||
default: |
|||
// Probably an error on in uv_guess_handle()
|
|||
throw new Error('Implement me. Unknown stdin file type!'); |
|||
} |
|||
|
|||
// For supporting legacy API we put the FD here.
|
|||
stdin.fd = fd; |
|||
|
|||
// stdin starts out life in a paused state, but node doesn't
|
|||
// know yet. Explicitly to readStop() it to put it in the
|
|||
// not-reading state.
|
|||
if (stdin._handle && stdin._handle.readStop) { |
|||
stdin._handle.reading = false; |
|||
stdin._readableState.reading = false; |
|||
stdin._handle.readStop(); |
|||
} |
|||
|
|||
// if the user calls stdin.pause(), then we need to stop reading
|
|||
// immediately, so that the process can close down.
|
|||
stdin.on('pause', function() { |
|||
if (!stdin._handle) |
|||
return; |
|||
stdin._readableState.reading = false; |
|||
stdin._handle.reading = false; |
|||
stdin._handle.readStop(); |
|||
}); |
|||
|
|||
return stdin; |
|||
}); |
|||
|
|||
process.openStdin = function() { |
|||
process.stdin.resume(); |
|||
return process.stdin; |
|||
}; |
|||
} |
|||
|
|||
function createWritableStdioStream(fd) { |
|||
var stream; |
|||
var tty_wrap = process.binding('tty_wrap'); |
|||
|
|||
// Note stream._type is used for test-module-load-list.js
|
|||
|
|||
switch (tty_wrap.guessHandleType(fd)) { |
|||
case 'TTY': |
|||
var tty = require('tty'); |
|||
stream = new tty.WriteStream(fd); |
|||
stream._type = 'tty'; |
|||
break; |
|||
|
|||
case 'FILE': |
|||
var fs = require('fs'); |
|||
stream = new fs.SyncWriteStream(fd, { autoClose: false }); |
|||
stream._type = 'fs'; |
|||
break; |
|||
|
|||
case 'PIPE': |
|||
case 'TCP': |
|||
var net = require('net'); |
|||
stream = new net.Socket({ |
|||
fd: fd, |
|||
readable: false, |
|||
writable: true |
|||
}); |
|||
stream._type = 'pipe'; |
|||
break; |
|||
|
|||
default: |
|||
// Probably an error on in uv_guess_handle()
|
|||
throw new Error('Implement me. Unknown stream file type!'); |
|||
} |
|||
|
|||
// For supporting legacy API we put the FD here.
|
|||
stream.fd = fd; |
|||
|
|||
stream._isStdio = true; |
|||
|
|||
return stream; |
|||
} |
Loading…
Reference in new issue