Skip to content

Commit

Permalink
process: move eval and exception bootstrap ito process/execution.js
Browse files Browse the repository at this point in the history
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: #25199
Reviewed-By: James M Snell <jasnell@gmail.com>
  • Loading branch information
joyeecheung authored and BridgeAR committed Jan 17, 2019
1 parent f32e6a8 commit 456b1b5
Show file tree
Hide file tree
Showing 12 changed files with 199 additions and 162 deletions.
127 changes: 26 additions & 101 deletions lib/internal/bootstrap/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,30 @@

const { internalBinding, NativeModule } = loaderExports;

const exceptionHandlerState = { captureFn: null };
let getOptionValue;

function startup() {
setupTraceCategoryState();

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();

Expand Down Expand Up @@ -83,20 +97,14 @@ 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;
process.cpuUsage = wrapped.cpuUsage;
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();
Expand Down Expand Up @@ -311,7 +319,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;
}

Expand Down Expand Up @@ -382,7 +390,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;
}

Expand Down Expand Up @@ -436,7 +446,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;
}
Expand All @@ -458,7 +469,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);
}
});
}
Expand Down Expand Up @@ -654,93 +666,6 @@ function setupDOMException() {

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');
Expand Down
5 changes: 2 additions & 3 deletions lib/internal/console/inspector.js
Original file line number Diff line number Diff line change
@@ -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);
Expand Down
149 changes: 149 additions & 0 deletions lib/internal/process/execution.js
Original file line number Diff line number Diff line change
@@ -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
};
Loading

0 comments on commit 456b1b5

Please sign in to comment.