-
Notifications
You must be signed in to change notification settings - Fork 29.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
repl: emit uncaughtException #20803
repl: emit uncaughtException #20803
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -138,16 +138,47 @@ global or scoped variable, the input `fs` will be evaluated on-demand as | |
``` | ||
|
||
#### Global Uncaught Exceptions | ||
<!-- YAML | ||
changes: | ||
- version: REPLACEME | ||
pr-url: https://github.com/nodejs/node/pull/20803 | ||
description: The uncaughtException event is from now on triggered if the | ||
repl is used as standalone program. | ||
--> | ||
|
||
The REPL uses the [`domain`][] module to catch all uncaught exceptions for that | ||
REPL session. | ||
|
||
This use of the [`domain`][] module in the REPL has these side effects: | ||
|
||
* Uncaught exceptions do not emit the [`'uncaughtException'`][] event. | ||
* Uncaught exceptions only emit the [`'uncaughtException'`][] event if the | ||
`repl` is used as standalone program. If the `repl` is included anywhere in | ||
another application, adding this event synchronous will throw an | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is meant by "adding this event synchronous"? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is actually outdated in the other PR. In this version I was not able to actually handle listeners that were added asynchronously. E.g., |
||
[`ERR_INVALID_REPL_INPUT`][] error! | ||
* Trying to use [`process.setUncaughtExceptionCaptureCallback()`][] throws | ||
an [`ERR_DOMAIN_CANNOT_SET_UNCAUGHT_EXCEPTION_CAPTURE`][] error. | ||
|
||
As standalone program: | ||
BridgeAR marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
```js | ||
process.on('uncaughtException', () => console.log('Uncaught')); | ||
|
||
throw new Error('foobar'); | ||
// Uncaught | ||
``` | ||
|
||
When used in another application: | ||
|
||
```js | ||
process.on('uncaughtException', () => console.log('Uncaught')); | ||
// TypeError [ERR_INVALID_REPL_INPUT]: Unhandled exception listeners can not be | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: perhaps "Listeners for |
||
// used in the REPL | ||
|
||
throw new Error('foobar'); | ||
// Thrown: | ||
// Error: foobar | ||
``` | ||
|
||
#### Assignment of the `_` (underscore) variable | ||
<!-- YAML | ||
changes: | ||
|
@@ -661,6 +692,7 @@ For an example of running a REPL instance over [curl(1)][], see: | |
[`'uncaughtException'`]: process.html#process_event_uncaughtexception | ||
[`--experimental-repl-await`]: cli.html#cli_experimental_repl_await | ||
[`ERR_DOMAIN_CANNOT_SET_UNCAUGHT_EXCEPTION_CAPTURE`]: errors.html#errors_err_domain_cannot_set_uncaught_exception_capture | ||
[`ERR_INVALID_REPL_INPUT`]: errors.html#errors_err_invalid_repl_input | ||
[`domain`]: domain.html | ||
[`process.setUncaughtExceptionCaptureCallback()`]: process.html#process_process_setuncaughtexceptioncapturecallback_fn | ||
[`readline.InterfaceCompleter`]: readline.html#readline_use_of_the_completer_function | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
'use strict'; | ||
|
||
require('../common'); | ||
|
||
// Adding an `uncaughtException` listener in an REPL instance suppresses errors | ||
// in the whole application. | ||
// Closing the instance won't remove those listeners either. | ||
|
||
if (process.argv[2] === 'child') { | ||
|
||
const ArrayStream = require('../common/arraystream'); | ||
const repl = require('repl'); | ||
|
||
let accum = ''; | ||
|
||
const output = new ArrayStream(); | ||
output.write = (data) => accum += data.replace('\r', ''); | ||
|
||
const r = repl.start({ | ||
prompt: '', | ||
input: new ArrayStream(), | ||
output, | ||
terminal: false, | ||
useColors: false, | ||
global: false | ||
}); | ||
|
||
r.write( | ||
'setTimeout(() => {\n' + | ||
' process.on("uncaughtException", () => console.log("Foo"));\n' + | ||
'}, 1);\n' | ||
); | ||
|
||
// Event listeners added to the global `process` won't be removed after | ||
// closing the REPL instance! Those should be removed again, especially since | ||
// the REPL's `global` option is set to `false`. | ||
r.close(); | ||
|
||
setTimeout(() => { | ||
// This should definitely not be silenced! | ||
throw new Error('HU'); | ||
}, 2); | ||
|
||
setTimeout(() => { | ||
console.error('FAILED'); | ||
process.exit(1); | ||
}, 10); | ||
|
||
return; | ||
} | ||
|
||
const cp = require('child_process'); | ||
const assert = require('assert'); | ||
|
||
const res = cp.spawnSync( | ||
process.execPath, | ||
[__filename, 'child'], | ||
{ encoding: 'utf8' } | ||
); | ||
|
||
assert.notStrictEqual(res.stderr, 'FAILED\n'); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,7 +5,6 @@ const fixtures = require('../common/fixtures'); | |
const assert = require('assert'); | ||
const repl = require('repl'); | ||
|
||
|
||
function run({ command, expected }) { | ||
let accum = ''; | ||
|
||
|
@@ -40,8 +39,6 @@ process.on('uncaughtException', (e) => { | |
throw e; | ||
}); | ||
|
||
process.on('exit', () => (Error.prepareStackTrace = origPrepareStackTrace)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems unrelated to the PR changes. What am I missing? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is somewhat unrelated. In a former PR this test was impacted as well and I noticed that this is actually not necessary, so I just went ahead and removed it. |
||
|
||
const tests = [ | ||
{ | ||
// test .load for a file that throws | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
'use strict'; | ||
const common = require('../common'); | ||
const assert = require('assert'); | ||
const cp = require('child_process'); | ||
const child = cp.spawn(process.execPath, ['-i']); | ||
let output = ''; | ||
|
||
child.stdout.setEncoding('utf8'); | ||
child.stdout.on('data', (data) => { | ||
output += data; | ||
}); | ||
|
||
child.on('exit', common.mustCall(() => { | ||
const results = output.replace(/^> /mg, '').split('\n'); | ||
assert.deepStrictEqual( | ||
results, | ||
[ | ||
'Thrown:', | ||
'ReferenceError: x is not defined', | ||
'short', | ||
'undefined', | ||
'Foobar', | ||
'' | ||
] | ||
); | ||
})); | ||
|
||
child.stdin.write('x\n'); | ||
child.stdin.write( | ||
'process.on("uncaughtException", () => console.log("Foobar"));' + | ||
'console.log("short")\n'); | ||
child.stdin.write('x\n'); | ||
child.stdin.end(); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
'use strict'; | ||
require('../common'); | ||
const ArrayStream = require('../common/arraystream'); | ||
const assert = require('assert'); | ||
const repl = require('repl'); | ||
|
||
let count = 0; | ||
|
||
function run({ command, expected }) { | ||
let accum = ''; | ||
|
||
const output = new ArrayStream(); | ||
output.write = (data) => accum += data.replace('\r', ''); | ||
|
||
const r = repl.start({ | ||
prompt: '', | ||
input: new ArrayStream(), | ||
output, | ||
terminal: false, | ||
useColors: false | ||
}); | ||
|
||
r.write(`${command}\n`); | ||
if (typeof expected === 'string') { | ||
assert.strictEqual(accum, expected); | ||
} else { | ||
assert(expected.test(accum), accum); | ||
} | ||
r.close(); | ||
count++; | ||
} | ||
|
||
const tests = [ | ||
{ | ||
command: 'x', | ||
expected: 'Thrown:\n' + | ||
'ReferenceError: x is not defined\n' | ||
}, | ||
{ | ||
command: 'process.on("uncaughtException", () => console.log("Foobar"));\n', | ||
expected: /^Thrown:\nTypeError \[ERR_INVALID_REPL_INPUT]: Unhandled exception/ | ||
}, | ||
{ | ||
command: 'x;\n', | ||
expected: 'Thrown:\n' + | ||
'ReferenceError: x is not defined\n' | ||
}, | ||
{ | ||
command: 'process.on("uncaughtException", () => console.log("Foobar"));' + | ||
'console.log("Baz");\n', | ||
// eslint-disable-next-line node-core/no-unescaped-regexp-dot | ||
expected: /^Baz(.|\n)*ERR_INVALID_REPL_INPUT/ | ||
} | ||
]; | ||
|
||
process.on('exit', () => { | ||
assert.strictEqual(count, tests.length); | ||
}); | ||
|
||
tests.forEach(run); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand what is meant by "can or may not be used".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought I'll keep it broad so that it could be used for different REPL input. So far it will only be used for uncaught exception listeners, so only the
may
part applies. I am happy to change it to onlymay
or what ever you have as alternative.