Skip to content

Forcibly kill every forked subprocess. #120

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 2 commits into from
Nov 5, 2015

Conversation

jamestalmage
Copy link
Contributor

Killing the parent process skips execution of end listeners
on remaining forked child processes.

This interferes with nyc if processes are kept running after
test completion.

Refrence: istanbuljs/nyc#52

Killing the parent process skips execution of `end` listeners
on remaining forked child processes.

This interferes with `nyc` if processes are kept running after
test completion.

Refrence: istanbuljs/nyc#52
process.exit(failed > 0 ? 1 : 0);
// Individually kill each child process.
Promise.map(results, function (result) {
return result.kill();
Copy link
Member

Choose a reason for hiding this comment

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

Why are you using promises here? cp.kill() is a synchronous operation. Just do a [].forEach().

Copy link
Contributor Author

Choose a reason for hiding this comment

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

result.kill !== process.kill. It is a method I attached to the result. It returns a Promise that resolves with childProcess.on('exit',.... This is waiting for all the child processes to exit before killing the parent.

result.kill also makes sure process.kill is not being called on any processes that have already exited. Because:

May emit an 'error' event when the signal cannot be delivered. Sending a signal to a child process that has already exited is not an error but may have unforeseen consequences: if the PID (the process ID) has been reassigned to another process, the signal will be delivered to that process instead. What happens next is anyone's guess.

Note that while the function is called kill, the signal delivered to the child process may not actually kill it. kill really just sends a signal to a process.

---- https://nodejs.org/api/child_process.html#child_process_child_kill_signal

process.kill may return synchronously, but we want to wait for children to actually exit. exit events certainly are not processed synchronously.

Proof: https://github.com/jamestalmage/process-kill-is-sync

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Specifically,
nyc uses signal-exit, which intercepts the kill signal and allows it to finish up writing files to disk before the process is actually killed.

I updated jamestalmage/process-kill-is-sync to illustrate signal-exit delaying the exit.

Copy link
Member

Choose a reason for hiding this comment

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

Oh, right. Hard to read diffs on mobile...

@sindresorhus
Copy link
Member

Lgtm.

@vdemedes ?

@sindresorhus
Copy link
Member

@jamestalmage Good find btw. I've been experiencing this problem too.

@SamVerschueren This might be related to our nyc problems in Pageres.

@SamVerschueren
Copy link
Contributor

Nice! 👍

Will have a look back at pageres if this PR is merged (and released :))

var promise = new Promise(function (resolve, reject) {
ps.on('results', function (results) {
results.kill = kill;
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it would be better to attach kill() method to a returned Promise, like on() - https://github.com/jamestalmage/ava/blob/jt-kill-forked-processes/lib/fork.js#L66.

That way, we won't have weird stuff in test results.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We don't have a reference to the promise in cli.js#exit(), only the result. Hence the need to attach it to the result.

That way, we won't have weird stuff in test results.

Not sure how this would create "weird stuff" in test results.

Copy link
Contributor

Choose a reason for hiding this comment

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

Then we better store references to forks, rather than injecting a function in result object.

Not sure how this would create "weird stuff" in test results.

Function in an object with test results is weird stuff.

@vadimdemedes
Copy link
Contributor

So, basically, this PR could be cleaned up by:

  1. Attaching kill() on the returned Promise, not the results object
  2. Don't resolve here - https://github.com/jamestalmage/ava/blob/jt-kill-forked-processes/lib/fork.js#L39, but resolve/reject based on the exit code:
ps.on('exit', function (code) {
  // resolve/reject
});

inside https://github.com/jamestalmage/ava/blob/jt-kill-forked-processes/lib/fork.js#L36

@jamestalmage
Copy link
Contributor Author

@vdemedes
Attaching kill() to the promise is doable.

Your second request is problematic. It forces the process to exit before the results promise is resolved. This is problematic for situations where the tests are done, but something is keeping the process running (i.e. you launched a server and fail to shut it down).

@vadimdemedes
Copy link
Contributor

I don't understand your point. I am just suggesting to resolve/reject based on exit, not results. The forked processes will exit anyway, we are just detecting it a bit differently.

@jamestalmage
Copy link
Contributor Author

The forked processes will exit anyway

Not always, see istanbuljs/nyc#52.

That was primary motivator of this PR.

@jamestalmage
Copy link
Contributor Author

Here is the relevant comment: istanbuljs/nyc#52 (comment)

Firebase has a known issue where the process doesn't die until you explicitly call process.exit(). But I don't understand this issue well enough / have the right context to know if that's related to this issue

Firebase won't be the last place people run into this (launching an express instance that you fail to destroy, etc).

This PR waits until all the results are in, and then goes back and forcibly kills any child processes that haven't shut themselves down already.

It's necessary to keep process closure and test result promise resolution as separate things.

@SamVerschueren
Copy link
Contributor

This is probably also an issue in pageres with nyc.

@jamestalmage
Copy link
Contributor Author

@vdemedes
@4d907aa incorporates the "kill() on promise" request.

Personally, I felt the code read better the way I had it originally, but I will defer to you guys.

As an alternate, what if I defined a non-enumerable kill() property on result?

@vadimdemedes
Copy link
Contributor

I think I will merge this PR and pick it up from then, I still have some tips to clean up the code.

Thank you for your work!

vadimdemedes pushed a commit that referenced this pull request Nov 5, 2015
Kill every forked process after tests are finished
@vadimdemedes vadimdemedes merged commit 5a53900 into avajs:master Nov 5, 2015
@jamestalmage jamestalmage deleted the jt-kill-forked-processes branch November 5, 2015 20:11
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