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

Signal handling improvements #10958

Merged
merged 2 commits into from Jul 21, 2022
Merged

Signal handling improvements #10958

merged 2 commits into from Jul 21, 2022

Conversation

Seldaek
Copy link
Member

@Seldaek Seldaek commented Jul 20, 2022

Fixes #6059

The main improvement is that when we get a SIGINT/SIGTERM/SIGHUP while an external process is running we now wait until that one terminates before terminating.

This means Composer should now show the full output of child processes before killing itself, and also it gives a chance to child processes to handle the signal more gracefully.

@Seldaek Seldaek added this to the 2.4 milestone Jul 20, 2022
@private-packagist
Copy link

composer.lock

Package changes

Package Operation From To Changes
seld/signal-handler add - 2.0.1 view code

Settings · Docs · Powered by Private Packagist

$process->run($callback);
$signalHandler = SignalHandler::create([SignalHandler::SIGINT, SignalHandler::SIGTERM, SignalHandler::SIGHUP], function (string $signal) {
if ($this->io !== null) {
$this->io->writeError('Received '.$signal.', aborting when child process is done', true, IOInterface::DEBUG);
Copy link
Member

Choose a reason for hiding this comment

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

Maybe I'm wrong about how this works, but I don't think anything here actually forwards the signals to the child processes? At least a sigint would likely correctly interrupt a child process too if it was to receive it as the user intended?

Copy link
Member Author

@Seldaek Seldaek Jul 20, 2022

Choose a reason for hiding this comment

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

When you signal (at least with a ctrl-C, but I can't imagine it's different with other signaling methods) the whole process tree gets signaled, so the child gets it even without forwarding.

Copy link
Member Author

Choose a reason for hiding this comment

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

There is one issue I have found in symfony/process it's that even if the child process handles/suppresses the signal, and exit(0), Process will still throw a ProcessSignaledException. I need to investigate a bit more to see whether this can be fixed as it'd be nice for completeness but I am not sure this may be a PHP limitation, and anyway it can't happen in this repo so I'm ignoring this for now.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm a bit late to the party here, forgive me.

I just want to clarify one important aspect: Ctrl+C is different from a SIGINT.

In a terminal, a Ctrl+C sends a SIGINT to every process that has the same process group ID as the foreground process group. This is the equivalent of doing kill -INT -$composerpid (negative process number means process group number, and the lead process of a process group's process ID defines the group ID).

If, however, you send a SIGINT to only the Composer process, the children will not receive the same signal (and neither will they for a SIGTERM, SIGKILL, etc). If Composer terminates and they're still running, they will become orphaned, and may receive a SIGHUP, which might cause them to exit, but YMMV.

IMO, the correct behavior for Composer on SIGINT in this particular situation is to do nothing. It's fine to ignore a SIGINT if the program chooses so, and if the SIGINT was sent because of a Ctrl+C keystroke, then the child process(es) will receive this signal and (probably) terminate themselves (quickly or slowly).

However, it's still important to detect the SIGINT, and, if the child exited due to a SIGINT as well, terminate Composer itself by signaling SIGINT to itself, rather than exiting normally.

The reason a child process terminated can be checked using pcntl_wifsignaled() and friends, to distinguish between "the child exited zero or non-zero" and "the child exited due to a signal". On the shell, this is represented using an exit code greater than 128 (128+$SIGNALNO), but that's just how the shell represents it.

Distinguishing these cases, and correctly terminating oneself using the right signal, becomes important in e.g. loops. http://mywiki.wooledge.org/SignalTrap#Special_Note_On_SIGINT_and_SIGQUIT (and the rest of the article) have some background info there, and https://www.cons.org/cracauer/sigint.html is even more detailed.

For SIGTERM, that signal should be passed on to the immediate child process, and, after a certain timeout, to all child processes, possibly followed by a SIGKILL, to ensure correct termination.

Welcome to the world of process management ;)

@Seldaek Seldaek merged commit d0b60f1 into composer:main Jul 21, 2022
@Seldaek Seldaek deleted the signal_fixes branch July 21, 2022 09:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Passing SIGINT to script process
3 participants