diff --git a/lib/internal/bootstrap/node.js b/lib/internal/bootstrap/node.js index c586ddfd5e6f89..bb9a2b177c4304 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 004684d522102b..1b05d6e30a0ba0 100644 --- a/lib/internal/bootstrap/pre_execution.js +++ b/lib/internal/bootstrap/pre_execution.js @@ -37,9 +37,6 @@ function prepareMainThreadExecution(expandArgv1 = false) { setupDebugEnv(); - // Only main thread receives signals. - setupSignalHandlers(); - // Process initial diagnostic reporting configuration, if present. initializeReport(); initializeReportSignalHandlers(); // Main-thread-only. @@ -175,20 +172,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 00000000000000..1ec449b34a9f71 --- /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 00000000000000..88f7752072015a --- /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 00000000000000..cd4b9cdeeb14fa --- /dev/null +++ b/lib/internal/bootstrap/switches/is_main_thread.js @@ -0,0 +1,262 @@ +'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() { + 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 00000000000000..33e98d387cfd15 --- /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); +} diff --git a/lib/internal/process/main_thread_only.js b/lib/internal/process/main_thread_only.js deleted file mode 100644 index 17df409472f58e..00000000000000 --- a/lib/internal/process/main_thread_only.js +++ /dev/null @@ -1,173 +0,0 @@ -'use strict'; - -// This file contains process bootstrappers that can only be -// run in the main thread - -const { - ArrayIsArray, - Map, -} = 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() { - 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 -}; diff --git a/lib/internal/process/signal.js b/lib/internal/process/signal.js new file mode 100644 index 00000000000000..abc2b93dbfb7f3 --- /dev/null +++ b/lib/internal/process/signal.js @@ -0,0 +1,53 @@ +'use strict'; + +const { + Map, +} = primordials; + +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 +}; diff --git a/lib/internal/process/stdio.js b/lib/internal/process/stdio.js deleted file mode 100644 index 2491f14cdf0140..00000000000000 --- a/lib/internal/process/stdio.js +++ /dev/null @@ -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; -} diff --git a/lib/internal/process/worker_thread_only.js b/lib/internal/process/worker_thread_only.js index 920f0f77261bab..9d2a43b441a584 100644 --- a/lib/internal/process/worker_thread_only.js +++ b/lib/internal/process/worker_thread_only.js @@ -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 }; diff --git a/node.gyp b/node.gyp index c371c7e7490857..5929785389fc7c 100644 --- a/node.gyp +++ b/node.gyp @@ -29,6 +29,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', @@ -162,14 +166,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', diff --git a/src/node.cc b/src/node.cc index 20a627c0ba2a5e..cadbd9a62db9e0 100644 --- a/src/node.cc +++ b/src/node.cc @@ -117,7 +117,6 @@ namespace node { using native_module::NativeModuleEnv; -using v8::Boolean; using v8::EscapableHandleScope; using v8::Function; using v8::FunctionCallbackInfo; @@ -291,26 +290,60 @@ MaybeLocal 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> 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> node_args = { process_object(), native_module_require(), internal_binding_loader(), - Boolean::New(isolate_, is_main_thread()), - Boolean::New(isolate_, owns_process_state()), primordials()}; MaybeLocal 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 env_var_proxy; if (!CreateEnvVarProxy(context(), isolate_, as_callback_data()) .ToLocal(&env_var_proxy) || diff --git a/test/parallel/test-bootstrap-modules.js b/test/parallel/test-bootstrap-modules.js index fbb2a07c307da8..9b075d981a7e5c 100644 --- a/test/parallel/test-bootstrap-modules.js +++ b/test/parallel/test-bootstrap-modules.js @@ -54,6 +54,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', @@ -71,10 +72,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'); diff --git a/test/parallel/test-dummy-stdio.js b/test/parallel/test-dummy-stdio.js index 165c2c2c575ff5..4866f85c7def4b 100644 --- a/test/parallel/test-dummy-stdio.js +++ b/test/parallel/test-dummy-stdio.js @@ -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);