Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nodejs/node.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoyee Cheung <joyeec9h3@gmail.com>2019-12-09 05:53:02 +0300
committerJoyee Cheung <joyeec9h3@gmail.com>2019-12-20 17:10:13 +0300
commitc63d511c137130030c263fb98d0d261105244c08 (patch)
treeffda0e6de496ccb88080a5499ba55a2e28241ca2 /lib/internal/bootstrap
parent277f3436890fac87fe6b762abc00f59d36a9ec6f (diff)
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>
Diffstat (limited to 'lib/internal/bootstrap')
-rw-r--r--lib/internal/bootstrap/node.js117
-rw-r--r--lib/internal/bootstrap/pre_execution.js18
-rw-r--r--lib/internal/bootstrap/switches/does_not_own_process_state.js18
-rw-r--r--lib/internal/bootstrap/switches/does_own_process_state.js96
-rw-r--r--lib/internal/bootstrap/switches/is_main_thread.js263
-rw-r--r--lib/internal/bootstrap/switches/is_not_main_thread.js68
6 files changed, 464 insertions, 116 deletions
diff --git a/lib/internal/bootstrap/node.js b/lib/internal/bootstrap/node.js
index c586ddfd5e6..bb9a2b177c4 100644
--- a/lib/internal/bootstrap/node.js
+++ b/lib/internal/bootstrap/node.js
@@ -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',
diff --git a/lib/internal/bootstrap/pre_execution.js b/lib/internal/bootstrap/pre_execution.js
index 40dabdda504..7af4be660fc 100644
--- a/lib/internal/bootstrap/pre_execution.js
+++ b/lib/internal/bootstrap/pre_execution.js
@@ -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;
diff --git a/lib/internal/bootstrap/switches/does_not_own_process_state.js b/lib/internal/bootstrap/switches/does_not_own_process_state.js
new file mode 100644
index 00000000000..1ec449b34a9
--- /dev/null
+++ b/lib/internal/bootstrap/switches/does_not_own_process_state.js
@@ -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 -----
diff --git a/lib/internal/bootstrap/switches/does_own_process_state.js b/lib/internal/bootstrap/switches/does_own_process_state.js
new file mode 100644
index 00000000000..88f77520720
--- /dev/null
+++ b/lib/internal/bootstrap/switches/does_own_process_state.js
@@ -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)
+ };
+}
diff --git a/lib/internal/bootstrap/switches/is_main_thread.js b/lib/internal/bootstrap/switches/is_main_thread.js
new file mode 100644
index 00000000000..4f7ef7b6a69
--- /dev/null
+++ b/lib/internal/bootstrap/switches/is_main_thread.js
@@ -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;
+};
diff --git a/lib/internal/bootstrap/switches/is_not_main_thread.js b/lib/internal/bootstrap/switches/is_not_main_thread.js
new file mode 100644
index 00000000000..33e98d387cf
--- /dev/null
+++ b/lib/internal/bootstrap/switches/is_not_main_thread.js
@@ -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);
+}