bug #38845 [Console] Register signal handling only for commands implemeting SignalableCommandInterface (lyrixx)

This PR was merged into the 5.x branch.

Discussion
----------

[Console] Register signal handling only for commands implemeting SignalableCommandInterface

| Q             | A
| ------------- | ---
| Branch?       | 5.x for features
| Bug fix?      | yes
| New feature?  | no
| Deprecations? | no
| Tickets       | Fix #38820
| License       | MIT
| Doc PR        |

---

Actually, it does not make sens to listen all signals for all commands.

This commit also add more tests for this part of code.

Commits
-------

37b1faec8c [Console] Register signal handling only for commands implemeting SignalableCommandInterface
This commit is contained in:
Robin Chalas 2020-10-27 18:00:50 +01:00
commit 268b5b746c
2 changed files with 82 additions and 17 deletions

View File

@ -286,23 +286,6 @@ class Application implements ResetInterface
$command = $this->find($alternative);
}
if ($this->dispatcher && $this->signalRegistry) {
foreach ($this->signalsToDispatchEvent as $signal) {
$event = new ConsoleSignalEvent($command, $input, $output, $signal);
$this->signalRegistry->register($signal, function ($signal, $hasNext) use ($event) {
$this->dispatcher->dispatch($event, ConsoleEvents::SIGNAL);
// No more handlers, we try to simulate PHP default behavior
if (!$hasNext) {
if (!\in_array($signal, [\SIGUSR1, \SIGUSR2], true)) {
exit(0);
}
}
});
}
}
$this->runningCommand = $command;
$exitCode = $this->doRunCommand($command, $input, $output);
$this->runningCommand = null;
@ -961,6 +944,24 @@ class Application implements ResetInterface
if (!$this->signalRegistry) {
throw new RuntimeException('Unable to subscribe to signal events. Make sure that the `pcntl` extension is installed and that "pcntl_*" functions are not disabled by your php.ini\'s "disable_functions" directive.');
}
if ($this->dispatcher) {
foreach ($this->signalsToDispatchEvent as $signal) {
$event = new ConsoleSignalEvent($command, $input, $output, $signal);
$this->signalRegistry->register($signal, function ($signal, $hasNext) use ($event) {
$this->dispatcher->dispatch($event, ConsoleEvents::SIGNAL);
// No more handlers, we try to simulate PHP default behavior
if (!$hasNext) {
if (!\in_array($signal, [\SIGUSR1, \SIGUSR2], true)) {
exit(0);
}
}
});
}
}
foreach ($command->getSubscribedSignals() as $signal) {
$this->signalRegistry->register($signal, [$command, 'handleSignal']);
}

View File

@ -14,6 +14,7 @@ namespace Symfony\Component\Console\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Command\SignalableCommandInterface;
use Symfony\Component\Console\CommandLoader\FactoryCommandLoader;
use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
@ -1808,6 +1809,39 @@ class ApplicationTest extends TestCase
$app->setCommandLoader($loader);
$app->get('test');
}
/**
* @requires extension pcntl
*/
public function testSignal()
{
$command = new SignableCommand();
$dispatcherCalled = false;
$dispatcher = new EventDispatcher();
$dispatcher->addListener('console.signal', function () use (&$dispatcherCalled) {
$dispatcherCalled = true;
});
$application = new Application();
$application->setAutoExit(false);
$application->setDispatcher($dispatcher);
$application->setSignalsToDispatchEvent(SIGALRM);
$application->add($command);
$this->assertFalse($command->signaled);
$this->assertFalse($dispatcherCalled);
$this->assertSame(0, $application->run(new ArrayInput(['signal'])));
$this->assertFalse($command->signaled);
$this->assertFalse($dispatcherCalled);
$command->loop = 100000;
pcntl_alarm(1);
$this->assertSame(1, $application->run(new ArrayInput(['signal'])));
$this->assertTrue($command->signaled);
$this->assertTrue($dispatcherCalled);
}
}
class CustomApplication extends Application
@ -1865,3 +1899,33 @@ class DisabledCommand extends Command
return false;
}
}
class SignableCommand extends Command implements SignalableCommandInterface
{
public $signaled = false;
public $loop = 100;
protected static $defaultName = 'signal';
public function getSubscribedSignals(): array
{
return [SIGALRM];
}
public function handleSignal(int $signal): void
{
$this->signaled = true;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
for ($i = 0; $i < $this->loop; ++$i) {
usleep(100);
if ($this->signaled) {
return 1;
}
}
return 0;
}
}