bootstrap: use different scripts to setup different configurations

This patch splits the handling of `isMainThread` and
`ownsProcessState` from conditionals in
`lib/internal/bootstrap/node.js` into different scripts under
`lib/internal/bootstrap/switches/`, and call them accordingly
from C++ after `node.js` is run.

This:

- Creates a common denominator of the main thread and the worker
  thread bootstrap that can be snapshotted and shared by
  both.
- Makes it possible to override the configurations on-the-fly.

PR-URL: https://github.com/nodejs/node/pull/30862
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
This commit is contained in:
Joyee Cheung
2019-12-09 10:53:02 +08:00
parent 277f343689
commit c63d511c13
14 changed files with 565 additions and 545 deletions

View File

@@ -3,9 +3,7 @@
// This file is invoked by `node::RunBootstrapping()` in `src/node.cc`, and is
// responsible for setting up node.js core before executing main scripts
// under `lib/internal/main/`.
// This file is currently run to bootstrap both the main thread and the worker
// threads. Some setups are conditional, controlled with isMainThread and
// ownsProcessState.
//
// This file is expected not to perform any asynchronous operations itself
// when being executed - those should be done in either
// `lib/internal/bootstrap/pre_execution.js` or in main scripts. The majority
@@ -22,16 +20,21 @@
// module loaders, including `process.binding()`, `process._linkedBinding()`,
// `internalBinding()` and `NativeModule`.
//
// After this file is run, one of the main scripts under `lib/internal/main/`
// will be selected by C++ to start the actual execution. The main scripts may
// run additional setups exported by `lib/internal/bootstrap/pre_execution.js`,
// depending on the execution mode.
// This file is run to bootstrap both the main thread and the worker threads.
// After this file is run, certain properties are setup according to the
// configuration of the Node.js instance using the files in
// `lib/internal/bootstrap/switches/`.
//
// Then, depending on how the Node.js instance is launched, one of the main
// scripts in `lib/internal/main` will be selected by C++ to start the actual
// execution. They may run additional setups exported by
// `lib/internal/bootstrap/pre_execution.js` depending on the runtime states.
'use strict';
// This file is compiled as if it's wrapped in a function with arguments
// passed by node::RunBootstrapping()
/* global process, require, internalBinding, isMainThread, ownsProcessState */
/* global process, require, internalBinding */
setupPrepareStackTrace();
@@ -54,48 +57,12 @@ setupBuffer();
process.domain = null;
process._exiting = false;
// Bootstrappers for all threads, including worker threads and main thread
const perThreadSetup = require('internal/process/per_thread');
// Bootstrappers for the main thread only
let mainThreadSetup;
// Bootstrappers for the worker threads only
let workerThreadSetup;
if (ownsProcessState) {
mainThreadSetup = require(
'internal/process/main_thread_only'
);
} else {
workerThreadSetup = require(
'internal/process/worker_thread_only'
);
}
// process.config is serialized config.gypi
process.config = JSONParse(internalBinding('native_module').config);
// Bootstrappers for all threads, including worker threads and main thread
const perThreadSetup = require('internal/process/per_thread');
const rawMethods = internalBinding('process_methods');
// Set up methods and events on the process object for the main thread
if (isMainThread) {
process.abort = rawMethods.abort;
const wrapped = mainThreadSetup.wrapProcessMethods(rawMethods);
process.umask = wrapped.umask;
process.chdir = wrapped.chdir;
process.cwd = wrapped.cwd;
// TODO(joyeecheung): deprecate and remove these underscore methods
process._debugProcess = rawMethods._debugProcess;
process._debugEnd = rawMethods._debugEnd;
process._startProfilerIdleNotifier =
rawMethods._startProfilerIdleNotifier;
process._stopProfilerIdleNotifier = rawMethods._stopProfilerIdleNotifier;
} else {
const wrapped = workerThreadSetup.wrapProcessMethods(rawMethods);
process.abort = workerThreadSetup.unavailable('process.abort()');
process.chdir = workerThreadSetup.unavailable('process.chdir()');
process.umask = wrapped.umask;
process.cwd = rawMethods.cwd;
}
// Set up methods on the process object for all threads
{
@@ -119,6 +86,11 @@ if (isMainThread) {
process.memoryUsage = wrapped.memoryUsage;
process.kill = wrapped.kill;
process.exit = wrapped.exit;
process.openStdin = function() {
process.stdin.resume();
return process.stdin;
};
}
const credentials = internalBinding('credentials');
@@ -128,34 +100,6 @@ if (credentials.implementsPosixCredentials) {
process.getgid = credentials.getgid;
process.getegid = credentials.getegid;
process.getgroups = credentials.getgroups;
if (ownsProcessState) {
const wrapped = mainThreadSetup.wrapPosixCredentialSetters(credentials);
process.initgroups = wrapped.initgroups;
process.setgroups = wrapped.setgroups;
process.setegid = wrapped.setegid;
process.seteuid = wrapped.seteuid;
process.setgid = wrapped.setgid;
process.setuid = wrapped.setuid;
} else {
process.initgroups =
workerThreadSetup.unavailable('process.initgroups()');
process.setgroups = workerThreadSetup.unavailable('process.setgroups()');
process.setegid = workerThreadSetup.unavailable('process.setegid()');
process.seteuid = workerThreadSetup.unavailable('process.seteuid()');
process.setgid = workerThreadSetup.unavailable('process.setgid()');
process.setuid = workerThreadSetup.unavailable('process.setuid()');
}
}
if (isMainThread) {
const { getStdout, getStdin, getStderr } =
require('internal/process/stdio').getMainThreadStdio();
setupProcessStdio(getStdout, getStdin, getStderr);
} else {
const { getStdout, getStdin, getStderr } =
workerThreadSetup.createStdioGetters();
setupProcessStdio(getStdout, getStdin, getStderr);
}
// Setup the callbacks that node::AsyncWrap will call when there are hooks to
@@ -343,31 +287,6 @@ function setupProcessObject() {
});
}
function setupProcessStdio(getStdout, getStdin, getStderr) {
ObjectDefineProperty(process, 'stdout', {
configurable: true,
enumerable: true,
get: getStdout
});
ObjectDefineProperty(process, 'stderr', {
configurable: true,
enumerable: true,
get: getStderr
});
ObjectDefineProperty(process, 'stdin', {
configurable: true,
enumerable: true,
get: getStdin
});
process.openStdin = function() {
process.stdin.resume();
return process.stdin;
};
}
function setupGlobalProxy() {
ObjectDefineProperty(global, SymbolToStringTag, {
value: 'global',

View File

@@ -36,9 +36,6 @@ function prepareMainThreadExecution(expandArgv1 = false) {
setupDebugEnv();
// Only main thread receives signals.
setupSignalHandlers();
// Process initial diagnostic reporting configuration, if present.
initializeReport();
initializeReportSignalHandlers(); // Main-thread-only.
@@ -174,20 +171,7 @@ function setupDebugEnv() {
}
}
function setupSignalHandlers() {
const {
createSignalHandlers
} = require('internal/process/main_thread_only');
const {
startListeningIfSignal,
stopListeningIfSignal
} = createSignalHandlers();
process.on('newListener', startListeningIfSignal);
process.on('removeListener', stopListeningIfSignal);
}
// This has to be called after both initializeReport() and
// setupSignalHandlers() are called
// This has to be called after initializeReport() is called
function initializeReportSignalHandlers() {
if (!getOptionValue('--experimental-report')) {
return;

View File

@@ -0,0 +1,18 @@
'use strict';
const credentials = internalBinding('credentials');
if (credentials.implementsPosixCredentials) {
// TODO: this should be detached from ERR_WORKER_UNSUPPORTED_OPERATION
const { unavailable } = require('internal/process/worker_thread_only');
process.initgroups = unavailable('process.initgroups()');
process.setgroups = unavailable('process.setgroups()');
process.setegid = unavailable('process.setegid()');
process.seteuid = unavailable('process.seteuid()');
process.setgid = unavailable('process.setgid()');
process.setuid = unavailable('process.setuid()');
}
// ---- keep the attachment of the wrappers above so that it's easier to ----
// ---- compare the setups side-by-side -----

View File

@@ -0,0 +1,96 @@
'use strict';
const credentials = internalBinding('credentials');
if (credentials.implementsPosixCredentials) {
const wrapped = wrapPosixCredentialSetters(credentials);
process.initgroups = wrapped.initgroups;
process.setgroups = wrapped.setgroups;
process.setegid = wrapped.setegid;
process.seteuid = wrapped.seteuid;
process.setgid = wrapped.setgid;
process.setuid = wrapped.setuid;
}
// ---- keep the attachment of the wrappers above so that it's easier to ----
// ---- compare the setups side-by-side -----
function wrapPosixCredentialSetters(credentials) {
const {
ArrayIsArray,
} = primordials;
const {
codes: {
ERR_INVALID_ARG_TYPE,
ERR_UNKNOWN_CREDENTIAL
}
} = require('internal/errors');
const {
validateUint32
} = require('internal/validators');
const {
initgroups: _initgroups,
setgroups: _setgroups,
setegid: _setegid,
seteuid: _seteuid,
setgid: _setgid,
setuid: _setuid
} = credentials;
function initgroups(user, extraGroup) {
validateId(user, 'user');
validateId(extraGroup, 'extraGroup');
// Result is 0 on success, 1 if user is unknown, 2 if group is unknown.
const result = _initgroups(user, extraGroup);
if (result === 1) {
throw new ERR_UNKNOWN_CREDENTIAL('User', user);
} else if (result === 2) {
throw new ERR_UNKNOWN_CREDENTIAL('Group', extraGroup);
}
}
function setgroups(groups) {
if (!ArrayIsArray(groups)) {
throw new ERR_INVALID_ARG_TYPE('groups', 'Array', groups);
}
for (let i = 0; i < groups.length; i++) {
validateId(groups[i], `groups[${i}]`);
}
// Result is 0 on success. A positive integer indicates that the
// corresponding group was not found.
const result = _setgroups(groups);
if (result > 0) {
throw new ERR_UNKNOWN_CREDENTIAL('Group', groups[result - 1]);
}
}
function wrapIdSetter(type, method) {
return function(id) {
validateId(id, 'id');
// Result is 0 on success, 1 if credential is unknown.
const result = method(id);
if (result === 1) {
throw new ERR_UNKNOWN_CREDENTIAL(type, id);
}
};
}
function validateId(id, name) {
if (typeof id === 'number') {
validateUint32(id, name);
} else if (typeof id !== 'string') {
throw new ERR_INVALID_ARG_TYPE(name, ['number', 'string'], id);
}
}
return {
initgroups,
setgroups,
setegid: wrapIdSetter('Group', _setegid),
seteuid: wrapIdSetter('User', _seteuid),
setgid: wrapIdSetter('Group', _setgid),
setuid: wrapIdSetter('User', _setuid)
};
}

View File

@@ -0,0 +1,263 @@
'use strict';
const { ObjectDefineProperty } = primordials;
const rawMethods = internalBinding('process_methods');
process.abort = rawMethods.abort;
process.umask = wrappedUmask;
process.chdir = wrappedChdir;
process.cwd = wrappedCwd;
// TODO(joyeecheung): deprecate and remove these underscore methods
process._debugProcess = rawMethods._debugProcess;
process._debugEnd = rawMethods._debugEnd;
process._startProfilerIdleNotifier = rawMethods._startProfilerIdleNotifier;
process._stopProfilerIdleNotifier = rawMethods._stopProfilerIdleNotifier;
function defineStream(name, getter) {
ObjectDefineProperty(process, name, {
configurable: true,
enumerable: true,
get: getter
});
}
defineStream('stdout', getStdout);
defineStream('stdin', getStdin);
defineStream('stderr', getStderr);
// Worker threads don't receive signals.
const {
startListeningIfSignal,
stopListeningIfSignal
} = require('internal/process/signal');
process.on('newListener', startListeningIfSignal);
process.on('removeListener', stopListeningIfSignal);
// ---- keep the attachment of the wrappers above so that it's easier to ----
// ---- compare the setups side-by-side -----
const { guessHandleType } = internalBinding('util');
const {
parseMode,
validateString
} = require('internal/validators');
// Cache the working directory to prevent lots of lookups. If the working
// directory is changed by `chdir`, it'll be updated.
let cachedCwd = '';
function wrappedChdir(directory) {
validateString(directory, 'directory');
rawMethods.chdir(directory);
// Mark cache that it requires an update.
cachedCwd = '';
}
function wrappedUmask(mask) {
if (mask !== undefined) {
mask = parseMode(mask, 'mask');
}
return rawMethods.umask(mask);
}
function wrappedCwd() {
if (cachedCwd === '')
cachedCwd = rawMethods.cwd();
return cachedCwd;
}
function createWritableStdioStream(fd) {
let stream;
// Note stream._type is used for test-module-load-list.js
switch (guessHandleType(fd)) {
case 'TTY':
const tty = require('tty');
stream = new tty.WriteStream(fd);
stream._type = 'tty';
break;
case 'FILE':
const SyncWriteStream = require('internal/fs/sync_write_stream');
stream = new SyncWriteStream(fd, { autoClose: false });
stream._type = 'fs';
break;
case 'PIPE':
case 'TCP':
const net = require('net');
// If fd is already being used for the IPC channel, libuv will return
// an error when trying to use it again. In that case, create the socket
// using the existing handle instead of the fd.
if (process.channel && process.channel.fd === fd) {
stream = new net.Socket({
handle: process.channel,
readable: false,
writable: true
});
} else {
stream = new net.Socket({
fd,
readable: false,
writable: true
});
}
stream._type = 'pipe';
break;
default:
// Provide a dummy black-hole output for e.g. non-console
// Windows applications.
const { Writable } = require('stream');
stream = new Writable({
write(buf, enc, cb) {
cb();
}
});
}
// For supporting legacy API we put the FD here.
stream.fd = fd;
stream._isStdio = true;
return stream;
}
function dummyDestroy(err, cb) {
cb(err);
// We need to emit 'close' anyway so that the closing
// of the stream is observable. We just make sure we
// are not going to do it twice.
// The 'close' event is needed so that finished and
// pipeline work correctly.
if (!this._writableState.emitClose) {
process.nextTick(() => {
this.emit('close');
});
}
}
let stdin;
let stdout;
let stderr;
function getStdout() {
if (stdout) return stdout;
stdout = createWritableStdioStream(1);
stdout.destroySoon = stdout.destroy;
// Override _destroy so that the fd is never actually closed.
stdout._destroy = dummyDestroy;
if (stdout.isTTY) {
process.on('SIGWINCH', () => stdout._refreshSize());
}
return stdout;
}
function getStderr() {
if (stderr) return stderr;
stderr = createWritableStdioStream(2);
stderr.destroySoon = stderr.destroy;
// Override _destroy so that the fd is never actually closed.
stderr._destroy = dummyDestroy;
if (stderr.isTTY) {
process.on('SIGWINCH', () => stderr._refreshSize());
}
return stderr;
}
function getStdin() {
if (stdin) return stdin;
const fd = 0;
switch (guessHandleType(fd)) {
case 'TTY':
const tty = require('tty');
stdin = new tty.ReadStream(fd, {
highWaterMark: 0,
readable: true,
writable: false
});
break;
case 'FILE':
const fs = require('fs');
stdin = new fs.ReadStream(null, { fd: fd, autoClose: false });
break;
case 'PIPE':
case 'TCP':
const 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,
manualStart: true
});
} else {
stdin = new net.Socket({
fd: fd,
readable: true,
writable: false,
manualStart: true
});
}
// Make sure the stdin can't be `.end()`-ed
stdin._writableState.ended = true;
break;
default:
// Provide a dummy contentless input for e.g. non-console
// Windows applications.
const { Readable } = require('stream');
stdin = new Readable({ read() {} });
stdin.push(null);
}
// 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
// once the stream implementation does so (one nextTick later),
// so that the process can close down.
stdin.on('pause', () => {
process.nextTick(onpause);
});
function onpause() {
if (!stdin._handle)
return;
if (stdin._handle.reading && !stdin.readableFlowing) {
stdin._readableState.reading = false;
stdin._handle.reading = false;
stdin._handle.readStop();
}
}
return stdin;
}
// Used by internal tests.
rawMethods.resetStdioForTesting = function() {
stdin = undefined;
stdout = undefined;
stderr = undefined;
};

View File

@@ -0,0 +1,68 @@
'use strict';
const { ObjectDefineProperty } = primordials;
const rawMethods = internalBinding('process_methods');
const {
unavailable
} = require('internal/process/worker_thread_only');
process.abort = unavailable('process.abort()');
process.chdir = unavailable('process.chdir()');
process.umask = wrappedUmask;
process.cwd = rawMethods.cwd;
delete process._debugProcess;
delete process._debugEnd;
delete process._startProfilerIdleNotifier;
delete process._stopProfilerIdleNotifier;
function defineStream(name, getter) {
ObjectDefineProperty(process, name, {
configurable: true,
enumerable: true,
get: getter
});
}
defineStream('stdout', getStdout);
defineStream('stdin', getStdin);
defineStream('stderr', getStderr);
// Worker threads don't receive signals.
const {
startListeningIfSignal,
stopListeningIfSignal
} = require('internal/process/signal');
process.removeListener('newListener', startListeningIfSignal);
process.removeListener('removeListener', stopListeningIfSignal);
// ---- keep the attachment of the wrappers above so that it's easier to ----
// ---- compare the setups side-by-side -----
const {
createWorkerStdio
} = require('internal/worker/io');
const {
codes: { ERR_WORKER_UNSUPPORTED_OPERATION }
} = require('internal/errors');
let workerStdio;
function lazyWorkerStdio() {
if (!workerStdio) workerStdio = createWorkerStdio();
return workerStdio;
}
function getStdout() { return lazyWorkerStdio().stdout; }
function getStderr() { return lazyWorkerStdio().stderr; }
function getStdin() { return lazyWorkerStdio().stdin; }
function wrappedUmask(mask) {
// process.umask() is a read-only operation in workers.
if (mask !== undefined) {
throw new ERR_WORKER_UNSUPPORTED_OPERATION('Setting process.umask()');
}
return rawMethods.umask(mask);
}

View File

@@ -1,173 +0,0 @@
'use strict';
// This file contains process bootstrappers that can only be
// run in the main thread
const {
ArrayIsArray,
} = primordials;
const {
errnoException,
codes: {
ERR_INVALID_ARG_TYPE,
ERR_UNKNOWN_CREDENTIAL
}
} = require('internal/errors');
const {
parseMode,
validateUint32,
validateString
} = require('internal/validators');
const { signals } = internalBinding('constants').os;
// The execution of this function itself should not cause any side effects.
function wrapProcessMethods(binding) {
// Cache the working directory to prevent lots of lookups. If the working
// directory is changed by `chdir`, it'll be updated.
let cachedCwd = '';
function chdir(directory) {
validateString(directory, 'directory');
binding.chdir(directory);
// Mark cache that it requires an update.
cachedCwd = '';
}
function umask(mask) {
if (mask !== undefined) {
mask = parseMode(mask, 'mask');
}
return binding.umask(mask);
}
function cwd() {
if (cachedCwd === '')
cachedCwd = binding.cwd();
return cachedCwd;
}
return {
chdir,
umask,
cwd
};
}
function wrapPosixCredentialSetters(credentials) {
const {
initgroups: _initgroups,
setgroups: _setgroups,
setegid: _setegid,
seteuid: _seteuid,
setgid: _setgid,
setuid: _setuid
} = credentials;
function initgroups(user, extraGroup) {
validateId(user, 'user');
validateId(extraGroup, 'extraGroup');
// Result is 0 on success, 1 if user is unknown, 2 if group is unknown.
const result = _initgroups(user, extraGroup);
if (result === 1) {
throw new ERR_UNKNOWN_CREDENTIAL('User', user);
} else if (result === 2) {
throw new ERR_UNKNOWN_CREDENTIAL('Group', extraGroup);
}
}
function setgroups(groups) {
if (!ArrayIsArray(groups)) {
throw new ERR_INVALID_ARG_TYPE('groups', 'Array', groups);
}
for (let i = 0; i < groups.length; i++) {
validateId(groups[i], `groups[${i}]`);
}
// Result is 0 on success. A positive integer indicates that the
// corresponding group was not found.
const result = _setgroups(groups);
if (result > 0) {
throw new ERR_UNKNOWN_CREDENTIAL('Group', groups[result - 1]);
}
}
function wrapIdSetter(type, method) {
return function(id) {
validateId(id, 'id');
// Result is 0 on success, 1 if credential is unknown.
const result = method(id);
if (result === 1) {
throw new ERR_UNKNOWN_CREDENTIAL(type, id);
}
};
}
function validateId(id, name) {
if (typeof id === 'number') {
validateUint32(id, name);
} else if (typeof id !== 'string') {
throw new ERR_INVALID_ARG_TYPE(name, ['number', 'string'], id);
}
}
return {
initgroups,
setgroups,
setegid: wrapIdSetter('Group', _setegid),
seteuid: wrapIdSetter('User', _seteuid),
setgid: wrapIdSetter('Group', _setgid),
setuid: wrapIdSetter('User', _setuid)
};
}
let Signal;
function isSignal(event) {
return typeof event === 'string' && signals[event] !== undefined;
}
// Worker threads don't receive signals.
function createSignalHandlers() {
const signalWraps = new Map();
// Detect presence of a listener for the special signal types
function startListeningIfSignal(type) {
if (isSignal(type) && !signalWraps.has(type)) {
if (Signal === undefined)
Signal = internalBinding('signal_wrap').Signal;
const wrap = new Signal();
wrap.unref();
wrap.onsignal = process.emit.bind(process, type, type);
const signum = signals[type];
const err = wrap.start(signum);
if (err) {
wrap.close();
throw errnoException(err, 'uv_signal_start');
}
signalWraps.set(type, wrap);
}
}
function stopListeningIfSignal(type) {
const wrap = signalWraps.get(type);
if (wrap !== undefined && process.listenerCount(type) === 0) {
wrap.close();
signalWraps.delete(type);
}
}
return {
startListeningIfSignal,
stopListeningIfSignal
};
}
module.exports = {
wrapProcessMethods,
createSignalHandlers,
wrapPosixCredentialSetters
};

View File

@@ -0,0 +1,49 @@
'use strict';
const {
errnoException,
} = require('internal/errors');
const { signals } = internalBinding('constants').os;
let Signal;
const signalWraps = new Map();
function isSignal(event) {
return typeof event === 'string' && signals[event] !== undefined;
}
// Detect presence of a listener for the special signal types
function startListeningIfSignal(type) {
if (isSignal(type) && !signalWraps.has(type)) {
if (Signal === undefined)
Signal = internalBinding('signal_wrap').Signal;
const wrap = new Signal();
wrap.unref();
wrap.onsignal = process.emit.bind(process, type, type);
const signum = signals[type];
const err = wrap.start(signum);
if (err) {
wrap.close();
throw errnoException(err, 'uv_signal_start');
}
signalWraps.set(type, wrap);
}
}
function stopListeningIfSignal(type) {
const wrap = signalWraps.get(type);
if (wrap !== undefined && process.listenerCount(type) === 0) {
wrap.close();
signalWraps.delete(type);
}
}
module.exports = {
startListeningIfSignal,
stopListeningIfSignal
};

View File

@@ -1,206 +0,0 @@
'use strict';
const { guessHandleType } = internalBinding('util');
exports.getMainThreadStdio = getMainThreadStdio;
function dummyDestroy(err, cb) {
cb(err);
// We need to emit 'close' anyway so that the closing
// of the stream is observable. We just make sure we
// are not going to do it twice.
// The 'close' event is needed so that finished and
// pipeline work correctly.
if (!this._writableState.emitClose) {
process.nextTick(() => {
this.emit('close');
});
}
}
function getMainThreadStdio() {
let stdin;
let stdout;
let stderr;
function getStdout() {
if (stdout) return stdout;
stdout = createWritableStdioStream(1);
stdout.destroySoon = stdout.destroy;
// Override _destroy so that the fd is never actually closed.
stdout._destroy = dummyDestroy;
if (stdout.isTTY) {
process.on('SIGWINCH', () => stdout._refreshSize());
}
return stdout;
}
function getStderr() {
if (stderr) return stderr;
stderr = createWritableStdioStream(2);
stderr.destroySoon = stderr.destroy;
// Override _destroy so that the fd is never actually closed.
stderr._destroy = dummyDestroy;
if (stderr.isTTY) {
process.on('SIGWINCH', () => stderr._refreshSize());
}
return stderr;
}
function getStdin() {
if (stdin) return stdin;
const fd = 0;
switch (guessHandleType(fd)) {
case 'TTY':
const tty = require('tty');
stdin = new tty.ReadStream(fd, {
highWaterMark: 0,
readable: true,
writable: false
});
break;
case 'FILE':
const fs = require('fs');
stdin = new fs.ReadStream(null, { fd: fd, autoClose: false });
break;
case 'PIPE':
case 'TCP':
const 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,
manualStart: true
});
} else {
stdin = new net.Socket({
fd: fd,
readable: true,
writable: false,
manualStart: true
});
}
// Make sure the stdin can't be `.end()`-ed
stdin._writableState.ended = true;
break;
default:
// Provide a dummy contentless input for e.g. non-console
// Windows applications.
const { Readable } = require('stream');
stdin = new Readable({ read() {} });
stdin.push(null);
}
// 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
// once the stream implementation does so (one nextTick later),
// so that the process can close down.
stdin.on('pause', () => {
process.nextTick(onpause);
});
function onpause() {
if (!stdin._handle)
return;
if (stdin._handle.reading && !stdin.readableFlowing) {
stdin._readableState.reading = false;
stdin._handle.reading = false;
stdin._handle.readStop();
}
}
return stdin;
}
exports.resetStdioForTesting = function() {
stdin = undefined;
stdout = undefined;
stderr = undefined;
};
return {
getStdout,
getStderr,
getStdin
};
}
function createWritableStdioStream(fd) {
let stream;
// Note stream._type is used for test-module-load-list.js
switch (guessHandleType(fd)) {
case 'TTY':
const tty = require('tty');
stream = new tty.WriteStream(fd);
stream._type = 'tty';
break;
case 'FILE':
const SyncWriteStream = require('internal/fs/sync_write_stream');
stream = new SyncWriteStream(fd, { autoClose: false });
stream._type = 'fs';
break;
case 'PIPE':
case 'TCP':
const net = require('net');
// If fd is already being used for the IPC channel, libuv will return
// an error when trying to use it again. In that case, create the socket
// using the existing handle instead of the fd.
if (process.channel && process.channel.fd === fd) {
stream = new net.Socket({
handle: process.channel,
readable: false,
writable: true
});
} else {
stream = new net.Socket({
fd,
readable: false,
writable: true
});
}
stream._type = 'pipe';
break;
default:
// Provide a dummy black-hole output for e.g. non-console
// Windows applications.
const { Writable } = require('stream');
stream = new Writable({
write(buf, enc, cb) {
cb();
}
});
}
// For supporting legacy API we put the FD here.
stream.fd = fd;
stream._isStdio = true;
return stream;
}

View File

@@ -3,42 +3,10 @@
// This file contains process bootstrappers that can only be
// run in the worker thread.
const {
createWorkerStdio
} = require('internal/worker/io');
const {
codes: { ERR_WORKER_UNSUPPORTED_OPERATION }
} = require('internal/errors');
let workerStdio;
function lazyWorkerStdio() {
if (!workerStdio) workerStdio = createWorkerStdio();
return workerStdio;
}
function createStdioGetters() {
return {
getStdout() { return lazyWorkerStdio().stdout; },
getStderr() { return lazyWorkerStdio().stderr; },
getStdin() { return lazyWorkerStdio().stdin; }
};
}
// The execution of this function itself should not cause any side effects.
function wrapProcessMethods(binding) {
function umask(mask) {
// process.umask() is a read-only operation in workers.
if (mask !== undefined) {
throw new ERR_WORKER_UNSUPPORTED_OPERATION('Setting process.umask()');
}
return binding.umask(mask);
}
return { umask };
}
function unavailable(name) {
function unavailableInWorker() {
throw new ERR_WORKER_UNSUPPORTED_OPERATION(name);
@@ -49,7 +17,5 @@ function unavailable(name) {
}
module.exports = {
createStdioGetters,
unavailable,
wrapProcessMethods
unavailable
};

View File

@@ -30,6 +30,10 @@
'lib/internal/bootstrap/loaders.js',
'lib/internal/bootstrap/node.js',
'lib/internal/bootstrap/pre_execution.js',
'lib/internal/bootstrap/switches/does_own_process_state.js',
'lib/internal/bootstrap/switches/does_not_own_process_state.js',
'lib/internal/bootstrap/switches/is_main_thread.js',
'lib/internal/bootstrap/switches/is_not_main_thread.js',
'lib/internal/per_context/primordials.js',
'lib/internal/per_context/domexception.js',
'lib/async_hooks.js',
@@ -164,14 +168,13 @@
'lib/internal/priority_queue.js',
'lib/internal/process/esm_loader.js',
'lib/internal/process/execution.js',
'lib/internal/process/main_thread_only.js',
'lib/internal/process/per_thread.js',
'lib/internal/process/policy.js',
'lib/internal/process/promises.js',
'lib/internal/process/stdio.js',
'lib/internal/process/warning.js',
'lib/internal/process/worker_thread_only.js',
'lib/internal/process/report.js',
'lib/internal/process/signal.js',
'lib/internal/process/task_queues.js',
'lib/internal/querystring.js',
'lib/internal/readline/utils.js',

View File

@@ -128,7 +128,6 @@ namespace node {
using native_module::NativeModuleEnv;
using v8::Boolean;
using v8::EscapableHandleScope;
using v8::Function;
using v8::FunctionCallbackInfo;
@@ -297,26 +296,60 @@ MaybeLocal<Value> Environment::BootstrapNode() {
global->Set(context(), FIXED_ONE_BYTE_STRING(isolate_, "global"), global)
.Check();
// process, require, internalBinding, isMainThread,
// ownsProcessState, primordials
// process, require, internalBinding, primordials
std::vector<Local<String>> node_params = {
process_string(),
require_string(),
internal_binding_string(),
FIXED_ONE_BYTE_STRING(isolate_, "isMainThread"),
FIXED_ONE_BYTE_STRING(isolate_, "ownsProcessState"),
primordials_string()};
std::vector<Local<Value>> node_args = {
process_object(),
native_module_require(),
internal_binding_loader(),
Boolean::New(isolate_, is_main_thread()),
Boolean::New(isolate_, owns_process_state()),
primordials()};
MaybeLocal<Value> result = ExecuteBootstrapper(
this, "internal/bootstrap/node", &node_params, &node_args);
if (result.IsEmpty()) {
return scope.EscapeMaybe(result);
}
if (is_main_thread()) {
result = ExecuteBootstrapper(this,
"internal/bootstrap/switches/is_main_thread",
&node_params,
&node_args);
} else {
result =
ExecuteBootstrapper(this,
"internal/bootstrap/switches/is_not_main_thread",
&node_params,
&node_args);
}
if (result.IsEmpty()) {
return scope.EscapeMaybe(result);
}
if (owns_process_state()) {
result = ExecuteBootstrapper(
this,
"internal/bootstrap/switches/does_own_process_state",
&node_params,
&node_args);
} else {
result = ExecuteBootstrapper(
this,
"internal/bootstrap/switches/does_not_own_process_state",
&node_params,
&node_args);
}
if (result.IsEmpty()) {
return scope.EscapeMaybe(result);
}
Local<Object> env_var_proxy;
if (!CreateEnvVarProxy(context(), isolate_, as_callback_data())
.ToLocal(&env_var_proxy) ||

View File

@@ -61,6 +61,7 @@ const expectedModules = new Set([
'NativeModule internal/process/execution',
'NativeModule internal/process/per_thread',
'NativeModule internal/process/promises',
'NativeModule internal/process/signal',
'NativeModule internal/process/task_queues',
'NativeModule internal/process/warning',
'NativeModule internal/querystring',
@@ -79,10 +80,7 @@ const expectedModules = new Set([
'NativeModule vm',
]);
if (common.isMainThread) {
expectedModules.add('NativeModule internal/process/main_thread_only');
expectedModules.add('NativeModule internal/process/stdio');
} else {
if (!common.isMainThread) {
expectedModules.add('Internal Binding messaging');
expectedModules.add('Internal Binding symbols');
expectedModules.add('Internal Binding worker');

View File

@@ -9,8 +9,10 @@ if (common.isWindows)
function runTest(fd, streamName, testOutputStream, expectedName) {
const result = child_process.spawnSync(process.execPath, [
'--expose-internals',
'-e', `
require('internal/process/stdio').resetStdioForTesting();
'--no-warnings',
'-e',
`const { internalBinding } = require('internal/test/binding');
internalBinding('process_methods').resetStdioForTesting();
fs.closeSync(${fd});
const ctorName = process.${streamName}.constructor.name;
process.${testOutputStream}.write(ctorName);