Skip to content

Commit 7163fbf

Browse files
committed
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 da13c44 commit 7163fbf

12 files changed

+199
-164
lines changed

lib/internal/bootstrap/node.js

+26-103
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();
@@ -305,7 +313,7 @@ function startExecution() {
305313
// This means we are in a Worker context, and any script execution
306314
// will be directed by the worker module.
307315
if (internalBinding('worker').getEnvMessagePort() !== undefined) {
308-
NativeModule.require('internal/worker').setupChild(evalScript);
316+
NativeModule.require('internal/worker').setupChild();
309317
return;
310318
}
311319

@@ -376,7 +384,9 @@ function executeUserCode() {
376384
addBuiltinLibsToObject
377385
} = NativeModule.require('internal/modules/cjs/helpers');
378386
addBuiltinLibsToObject(global);
379-
evalScript('[eval]', wrapForBreakOnFirstLine(getOptionValue('--eval')));
387+
const source = getOptionValue('--eval');
388+
const { evalScript } = NativeModule.require('internal/process/execution');
389+
evalScript('[eval]', source, process._breakFirstLine);
380390
return;
381391
}
382392

@@ -430,7 +440,8 @@ function executeUserCode() {
430440

431441
// User passed '-e' or '--eval' along with `-i` or `--interactive`
432442
if (process._eval != null) {
433-
evalScript('[eval]', wrapForBreakOnFirstLine(process._eval));
443+
const { evalScript } = NativeModule.require('internal/process/execution');
444+
evalScript('[eval]', process._eval, process._breakFirstLine);
434445
}
435446
return;
436447
}
@@ -452,7 +463,8 @@ function readAndExecuteStdin() {
452463
checkScriptSyntax(code, '[stdin]');
453464
} else {
454465
process._eval = code;
455-
evalScript('[stdin]', wrapForBreakOnFirstLine(process._eval));
466+
const { evalScript } = NativeModule.require('internal/process/execution');
467+
evalScript('[stdin]', process._eval, process._breakFirstLine);
456468
}
457469
});
458470
}
@@ -656,95 +668,6 @@ function setupDOMException() {
656668
registerDOMException(DOMException);
657669
}
658670

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