diff options
author | Joyee Cheung <joyeec9h3@gmail.com> | 2019-12-09 05:53:02 +0300 |
---|---|---|
committer | Joyee Cheung <joyeec9h3@gmail.com> | 2019-12-20 17:10:13 +0300 |
commit | c63d511c137130030c263fb98d0d261105244c08 (patch) | |
tree | ffda0e6de496ccb88080a5499ba55a2e28241ca2 /lib/internal/bootstrap | |
parent | 277f3436890fac87fe6b762abc00f59d36a9ec6f (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.js | 117 | ||||
-rw-r--r-- | lib/internal/bootstrap/pre_execution.js | 18 | ||||
-rw-r--r-- | lib/internal/bootstrap/switches/does_not_own_process_state.js | 18 | ||||
-rw-r--r-- | lib/internal/bootstrap/switches/does_own_process_state.js | 96 | ||||
-rw-r--r-- | lib/internal/bootstrap/switches/is_main_thread.js | 263 | ||||
-rw-r--r-- | lib/internal/bootstrap/switches/is_not_main_thread.js | 68 |
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); +} |