Skip to content

Commit 1c4d7eb

Browse files
joyeecheungaddaleax
authored andcommitted
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: #25199 Reviewed-By: James M Snell <[email protected]>
1 parent 7be4a39 commit 1c4d7eb

12 files changed

+199
-162
lines changed

lib/internal/bootstrap/node.js

+26-101
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,30 @@
1818

1919
const { internalBinding, NativeModule } = loaderExports;
2020

21-
const exceptionHandlerState = { captureFn: null };
2221
let getOptionValue;
2322

2423
function startup() {
2524
setupTraceCategoryState();
2625

2726
setupProcessObject();
2827

29-
// Do this good and early, since it handles errors.
30-
setupProcessFatal();
28+
// TODO(joyeecheung): this does not have to done so early, any fatal errors
29+
// thrown before user code execution should simply crash the process
30+
// and we do not care about any clean up at that point. We don't care
31+
// about emitting any events if the process crash upon bootstrap either.
32+
{
33+
const {
34+
fatalException,
35+
setUncaughtExceptionCaptureCallback,
36+
hasUncaughtExceptionCaptureCallback
37+
} = NativeModule.require('internal/process/execution');
38+
39+
process._fatalException = fatalException;
40+
process.setUncaughtExceptionCaptureCallback =
41+
setUncaughtExceptionCaptureCallback;
42+
process.hasUncaughtExceptionCaptureCallback =
43+
hasUncaughtExceptionCaptureCallback;
44+
}
3145

3246
setupGlobalVariables();
3347

@@ -83,20 +97,14 @@ function startup() {
8397
process.reallyExit = rawMethods.reallyExit;
8498
process._kill = rawMethods._kill;
8599

86-
const wrapped = perThreadSetup.wrapProcessMethods(
87-
rawMethods, exceptionHandlerState
88-
);
100+
const wrapped = perThreadSetup.wrapProcessMethods(rawMethods);
89101
process._rawDebug = wrapped._rawDebug;
90102
process.hrtime = wrapped.hrtime;
91103
process.hrtime.bigint = wrapped.hrtimeBigInt;
92104
process.cpuUsage = wrapped.cpuUsage;
93105
process.memoryUsage = wrapped.memoryUsage;
94106
process.kill = wrapped.kill;
95107
process.exit = wrapped.exit;
96-
process.setUncaughtExceptionCaptureCallback =
97-
wrapped.setUncaughtExceptionCaptureCallback;
98-
process.hasUncaughtExceptionCaptureCallback =
99-
wrapped.hasUncaughtExceptionCaptureCallback;
100108
}
101109

102110
NativeModule.require('internal/process/warning').setup();
@@ -311,7 +319,7 @@ function startExecution() {
311319
// This means we are in a Worker context, and any script execution
312320
// will be directed by the worker module.
313321
if (internalBinding('worker').getEnvMessagePort() !== undefined) {
314-
NativeModule.require('internal/worker').setupChild(evalScript);
322+
NativeModule.require('internal/worker').setupChild();
315323
return;
316324
}
317325

@@ -382,7 +390,9 @@ function executeUserCode() {
382390
addBuiltinLibsToObject
383391
} = NativeModule.require('internal/modules/cjs/helpers');
384392
addBuiltinLibsToObject(global);
385-
evalScript('[eval]', wrapForBreakOnFirstLine(getOptionValue('--eval')));
393+
const source = getOptionValue('--eval');
394+
const { evalScript } = NativeModule.require('internal/process/execution');
395+
evalScript('[eval]', source, process._breakFirstLine);
386396
return;
387397
}
388398

@@ -436,7 +446,8 @@ function executeUserCode() {
436446

437447
// User passed '-e' or '--eval' along with `-i` or `--interactive`
438448
if (process._eval != null) {
439-
evalScript('[eval]', wrapForBreakOnFirstLine(process._eval));
449+
const { evalScript } = NativeModule.require('internal/process/execution');
450+
evalScript('[eval]', process._eval, process._breakFirstLine);
440451
}
441452
return;
442453
}
@@ -458,7 +469,8 @@ function readAndExecuteStdin() {
458469
checkScriptSyntax(code, '[stdin]');
459470
} else {
460471
process._eval = code;
461-
evalScript('[stdin]', wrapForBreakOnFirstLine(process._eval));
472+
const { evalScript } = NativeModule.require('internal/process/execution');
473+
evalScript('[stdin]', process._eval, process._breakFirstLine);
462474
}
463475
});
464476
}
@@ -654,93 +666,6 @@ function setupDOMException() {
654666

655667
function noop() {}
656668

657-
function setupProcessFatal() {
658-
const {
659-
executionAsyncId,
660-
clearDefaultTriggerAsyncId,
661-
clearAsyncIdStack,
662-
hasAsyncIdStack,
663-
afterHooksExist,
664-
emitAfter
665-
} = NativeModule.require('internal/async_hooks');
666-
667-
process._fatalException = (er) => {
668-
// It's possible that defaultTriggerAsyncId was set for a constructor
669-
// call that threw and was never cleared. So clear it now.
670-
clearDefaultTriggerAsyncId();
671-
672-
if (exceptionHandlerState.captureFn !== null) {
673-
exceptionHandlerState.captureFn(er);
674-
} else if (!process.emit('uncaughtException', er)) {
675-
// If someone handled it, then great. otherwise, die in C++ land
676-
// since that means that we'll exit the process, emit the 'exit' event.
677-
try {
678-
if (!process._exiting) {
679-
process._exiting = true;
680-
process.exitCode = 1;
681-
process.emit('exit', 1);
682-
}
683-
} catch {
684-
// Nothing to be done about it at this point.
685-
}
686-
try {
687-
const { kExpandStackSymbol } = NativeModule.require('internal/util');
688-
if (typeof er[kExpandStackSymbol] === 'function')
689-
er[kExpandStackSymbol]();
690-
} catch {
691-
// Nothing to be done about it at this point.
692-
}
693-
return false;
694-
}
695-
696-
// If we handled an error, then make sure any ticks get processed
697-
// by ensuring that the next Immediate cycle isn't empty.
698-
NativeModule.require('timers').setImmediate(noop);
699-
700-
// Emit the after() hooks now that the exception has been handled.
701-
if (afterHooksExist()) {
702-
do {
703-
emitAfter(executionAsyncId());
704-
} while (hasAsyncIdStack());
705-
// Or completely empty the id stack.
706-
} else {
707-
clearAsyncIdStack();
708-
}
709-
710-
return true;
711-
};
712-
}
713-
714-
function wrapForBreakOnFirstLine(source) {
715-
if (!process._breakFirstLine)
716-
return source;
717-
const fn = `function() {\n\n${source};\n\n}`;
718-
return `process.binding('inspector').callAndPauseOnStart(${fn}, {})`;
719-
}
720-
721-
function evalScript(name, body) {
722-
const CJSModule = NativeModule.require('internal/modules/cjs/loader');
723-
const path = NativeModule.require('path');
724-
const { tryGetCwd } = NativeModule.require('internal/util');
725-
const cwd = tryGetCwd(path);
726-
727-
const module = new CJSModule(name);
728-
module.filename = path.join(cwd, name);
729-
module.paths = CJSModule._nodeModulePaths(cwd);
730-
const script = `global.__filename = ${JSON.stringify(name)};\n` +
731-
'global.exports = exports;\n' +
732-
'global.module = module;\n' +
733-
'global.__dirname = __dirname;\n' +
734-
'global.require = require;\n' +
735-
'return require("vm").runInThisContext(' +
736-
`${JSON.stringify(body)}, { filename: ` +
737-
`${JSON.stringify(name)}, displayErrors: true });\n`;
738-
const result = module._compile(script, `${name}-wrapper`);
739-
if (getOptionValue('--print')) console.log(result);
740-
// Handle any nextTicks added in the first tick of the program.
741-
process._tickCallback();
742-
}
743-
744669
function checkScriptSyntax(source, filename) {
745670
const CJSModule = NativeModule.require('internal/modules/cjs/loader');
746671
const vm = NativeModule.require('vm');

lib/internal/console/inspector.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
'use strict';
22

3-
const path = require('path');
43
const CJSModule = require('internal/modules/cjs/loader');
54
const { makeRequireFunction } = require('internal/modules/cjs/helpers');
6-
const { tryGetCwd } = require('internal/util');
5+
const { tryGetCwd } = require('internal/process/execution');
76
const { addCommandLineAPI, consoleCall } = internalBinding('inspector');
87

98
// Wrap a console implemented by Node.js with features from the VM inspector
109
function addInspectorApis(consoleFromNode, consoleFromVM) {
1110
// Setup inspector command line API.
12-
const cwd = tryGetCwd(path);
11+
const cwd = tryGetCwd();
1312
const consoleAPIModule = new CJSModule('<inspector console>');
1413
consoleAPIModule.paths =
1514
CJSModule._nodeModulePaths(cwd).concat(CJSModule.globalPaths);

lib/internal/process/execution.js

+149
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
'use strict';
2+
3+
const path = require('path');
4+
5+
const {
6+
codes: {
7+
ERR_INVALID_ARG_TYPE,
8+
ERR_UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET
9+
}
10+
} = require('internal/errors');
11+
12+
const {
13+
executionAsyncId,
14+
clearDefaultTriggerAsyncId,
15+
clearAsyncIdStack,
16+
hasAsyncIdStack,
17+
afterHooksExist,
18+
emitAfter
19+
} = require('internal/async_hooks');
20+
21+
// shouldAbortOnUncaughtToggle is a typed array for faster
22+
// communication with JS.
23+
const { shouldAbortOnUncaughtToggle } = internalBinding('util');
24+
25+
function tryGetCwd() {
26+
try {
27+
return process.cwd();
28+
} catch {
29+
// getcwd(3) can fail if the current working directory has been deleted.
30+
// Fall back to the directory name of the (absolute) executable path.
31+
// It's not really correct but what are the alternatives?
32+
return path.dirname(process.execPath);
33+
}
34+
}
35+
36+
function evalScript(name, body, breakFristLine) {
37+
const CJSModule = require('internal/modules/cjs/loader');
38+
if (breakFristLine) {
39+
const fn = `function() {\n\n${body};\n\n}`;
40+
body = `process.binding('inspector').callAndPauseOnStart(${fn}, {})`;
41+
}
42+
43+
const cwd = tryGetCwd();
44+
45+
const module = new CJSModule(name);
46+
module.filename = path.join(cwd, name);
47+
module.paths = CJSModule._nodeModulePaths(cwd);
48+
const script = `global.__filename = ${JSON.stringify(name)};\n` +
49+
'global.exports = exports;\n' +
50+
'global.module = module;\n' +
51+
'global.__dirname = __dirname;\n' +
52+
'global.require = require;\n' +
53+
'return require("vm").runInThisContext(' +
54+
`${JSON.stringify(body)}, { filename: ` +
55+
`${JSON.stringify(name)}, displayErrors: true });\n`;
56+
const result = module._compile(script, `${name}-wrapper`);
57+
if (require('internal/options').getOptionValue('--print')) {
58+
console.log(result);
59+
}
60+
// Handle any nextTicks added in the first tick of the program.
61+
process._tickCallback();
62+
}
63+
64+
const exceptionHandlerState = { captureFn: null };
65+
66+
function setUncaughtExceptionCaptureCallback(fn) {
67+
if (fn === null) {
68+
exceptionHandlerState.captureFn = fn;
69+
shouldAbortOnUncaughtToggle[0] = 1;
70+
return;
71+
}
72+
if (typeof fn !== 'function') {
73+
throw new ERR_INVALID_ARG_TYPE('fn', ['Function', 'null'], fn);
74+
}
75+
if (exceptionHandlerState.captureFn !== null) {
76+
throw new ERR_UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET();
77+
}
78+
exceptionHandlerState.captureFn = fn;
79+
shouldAbortOnUncaughtToggle[0] = 0;
80+
}
81+
82+
function hasUncaughtExceptionCaptureCallback() {
83+
return exceptionHandlerState.captureFn !== null;
84+
}
85+
86+
function noop() {}
87+
88+
// XXX(joyeecheung): for some reason this cannot be defined at the top-level
89+
// and exported to be written to process._fatalException, it has to be
90+
// returned as an *anonymous function* wrapped inside a factory function,
91+
// otherwise it breaks the test-timers.setInterval async hooks test -
92+
// this may indicate that node::FatalException should fix up the callback scope
93+
// before calling into process._fatalException, or this function should
94+
// take extra care of the async hooks before it schedules a setImmediate.
95+
function createFatalException() {
96+
return (er) => {
97+
// It's possible that defaultTriggerAsyncId was set for a constructor
98+
// call that threw and was never cleared. So clear it now.
99+
clearDefaultTriggerAsyncId();
100+
101+
if (exceptionHandlerState.captureFn !== null) {
102+
exceptionHandlerState.captureFn(er);
103+
} else if (!process.emit('uncaughtException', er)) {
104+
// If someone handled it, then great. otherwise, die in C++ land
105+
// since that means that we'll exit the process, emit the 'exit' event.
106+
try {
107+
if (!process._exiting) {
108+
process._exiting = true;
109+
process.exitCode = 1;
110+
process.emit('exit', 1);
111+
}
112+
} catch {
113+
// Nothing to be done about it at this point.
114+
}
115+
try {
116+
const { kExpandStackSymbol } = require('internal/util');
117+
if (typeof er[kExpandStackSymbol] === 'function')
118+
er[kExpandStackSymbol]();
119+
} catch {
120+
// Nothing to be done about it at this point.
121+
}
122+
return false;
123+
}
124+
125+
// If we handled an error, then make sure any ticks get processed
126+
// by ensuring that the next Immediate cycle isn't empty.
127+
require('timers').setImmediate(noop);
128+
129+
// Emit the after() hooks now that the exception has been handled.
130+
if (afterHooksExist()) {
131+
do {
132+
emitAfter(executionAsyncId());
133+
} while (hasAsyncIdStack());
134+
// Or completely empty the id stack.
135+
} else {
136+
clearAsyncIdStack();
137+
}
138+
139+
return true;
140+
};
141+
}
142+
143+
module.exports = {
144+
tryGetCwd,
145+
evalScript,
146+
fatalException: createFatalException(),
147+
setUncaughtExceptionCaptureCallback,
148+
hasUncaughtExceptionCaptureCallback
149+
};

0 commit comments

Comments
 (0)