Skip to content
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: don't use deprecated domain module #55204

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

avivkeller
Copy link
Member

@avivkeller avivkeller commented Oct 1, 2024

This PR refactors the REPL module by eliminating the deprecated node:domain module. It replaces its functionality with a modern approach using a try/catch block and process listener.

Why?

The domain module has been deprecated for a while, and its use is discouraged. Despite this, Node.js still relies on it in a key component: the REPL. To lead by example and promote best practices, this PR updates the REPL module to use more up-to-date error-handling mechanisms, completely removing reliance on the deprecated domain module.

Additionally, this update simplifies the REPL's code and results in a slight performance improvement—though the gains are give-or-take a few ticks and may not be noticeable.

Breaking Changes

This PR is marked as semver-major PRs that contain breaking changes and should be released in the next major version. because it introduces functional changes to the REPL:

  • Removal of options.domain: This undocumented feature is removed (without a formal deprecation cycle, given the domain module itself is deprecated, and the feature was never documented nor tested).
  • Removal of ERR_INVALID_REPL_INPUT: This error is no longer needed, as after this change, there are no restricted inputs.

This PR is also marked needs-citgm PRs that need a CITGM CI run. in case any of the above cause breakages.


CC @nodejs/repl
CC @nodejs/tsc due to semver-major PRs that contain breaking changes and should be released in the next major version.

@avivkeller avivkeller added the semver-major PRs that contain breaking changes and should be released in the next major version. label Oct 1, 2024
@nodejs-github-bot nodejs-github-bot added errors Issues and PRs related to JavaScript errors originated in Node.js core. needs-ci PRs that need a full CI run. repl Issues and PRs related to the REPL subsystem. labels Oct 1, 2024
@avivkeller avivkeller added the needs-citgm PRs that need a CITGM CI run. label Oct 1, 2024
@avivkeller avivkeller marked this pull request as draft October 1, 2024 00:33
@avivkeller

This comment was marked as resolved.

@avivkeller avivkeller marked this pull request as ready for review October 1, 2024 00:34
doc/api/repl.md Outdated Show resolved Hide resolved
lib/repl.js Outdated Show resolved Hide resolved
test/parallel/test-repl-save-load.js Show resolved Hide resolved
test/parallel/test-repl-tab-complete-nested-repls.js Outdated Show resolved Hide resolved
test/parallel/test-repl-tab.js Show resolved Hide resolved
test/parallel/test-repl-top-level-await.js Outdated Show resolved Hide resolved
Copy link

codecov bot commented Oct 1, 2024

Codecov Report

Attention: Patch coverage is 92.94118% with 6 lines in your changes missing coverage. Please review.

Project coverage is 88.42%. Comparing base (fa8f149) to head (b0d6991).
Report is 229 commits behind head on main.

Files with missing lines Patch % Lines
lib/repl.js 92.94% 5 Missing and 1 partial ⚠️
Additional details and impacted files
@@           Coverage Diff            @@
##             main   #55204    +/-   ##
========================================
  Coverage   88.41%   88.42%            
========================================
  Files         652      654     +2     
  Lines      186918   187688   +770     
  Branches    36072    36126    +54     
========================================
+ Hits       165270   165956   +686     
- Misses      14891    14963    +72     
- Partials     6757     6769    +12     
Files with missing lines Coverage Δ
lib/internal/errors.js 96.99% <ø> (-0.01%) ⬇️
lib/repl.js 94.72% <92.94%> (-0.25%) ⬇️

... and 139 files with indirect coverage changes

---- 🚨 Try these New Features:

@avivkeller avivkeller marked this pull request as draft October 1, 2024 19:54
@avivkeller avivkeller added the domain Issues and PRs related to the domain subsystem. label Oct 1, 2024
@avivkeller avivkeller marked this pull request as ready for review October 1, 2024 20:00
@avivkeller avivkeller requested review from aduh95 and anonrig October 1, 2024 20:01
@avivkeller avivkeller added the review wanted PRs that need reviews. label Oct 5, 2024
lib/repl.js Outdated Show resolved Hide resolved
Copy link
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a rough feeling that you'd need to add
a FinalizationRegistry to clear up any instances

lib/repl.js Outdated Show resolved Hide resolved
@avivkeller avivkeller removed the review wanted PRs that need reviews. label Oct 7, 2024
@avivkeller
Copy link
Member Author

I have a rough feeling that you'd need to add a FinalizationRegistry to clear up any instances

Why? Is unsetting the callback on close() not enough?

@avivkeller avivkeller marked this pull request as draft October 16, 2024 15:16
@avivkeller avivkeller added the wip Issues and PRs that are still a work in progress. label Oct 16, 2024
lib/repl.js Show resolved Hide resolved
@avivkeller avivkeller removed the wip Issues and PRs that are still a work in progress. label Oct 22, 2024
@avivkeller avivkeller marked this pull request as ready for review October 22, 2024 15:00
Copy link
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

I'm convinced.

@avivkeller avivkeller added author ready PRs that have at least one approval, no pending requests for changes, and a CI started. request-ci Add this label to start a Jenkins CI on a PR. labels Oct 26, 2024
@github-actions github-actions bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Oct 26, 2024
@nodejs-github-bot
Copy link
Collaborator

@avivkeller

This comment has been minimized.

@aduh95
Copy link
Contributor

aduh95 commented Nov 3, 2024

Can someone start a CITGM if they thinks it's necessary. IMO we can use the most recent main branch CITGM for comparison

I doubt we have any package on CITGM that use repl, do we?

@avivkeller avivkeller removed the needs-citgm PRs that need a CITGM CI run. label Nov 3, 2024
@avivkeller
Copy link
Member Author

avivkeller commented Nov 4, 2024

Got it! I've removed the label. IIRC semver-major PRs need two TSC approvals to land, would you mind reviewing?

@avivkeller avivkeller added the review wanted PRs that need reviews. label Nov 5, 2024
@mcollina mcollina added the request-ci Add this label to start a Jenkins CI on a PR. label Nov 10, 2024
@github-actions github-actions bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Nov 10, 2024
@nodejs-github-bot
Copy link
Collaborator

@nodejs-github-bot
Copy link
Collaborator

CI: https://ci.nodejs.org/job/node-test-pull-request/63530/

@mcollina mcollina added the tsc-agenda Issues and PRs to discuss during the meetings of the TSC. label Nov 18, 2024
@mcollina
Copy link
Member

@nodejs/tsc add it to the agenda for visibility.


* Uncaught exceptions only emit the [`'uncaughtException'`][] event in the
standalone REPL. Adding a listener for this event in a REPL within
another Node.js program results in [`ERR_INVALID_REPL_INPUT`][].
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd replace this section with one that describes the new behavior and amend the changes: YAML entry instead of dropping it (since the new behavior is definitely one that doesn't matter to some users but will have a significant negative impact on users who rely on it)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think a specific section is needed, IMO there's nothing to document. it adds a listener to the process object, what negative impact does that have?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@redyetidev Well, before this change, uncaughtException listeners wouldn't have seen uncaught exceptions coming from the REPL, and now they do, right? It's not a massive change or anything, but I'd still document it somewhere. If you feel that no separate section is necessary, I'd still keep the changes: block to document the change in behavior over time

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's incorrect. Before this change, uncaughtException listeners still saw REPL exceptions

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops. Nevermind then!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am confused about this change. As far as I see it in the code, only the stand alone repl works with the uncaughtException listeners. In case more than a single REPL instance is running, it is not possible to identify the correct listeners, so none receive the errors.

Copy link
Member

@addaleax addaleax left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs docs; I'd also add a test for the specific behavior that we now have when two separate REPL instances receive the same uncaught exception.

There's probably even room for a way to restore the current behavior (which is imo the clearly correct one, even if it makes use of code that's deprecated for good reasons) – we'd a) need to make the REPL take an option that tells it whether to install the global process.on('uncaughtException') listener and b) allow users to set that flag to off, and start a REPL within a domain on their own + move errors from the domain to the REPL instance. That's not too complex, I think. I probably misread the code and the current logic is trying to re-establish this behavior, all good.

@avivkeller
Copy link
Member Author

I'd also add a test for the specific behavior that we now have when two separate REPL instances receive the same uncaught exception.

That won't happen. It's specifically set up so that an uncaught exception triggered from REPL (A) won't affect REPL (B). A test to ensure this is https://github.com/nodejs/node/pull/55204/files#diff-ca6f6e3faa6e2dc3fba74eabfc212f36fa2f84b7a17e3f73662d24b678d18f73 (although it doesn't check two REPLs, rather it verifies that an uncaught exception triggered from outside the REPL does not affect the REPL, which [testing-wise] is a similar test)

Copy link
Member

@BridgeAR BridgeAR left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is definitely nice work, I still think there are a few open parts though.

@@ -612,13 +626,8 @@ function REPLServer(prompt,
}
} catch (e) {
err = e;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: the assignment is AFAIK not needed anymore

},
});

let kHasSetUncaughtListener = false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a constant, so I would name the variable different.

Suggested change
let kHasSetUncaughtListener = false;
let hasUncaughtExceptionListener = false;

}
};

self._onEvalError = function _onEvalError(e) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be great not to expose this property.
I would just pass the instance to the method so that this method is a purely internal one.

For testing purposes, we would only have to verify that nothing else is written to REPLinstance.output.write().

self._domain.on('error', function debugDomainError(e) {
debug('domain error');
try {
FunctionPrototypeApply(eval_, this, args);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

eval_ could be an async function provided by the user. Would it be a good idea to await that method? I am not certain if that would improve things or if it makes it more difficult


const kBufferedCommandSymbol = Symbol('bufferedCommand');
const kContextId = Symbol('contextId');
const kLoadingSymbol = Symbol('loading');
const kListeningREPLs = new SafeSet();
const kAsyncREPLMap = new SafeMap();
let kActiveREPL;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let kActiveREPL;
let activeREPL;

Comment on lines +234 to +235
if (kActiveREPL) {
kActiveREPL._onEvalError(er);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we definitely safe, that the active repl is definitely the correct one?
Let's say one repl starts an operation at time 0 and the operation needs 4 time intervals. A second repl instance start an operation at time 1 and that operation needs 2 intervals. The first instance fails after 2 intervals. Which repl instance is the error now reported upon? Do we maybe have a test for this?


* Uncaught exceptions only emit the [`'uncaughtException'`][] event in the
standalone REPL. Adding a listener for this event in a REPL within
another Node.js program results in [`ERR_INVALID_REPL_INPUT`][].
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am confused about this change. As far as I see it in the code, only the stand alone repl works with the uncaughtException listeners. In case more than a single REPL instance is running, it is not possible to identify the correct listeners, so none receive the errors.

@BridgeAR BridgeAR removed the tsc-agenda Issues and PRs to discuss during the meetings of the TSC. label Dec 5, 2024
@aduh95 aduh95 removed the author ready PRs that have at least one approval, no pending requests for changes, and a CI started. label Dec 7, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
domain Issues and PRs related to the domain subsystem. errors Issues and PRs related to JavaScript errors originated in Node.js core. needs-ci PRs that need a full CI run. repl Issues and PRs related to the REPL subsystem. review wanted PRs that need reviews. semver-major PRs that contain breaking changes and should be released in the next major version.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants