process: move eval and exception bootstrap ito process/execution.js

This patch:

- Moves `tryGetCwd`, `evalScript` and `fatalException` from
  `bootstrap/node.js` into `process/execution.js` so that
  they do have to be passed into the worker thread
  setup function, instead the worker code can require them
  when necessary.
- Moves `setUncaughtExceptionCaptureCallback` and
  `hasUncaughtExceptionCaptureCallback` along with the two
  global state `exceptionHandlerState` and
  `shouldAbortOnUncaughtToggle` info `process.execution.js`
  as those are only used by the fatalException and these
  two accessors as one self-contained unit.

PR-URL: https://github.com/nodejs/node/pull/25199
Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
Joyee Cheung 2018-12-23 11:42:28 +08:00
parent da13c44b0d
commit 7163fbf066
No known key found for this signature in database
GPG Key ID: 92B78A53C8303B8D
12 changed files with 199 additions and 164 deletions

View File

@ -18,7 +18,6 @@
const { internalBinding, NativeModule } = loaderExports;
const exceptionHandlerState = { captureFn: null };
let getOptionValue;
function startup() {
@ -26,8 +25,23 @@ function startup() {
setupProcessObject();
// Do this good and early, since it handles errors.
setupProcessFatal();
// TODO(joyeecheung): this does not have to done so early, any fatal errors
// thrown before user code execution should simply crash the process
// and we do not care about any clean up at that point. We don't care
// about emitting any events if the process crash upon bootstrap either.
{
const {
fatalException,
setUncaughtExceptionCaptureCallback,
hasUncaughtExceptionCaptureCallback
} = NativeModule.require('internal/process/execution');
process._fatalException = fatalException;
process.setUncaughtExceptionCaptureCallback =
setUncaughtExceptionCaptureCallback;
process.hasUncaughtExceptionCaptureCallback =
hasUncaughtExceptionCaptureCallback;
}
setupGlobalVariables();
@ -83,9 +97,7 @@ function startup() {
process.reallyExit = rawMethods.reallyExit;
process._kill = rawMethods._kill;
const wrapped = perThreadSetup.wrapProcessMethods(
rawMethods, exceptionHandlerState
);
const wrapped = perThreadSetup.wrapProcessMethods(rawMethods);
process._rawDebug = wrapped._rawDebug;
process.hrtime = wrapped.hrtime;
process.hrtime.bigint = wrapped.hrtimeBigInt;
@ -93,10 +105,6 @@ function startup() {
process.memoryUsage = wrapped.memoryUsage;
process.kill = wrapped.kill;
process.exit = wrapped.exit;
process.setUncaughtExceptionCaptureCallback =
wrapped.setUncaughtExceptionCaptureCallback;
process.hasUncaughtExceptionCaptureCallback =
wrapped.hasUncaughtExceptionCaptureCallback;
}
NativeModule.require('internal/process/warning').setup();
@ -305,7 +313,7 @@ function startExecution() {
// This means we are in a Worker context, and any script execution
// will be directed by the worker module.
if (internalBinding('worker').getEnvMessagePort() !== undefined) {
NativeModule.require('internal/worker').setupChild(evalScript);
NativeModule.require('internal/worker').setupChild();
return;
}
@ -376,7 +384,9 @@ function executeUserCode() {
addBuiltinLibsToObject
} = NativeModule.require('internal/modules/cjs/helpers');
addBuiltinLibsToObject(global);
evalScript('[eval]', wrapForBreakOnFirstLine(getOptionValue('--eval')));
const source = getOptionValue('--eval');
const { evalScript } = NativeModule.require('internal/process/execution');
evalScript('[eval]', source, process._breakFirstLine);
return;
}
@ -430,7 +440,8 @@ function executeUserCode() {
// User passed '-e' or '--eval' along with `-i` or `--interactive`
if (process._eval != null) {
evalScript('[eval]', wrapForBreakOnFirstLine(process._eval));
const { evalScript } = NativeModule.require('internal/process/execution');
evalScript('[eval]', process._eval, process._breakFirstLine);
}
return;
}
@ -452,7 +463,8 @@ function readAndExecuteStdin() {
checkScriptSyntax(code, '[stdin]');
} else {
process._eval = code;
evalScript('[stdin]', wrapForBreakOnFirstLine(process._eval));
const { evalScript } = NativeModule.require('internal/process/execution');
evalScript('[stdin]', process._eval, process._breakFirstLine);
}
});
}
@ -656,95 +668,6 @@ function setupDOMException() {
registerDOMException(DOMException);
}
function noop() {}
function setupProcessFatal() {
const {
executionAsyncId,
clearDefaultTriggerAsyncId,
clearAsyncIdStack,
hasAsyncIdStack,
afterHooksExist,
emitAfter
} = NativeModule.require('internal/async_hooks');
process._fatalException = (er) => {
// It's possible that defaultTriggerAsyncId was set for a constructor
// call that threw and was never cleared. So clear it now.
clearDefaultTriggerAsyncId();
if (exceptionHandlerState.captureFn !== null) {
exceptionHandlerState.captureFn(er);
} else if (!process.emit('uncaughtException', er)) {
// If someone handled it, then great. otherwise, die in C++ land
// since that means that we'll exit the process, emit the 'exit' event.
try {
if (!process._exiting) {
process._exiting = true;
process.exitCode = 1;
process.emit('exit', 1);
}
} catch {
// Nothing to be done about it at this point.
}
try {
const { kExpandStackSymbol } = NativeModule.require('internal/util');
if (typeof er[kExpandStackSymbol] === 'function')
er[kExpandStackSymbol]();
} catch {
// Nothing to be done about it at this point.
}
return false;
}
// If we handled an error, then make sure any ticks get processed
// by ensuring that the next Immediate cycle isn't empty.
NativeModule.require('timers').setImmediate(noop);
// Emit the after() hooks now that the exception has been handled.
if (afterHooksExist()) {
do {
emitAfter(executionAsyncId());
} while (hasAsyncIdStack());
// Or completely empty the id stack.
} else {
clearAsyncIdStack();
}
return true;
};
}
function wrapForBreakOnFirstLine(source) {
if (!process._breakFirstLine)
return source;
const fn = `function() {\n\n${source};\n\n}`;
return `process.binding('inspector').callAndPauseOnStart(${fn}, {})`;
}
function evalScript(name, body) {
const CJSModule = NativeModule.require('internal/modules/cjs/loader');
const path = NativeModule.require('path');
const { tryGetCwd } = NativeModule.require('internal/util');
const cwd = tryGetCwd(path);
const module = new CJSModule(name);
module.filename = path.join(cwd, name);
module.paths = CJSModule._nodeModulePaths(cwd);
const script = `global.__filename = ${JSON.stringify(name)};\n` +
'global.exports = exports;\n' +
'global.module = module;\n' +
'global.__dirname = __dirname;\n' +
'global.require = require;\n' +
'return require("vm").runInThisContext(' +
`${JSON.stringify(body)}, { filename: ` +
`${JSON.stringify(name)}, displayErrors: true });\n`;
const result = module._compile(script, `${name}-wrapper`);
if (getOptionValue('--print')) console.log(result);
// Handle any nextTicks added in the first tick of the program.
process._tickCallback();
}
function checkScriptSyntax(source, filename) {
const CJSModule = NativeModule.require('internal/modules/cjs/loader');
const vm = NativeModule.require('vm');

View File

@ -1,15 +1,14 @@
'use strict';
const path = require('path');
const CJSModule = require('internal/modules/cjs/loader');
const { makeRequireFunction } = require('internal/modules/cjs/helpers');
const { tryGetCwd } = require('internal/util');
const { tryGetCwd } = require('internal/process/execution');
const { addCommandLineAPI, consoleCall } = internalBinding('inspector');
// Wrap a console implemented by Node.js with features from the VM inspector
function addInspectorApis(consoleFromNode, consoleFromVM) {
// Setup inspector command line API.
const cwd = tryGetCwd(path);
const cwd = tryGetCwd();
const consoleAPIModule = new CJSModule('<inspector console>');
consoleAPIModule.paths =
CJSModule._nodeModulePaths(cwd).concat(CJSModule.globalPaths);

View File

@ -0,0 +1,149 @@
'use strict';
const path = require('path');
const {
codes: {
ERR_INVALID_ARG_TYPE,
ERR_UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET
}
} = require('internal/errors');
const {
executionAsyncId,
clearDefaultTriggerAsyncId,
clearAsyncIdStack,
hasAsyncIdStack,
afterHooksExist,
emitAfter
} = require('internal/async_hooks');
// shouldAbortOnUncaughtToggle is a typed array for faster
// communication with JS.
const { shouldAbortOnUncaughtToggle } = internalBinding('util');
function tryGetCwd() {
try {
return process.cwd();
} catch {
// getcwd(3) can fail if the current working directory has been deleted.
// Fall back to the directory name of the (absolute) executable path.
// It's not really correct but what are the alternatives?
return path.dirname(process.execPath);
}
}
function evalScript(name, body, breakFristLine) {
const CJSModule = require('internal/modules/cjs/loader');
if (breakFristLine) {
const fn = `function() {\n\n${body};\n\n}`;
body = `process.binding('inspector').callAndPauseOnStart(${fn}, {})`;
}
const cwd = tryGetCwd();
const module = new CJSModule(name);
module.filename = path.join(cwd, name);
module.paths = CJSModule._nodeModulePaths(cwd);
const script = `global.__filename = ${JSON.stringify(name)};\n` +
'global.exports = exports;\n' +
'global.module = module;\n' +
'global.__dirname = __dirname;\n' +
'global.require = require;\n' +
'return require("vm").runInThisContext(' +
`${JSON.stringify(body)}, { filename: ` +
`${JSON.stringify(name)}, displayErrors: true });\n`;
const result = module._compile(script, `${name}-wrapper`);
if (require('internal/options').getOptionValue('--print')) {
console.log(result);
}
// Handle any nextTicks added in the first tick of the program.
process._tickCallback();
}
const exceptionHandlerState = { captureFn: null };
function setUncaughtExceptionCaptureCallback(fn) {
if (fn === null) {
exceptionHandlerState.captureFn = fn;
shouldAbortOnUncaughtToggle[0] = 1;
return;
}
if (typeof fn !== 'function') {
throw new ERR_INVALID_ARG_TYPE('fn', ['Function', 'null'], fn);
}
if (exceptionHandlerState.captureFn !== null) {
throw new ERR_UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET();
}
exceptionHandlerState.captureFn = fn;
shouldAbortOnUncaughtToggle[0] = 0;
}
function hasUncaughtExceptionCaptureCallback() {
return exceptionHandlerState.captureFn !== null;
}
function noop() {}
// XXX(joyeecheung): for some reason this cannot be defined at the top-level
// and exported to be written to process._fatalException, it has to be
// returned as an *anonymous function* wrapped inside a factory function,
// otherwise it breaks the test-timers.setInterval async hooks test -
// this may indicate that node::FatalException should fix up the callback scope
// before calling into process._fatalException, or this function should
// take extra care of the async hooks before it schedules a setImmediate.
function createFatalException() {
return (er) => {
// It's possible that defaultTriggerAsyncId was set for a constructor
// call that threw and was never cleared. So clear it now.
clearDefaultTriggerAsyncId();
if (exceptionHandlerState.captureFn !== null) {
exceptionHandlerState.captureFn(er);
} else if (!process.emit('uncaughtException', er)) {
// If someone handled it, then great. otherwise, die in C++ land
// since that means that we'll exit the process, emit the 'exit' event.
try {
if (!process._exiting) {
process._exiting = true;
process.exitCode = 1;
process.emit('exit', 1);
}
} catch {
// Nothing to be done about it at this point.
}
try {
const { kExpandStackSymbol } = require('internal/util');
if (typeof er[kExpandStackSymbol] === 'function')
er[kExpandStackSymbol]();
} catch {
// Nothing to be done about it at this point.
}
return false;
}
// If we handled an error, then make sure any ticks get processed
// by ensuring that the next Immediate cycle isn't empty.
require('timers').setImmediate(noop);
// Emit the after() hooks now that the exception has been handled.
if (afterHooksExist()) {
do {
emitAfter(executionAsyncId());
} while (hasAsyncIdStack());
// Or completely empty the id stack.
} else {
clearAsyncIdStack();
}
return true;
};
}
module.exports = {
tryGetCwd,
evalScript,
fatalException: createFatalException(),
setUncaughtExceptionCaptureCallback,
hasUncaughtExceptionCaptureCallback
};

View File

@ -12,7 +12,6 @@ const {
ERR_INVALID_ARG_TYPE,
ERR_INVALID_OPT_VALUE,
ERR_OUT_OF_RANGE,
ERR_UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET,
ERR_UNKNOWN_SIGNAL
}
} = require('internal/errors');
@ -24,7 +23,7 @@ function assert(x, msg) {
}
// The execution of this function itself should not cause any side effects.
function wrapProcessMethods(binding, exceptionHandlerState) {
function wrapProcessMethods(binding) {
const {
hrtime: _hrtime,
hrtimeBigInt: _hrtimeBigInt,
@ -185,29 +184,6 @@ function wrapProcessMethods(binding, exceptionHandlerState) {
return true;
}
// shouldAbortOnUncaughtToggle is a typed array for faster
// communication with JS.
const { shouldAbortOnUncaughtToggle } = binding;
function setUncaughtExceptionCaptureCallback(fn) {
if (fn === null) {
exceptionHandlerState.captureFn = fn;
shouldAbortOnUncaughtToggle[0] = 1;
return;
}
if (typeof fn !== 'function') {
throw new ERR_INVALID_ARG_TYPE('fn', ['Function', 'null'], fn);
}
if (exceptionHandlerState.captureFn !== null) {
throw new ERR_UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET();
}
exceptionHandlerState.captureFn = fn;
shouldAbortOnUncaughtToggle[0] = 0;
}
function hasUncaughtExceptionCaptureCallback() {
return exceptionHandlerState.captureFn !== null;
}
return {
_rawDebug,
@ -216,9 +192,7 @@ function wrapProcessMethods(binding, exceptionHandlerState) {
cpuUsage,
memoryUsage,
kill,
exit,
setUncaughtExceptionCaptureCallback,
hasUncaughtExceptionCaptureCallback
exit
};
}

View File

@ -387,17 +387,6 @@ function once(callback) {
};
}
function tryGetCwd(path) {
try {
return process.cwd();
} catch {
// getcwd(3) can fail if the current working directory has been deleted.
// Fall back to the directory name of the (absolute) executable path.
// It's not really correct but what are the alternatives?
return path.dirname(process.execPath);
}
}
module.exports = {
assertCrypto,
cachedResult,
@ -417,7 +406,6 @@ module.exports = {
once,
promisify,
spliceOne,
tryGetCwd,
removeColors,
// Symbol used to customize promisify conversion

View File

@ -432,7 +432,7 @@ if (!isMainThread) {
let originalFatalException;
function setupChild(evalScript) {
function setupChild() {
// Called during bootstrap to set up worker script execution.
debug(`[${threadId}] is setting up worker child environment`);
const port = getEnvMessagePort();
@ -453,6 +453,7 @@ function setupChild(evalScript) {
port.unref();
port.postMessage({ type: messageTypes.UP_AND_RUNNING });
if (doEval) {
const { evalScript } = require('internal/process/execution');
evalScript('[worker eval]', filename);
} else {
process.argv[1] = filename; // script filename

View File

@ -142,6 +142,7 @@
'lib/internal/print_help.js',
'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/next_tick.js',
'lib/internal/process/per_thread.js',

View File

@ -458,14 +458,6 @@ static void InitializeProcessMethods(Local<Object> target,
env->SetMethod(target, "dlopen", binding::DLOpen);
env->SetMethod(target, "reallyExit", Exit);
env->SetMethodNoSideEffect(target, "uptime", Uptime);
Local<String> should_abort_on_uncaught_toggle =
FIXED_ONE_BYTE_STRING(env->isolate(), "shouldAbortOnUncaughtToggle");
CHECK(target
->Set(env->context(),
should_abort_on_uncaught_toggle,
env->should_abort_on_uncaught_toggle().GetJSArray())
.FromJust());
}
} // namespace node

View File

@ -233,6 +233,14 @@ void Initialize(Local<Object> target,
target->Set(context,
FIXED_ONE_BYTE_STRING(env->isolate(), "propertyFilter"),
constants).FromJust();
Local<String> should_abort_on_uncaught_toggle =
FIXED_ONE_BYTE_STRING(env->isolate(), "shouldAbortOnUncaughtToggle");
CHECK(target
->Set(env->context(),
should_abort_on_uncaught_toggle,
env->should_abort_on_uncaught_toggle().GetJSArray())
.FromJust());
}
} // namespace util

View File

@ -8,7 +8,7 @@ SyntaxError: Strict mode code may not include a with statement
at Object.runInThisContext (vm.js:*:*)
at Object.<anonymous> ([eval]-wrapper:*:*)
at Module._compile (internal/modules/cjs/loader.js:*:*)
at evalScript (internal/bootstrap/node.js:*:*)
at evalScript (internal/process/execution.js:*:*)
at executeUserCode (internal/bootstrap/node.js:*:*)
at startExecution (internal/bootstrap/node.js:*:*)
at startup (internal/bootstrap/node.js:*:*)
@ -25,7 +25,7 @@ Error: hello
at Object.runInThisContext (vm.js:*:*)
at Object.<anonymous> ([eval]-wrapper:*:*)
at Module._compile (internal/modules/cjs/loader.js:*:*)
at evalScript (internal/bootstrap/node.js:*:*)
at evalScript (internal/process/execution.js:*:*)
at executeUserCode (internal/bootstrap/node.js:*:*)
at startExecution (internal/bootstrap/node.js:*:*)
at startup (internal/bootstrap/node.js:*:*)
@ -41,7 +41,7 @@ Error: hello
at Object.runInThisContext (vm.js:*:*)
at Object.<anonymous> ([eval]-wrapper:*:*)
at Module._compile (internal/modules/cjs/loader.js:*:*)
at evalScript (internal/bootstrap/node.js:*:*)
at evalScript (internal/process/execution.js:*:*)
at executeUserCode (internal/bootstrap/node.js:*:*)
at startExecution (internal/bootstrap/node.js:*:*)
at startup (internal/bootstrap/node.js:*:*)
@ -57,7 +57,7 @@ ReferenceError: y is not defined
at Object.runInThisContext (vm.js:*:*)
at Object.<anonymous> ([eval]-wrapper:*:*)
at Module._compile (internal/modules/cjs/loader.js:*:*)
at evalScript (internal/bootstrap/node.js:*:*)
at evalScript (internal/process/execution.js:*:*)
at executeUserCode (internal/bootstrap/node.js:*:*)
at startExecution (internal/bootstrap/node.js:*:*)
at startup (internal/bootstrap/node.js:*:*)

View File

@ -8,7 +8,7 @@ SyntaxError: Strict mode code may not include a with statement
at Object.runInThisContext (vm.js:*)
at Object.<anonymous> ([stdin]-wrapper:*:*)
at Module._compile (internal/modules/cjs/loader.js:*:*)
at evalScript (internal/bootstrap/node.js:*:*)
at evalScript (internal/process/execution.js:*:*)
at Socket.process.stdin.on (internal/bootstrap/node.js:*:*)
at Socket.emit (events.js:*:*)
at endReadableNT (_stream_readable.js:*:*)
@ -25,7 +25,7 @@ Error: hello
at Object.runInThisContext (vm.js:*)
at Object.<anonymous> ([stdin]-wrapper:*:*)
at Module._compile (internal/modules/cjs/loader.js:*:*)
at evalScript (internal/bootstrap/node.js:*:*)
at evalScript (internal/process/execution.js:*:*)
at Socket.process.stdin.on (internal/bootstrap/node.js:*:*)
at Socket.emit (events.js:*:*)
at endReadableNT (_stream_readable.js:*:*)
@ -40,7 +40,7 @@ Error: hello
at Object.runInThisContext (vm.js:*)
at Object.<anonymous> ([stdin]-wrapper:*:*)
at Module._compile (internal/modules/cjs/loader.js:*:*)
at evalScript (internal/bootstrap/node.js:*:*)
at evalScript (internal/process/execution.js:*:*)
at Socket.process.stdin.on (internal/bootstrap/node.js:*:*)
at Socket.emit (events.js:*:*)
at endReadableNT (_stream_readable.js:*:*)
@ -56,7 +56,7 @@ ReferenceError: y is not defined
at Object.runInThisContext (vm.js:*)
at Object.<anonymous> ([stdin]-wrapper:*:*)
at Module._compile (internal/modules/cjs/loader.js:*:*)
at evalScript (internal/bootstrap/node.js:*:*)
at evalScript (internal/process/execution.js:*:*)
at Socket.process.stdin.on (internal/bootstrap/node.js:*:*)
at Socket.emit (events.js:*:*)
at endReadableNT (_stream_readable.js:*:*)

View File

@ -9,7 +9,7 @@ const common = require('../common');
const assert = require('assert');
const isMainThread = common.isMainThread;
const kMaxModuleCount = isMainThread ? 62 : 84;
const kMaxModuleCount = isMainThread ? 63 : 85;
assert(list.length <= kMaxModuleCount,
`Total length: ${list.length}\n` + list.join('\n')