Skip to content

Fix: avoid using process.exit() when possible #3955

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

Merged
merged 1 commit into from
Jul 20, 2017
Merged

Fix: avoid using process.exit() when possible #3955

merged 1 commit into from
Jul 20, 2017

Conversation

BYK
Copy link
Member

@BYK BYK commented Jul 18, 2017

Summary

Refs #3524. We are using process.exit() pretty liberally although it is strongly advised not to use it since it may cause the stdout to get corrupted/terminated before a full flush. This patch changes all possible process.exit(code) calls with process.exitCode = code statements.

We'd also ideally enable no-process-exit rule in ESLint but it requires an upgrade to ESLint v4 which should be handled separately.

Test plan

Expect tests to pass and actually finish (not run indefinitely due to yarn not exiting at all). Also, the script referenced in #3524 to output something like the following:

DATA 18
DATA 8192
DATA 8192
DATA 5392
DATA 15
EXIT 0

@BYK BYK requested a review from arcanis July 18, 2017 16:50
src/cli/index.js Outdated
@@ -142,7 +142,7 @@ if (outputWrapper) {
if (command.noArguments && commander.args.length) {
reporter.error(reporter.lang('noArguments'));
reporter.info(command.getDocsInfo);
process.exit(1);
process.exitCode = 1;
Copy link
Member

Choose a reason for hiding this comment

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

You need to shortcut the rest of the code to have the same behavior - process.exit doesn't return, but setting process.exitCode will continue executing the rest of the code.

Copy link
Member Author

Choose a reason for hiding this comment

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

Not shortcutting the whole point with this change. We should not need the shortcut behavior and turns out we really don't since all tests are passing. It was only necessary at one point so I left it that way.

Copy link
Member

Choose a reason for hiding this comment

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

I don't think that's a good idea.

  • it kinda violates the principle of the least surprise, since we would no longer have the guarantee that any part of this file would only be executed if no other command matched. It's only a matter of time before we make some mistake that will execute two commands in some edge case.

  • Case in point, your current code has a bug that will make yarn -v print the version number before running yarn install -v (and yarn -v -h will print the version number then call yarn help -v).

  • Even without that, not shortcutting also means that we will be running useless tasks (such as instanciating a reporter etc) even when not needed. That's an issue because those commands must be fast (some tools such as Nuclide might need to call them often, and if their results are late it will slow down the all process).

I'm not against using exitCode, you have a point that shutting down the program before the I/O has finished is bad, but I strongly think we then need to refactor the CLI code to make sure we never have to check that our code won't get called twice - might only be a matter of adding else clauses 😃

@@ -3,7 +3,7 @@ import {forwardSignalToSpawnedProcesses} from './child.js';

function forwardSignalAndExit(signal: string) {
forwardSignalToSpawnedProcesses(signal);
process.exit(1);
throw new Error('Received SIGTERM, terminating.');
Copy link
Member

Choose a reason for hiding this comment

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

Will that print a stacktrace on the console? That won't be helpful for the user.

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 thought it would be. Why do you think it wouldn't?

Copy link
Member

Choose a reason for hiding this comment

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

SIGTERM is the default signal sent by kill. It doesn't make much sense to print a stacktrace (which heavily imply that something went wrong in the program implementation) if the termination came from the outside, don't you think?

Copy link
Member Author

Choose a reason for hiding this comment

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

Alright, makes sense. I'll find an alternative way for this one.

src/cli/index.js Outdated
@@ -142,7 +142,7 @@ if (outputWrapper) {
if (command.noArguments && commander.args.length) {
reporter.error(reporter.lang('noArguments'));
reporter.info(command.getDocsInfo);
process.exit(1);
process.exitCode = 1;
Copy link
Member

Choose a reason for hiding this comment

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

I don't think that's a good idea.

  • it kinda violates the principle of the least surprise, since we would no longer have the guarantee that any part of this file would only be executed if no other command matched. It's only a matter of time before we make some mistake that will execute two commands in some edge case.

  • Case in point, your current code has a bug that will make yarn -v print the version number before running yarn install -v (and yarn -v -h will print the version number then call yarn help -v).

  • Even without that, not shortcutting also means that we will be running useless tasks (such as instanciating a reporter etc) even when not needed. That's an issue because those commands must be fast (some tools such as Nuclide might need to call them often, and if their results are late it will slow down the all process).

I'm not against using exitCode, you have a point that shutting down the program before the I/O has finished is bad, but I strongly think we then need to refactor the CLI code to make sure we never have to check that our code won't get called twice - might only be a matter of adding else clauses 😃

src/cli/index.js Outdated
loudRejection();
handleSignals();

const startArgs = process.argv.slice(0, 2);
Copy link
Member

Choose a reason for hiding this comment

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

What do you think about modularizing and putting this in the main parameters? I can see this function as an interesting entry point to run commands in our testcases - we currently need to instanciate each class and pass them the right properties, which might be tedious and error-prone, so it could be nice to just be able to use run([ 'install', '--check-files' ]) instead and use the actual CLI pipeline.

Copy link
Member

Choose a reason for hiding this comment

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

Actually my comment was more about Line 31, but it's also applicable here.

@BYK BYK force-pushed the stdout-buffer branch from 6ad73b7 to bde7b57 Compare July 19, 2017 15:32
@@ -196,7 +196,7 @@ export default class ConsoleReporter extends BaseReporter {
(err, answer) => {
if (err) {
if (err.message === 'canceled') {
process.exit(1);
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't we exit immediately here? Or reject?

Copy link
Member Author

@BYK BYK Jul 20, 2017

Choose a reason for hiding this comment

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

Yeah, I think you're right. I thought about the same thing and don't know why I didn't merge the reject part. Update coming.

@@ -35,6 +35,8 @@ const compiler = webpack({
output: {
filename: `yarn-${version}.js`,
path: path.join(basedir, 'artifacts'),
library: 'yarn-cli',
libraryTarget: 'commonjs2',
Copy link
Member

Choose a reason for hiding this comment

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

Is this change required? What breaks without 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.

Yes, this is the solution that fixes the single-file bundles. Without this, Webpack exports an empty object, causing yarn-bundled-entry-point.js to fail since main function can't be imported.

@BYK BYK force-pushed the stdout-buffer branch from a231d62 to 579d32f Compare July 20, 2017 15:30
@BYK BYK merged commit 61e402b into master Jul 20, 2017
@BYK BYK deleted the stdout-buffer branch July 20, 2017 16:23
BYK added a commit that referenced this pull request Jul 26, 2017
**Summary**

Upgrade ESLint to version 4. I did this mostly to get [no-process-exit](http://eslint.org/docs/rules/no-process-exit) rule introduced in v4. Refs #3955.

**Test plan**

Lint should pass without errors.
BYK pushed a commit that referenced this pull request Jul 26, 2017
**Summary**

A follow up to #3955 to disable further usage of `process.exit()`.

**Test plan**

`yarn lint` should not produce any errors.
BYK added a commit that referenced this pull request Jul 27, 2017
**Summary**

A follow up to #3955 to disable further usage of `process.exit()`.

**Test plan**

`yarn lint` should not produce any errors.
@skevy
Copy link
Contributor

skevy commented Jul 29, 2017

I noticed when pulling down master and running build-dist that the webpack builds were no longer working.

I traced it back to this PR. You can see in CircleCI the output of the last working build here:

https://circleci.com/gh/yarnpkg/yarn/4533

Notice how $ ./artifacts/yarn-`./dist/bin/yarn --version`.js --version outputs a version number.

When this commit runs in CI, however (https://circleci.com/gh/yarnpkg/yarn/4536), there is no version number output, and I confirm that the webpack build does not work at all.

cc @BYK @arcanis @Daniel15

@Daniel15
Copy link
Member

Hmm, interesting, I wonder why the nightly end-to-end tests didn't fail. They use the nightly Debian packages, which contain the Webpack build inside them. https://build.dan.cx/job/yarn-e2e/label=docker,os=ubuntu-16.04/269/console

@skevy
Copy link
Contributor

skevy commented Jul 29, 2017

I'm not sure why those don't fail (without looking into it more) -- but the reason CircleCI doesn't fail is because yarn --version still exists with code 0, even though it doesn't do anything.

@Daniel15
Copy link
Member

Right. Maybe the build should assert that it actually returns the correct version number.

@skevy
Copy link
Contributor

skevy commented Jul 29, 2017

👍

@BYK
Copy link
Member Author

BYK commented Jul 31, 2017

I'll work on this soon.

@BYK
Copy link
Member Author

BYK commented Jul 31, 2017

Filed #4057, actively working on it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants