Skip to content

Commit

Permalink
[Console] Rework the signal integration
Browse files Browse the repository at this point in the history
  • Loading branch information
lyrixx committed Aug 13, 2020
1 parent ae677cc commit 93c7e1f
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 29 deletions.
34 changes: 25 additions & 9 deletions src/Symfony/Component/Console/Application.php
Expand Up @@ -14,6 +14,7 @@
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Command\HelpCommand;
use Symfony\Component\Console\Command\ListCommand;
use Symfony\Component\Console\Command\SignalableCommandInterface;
use Symfony\Component\Console\CommandLoader\CommandLoaderInterface;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\Console\Event\ConsoleErrorEvent;
Expand Down Expand Up @@ -79,13 +80,18 @@ class Application implements ResetInterface
private $singleCommand = false;
private $initialized;
private $signalRegistry;
private $signalsToDispatchEvent = [];

public function __construct(string $name = 'UNKNOWN', string $version = 'UNKNOWN')
{
$this->name = $name;
$this->version = $version;
$this->terminal = new Terminal();
$this->defaultCommand = 'list';
$this->signalRegistry = new SignalRegistry();
if (\defined('SIGINT')) {
$this->signalsToDispatchEvent = [SIGINT, SIGTERM, SIGUSR1, SIGUSR2];
}
}

/**
Expand All @@ -101,9 +107,14 @@ public function setCommandLoader(CommandLoaderInterface $commandLoader)
$this->commandLoader = $commandLoader;
}

public function setSignalRegistry(SignalRegistry $signalRegistry)
public function getSignalRegistry(): SignalRegistry
{
$this->signalRegistry = $signalRegistry;
return $this->signalRegistry;
}

public function setSignalsToDispatchEvent(int ...$signalsToDispatchEvent)
{
$this->signalsToDispatchEvent = $signalsToDispatchEvent;
}

/**
Expand Down Expand Up @@ -268,14 +279,13 @@ public function doRun(InputInterface $input, OutputInterface $output)
$command = $this->find($alternative);
}

if ($this->signalRegistry) {
foreach ($this->signalRegistry->getHandlingSignals() as $handlingSignal) {
$event = new ConsoleSignalEvent($command, $input, $output, $handlingSignal);
$onSignalHandler = function () use ($event) {
$this->dispatcher->dispatch($event, ConsoleEvents::SIGNAL);
};
if ($this->dispatcher) {
foreach ($this->signalsToDispatchEvent as $signal) {
$event = new ConsoleSignalEvent($command, $input, $output, $signal);

$this->signalRegistry->register($handlingSignal, $onSignalHandler);
$this->signalRegistry->register($signal, function () use ($event) {
$this->dispatcher->dispatch($event, ConsoleEvents::SIGNAL);
});
}
}

Expand Down Expand Up @@ -926,6 +936,12 @@ protected function doRunCommand(Command $command, InputInterface $input, OutputI
}
}

if ($command instanceof SignalableCommandInterface) {
foreach ($command->getSubscribedSignals() as $signal) {
$this->signalRegistry->register($signal, [$command, 'handleSignal']);
}
}

if (null === $this->dispatcher) {
return $command->run($input, $output);
}
Expand Down
4 changes: 4 additions & 0 deletions src/Symfony/Component/Console/CHANGELOG.md
Expand Up @@ -7,6 +7,10 @@ CHANGELOG
* Added `SingleCommandApplication::setAutoExit()` to allow testing via `CommandTester`
* added support for multiline responses to questions through `Question::setMultiline()`
and `Question::isMultiline()`
* Added `SignalRegistry` class to stack signals handlers
* Added support for signals:
* Added `Application::getSignalRegistry()` and `Application::setSignalsToDispatchEvent()` methods
* Added `SignalableCommandInterface` interface

5.1.0
-----
Expand Down
@@ -0,0 +1,30 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Console\Command;

/**
* Interface for command reacting to signal.
*
* @author Grégoire Pineau <lyrixx@lyrix.info>
*/
interface SignalableCommandInterface
{
/**
* Returns the list of signals to subscribe.
*/
public function getSubscribedSignals(): array;

/**
* The method will be called when the application is signaled.
*/
public function handleSignal(int $signal): void;
}
29 changes: 9 additions & 20 deletions src/Symfony/Component/Console/SignalRegistry/SignalRegistry.php
Expand Up @@ -13,26 +13,27 @@

final class SignalRegistry
{
private $registeredSignals = [];

private $handlingSignals = [];
private $signalHandlers = [];

public function __construct()
{
pcntl_async_signals(true);
if (\function_exists('pcntl_async_signals')) {
pcntl_async_signals(true);
}
}

public function register(int $signal, callable $signalHandler): void
{
if (!isset($this->registeredSignals[$signal])) {
if (!isset($this->signalHandlers[$signal])) {
$previousCallback = pcntl_signal_get_handler($signal);

if (\is_callable($previousCallback)) {
$this->registeredSignals[$signal][] = $previousCallback;
$this->signalHandlers[$signal][] = $previousCallback;
}
}

$this->registeredSignals[$signal][] = $signalHandler;
$this->signalHandlers[$signal][] = $signalHandler;

pcntl_signal($signal, [$this, 'handle']);
}

Expand All @@ -41,20 +42,8 @@ public function register(int $signal, callable $signalHandler): void
*/
public function handle(int $signal): void
{
foreach ($this->registeredSignals[$signal] as $signalHandler) {
foreach ($this->signalHandlers[$signal] as $signalHandler) {
$signalHandler($signal);
}
}

public function addHandlingSignals(int ...$signals): void
{
foreach ($signals as $signal) {
$this->handlingSignals[$signal] = true;
}
}

public function getHandlingSignals(): array
{
return array_keys($this->handlingSignals);
}
}

0 comments on commit 93c7e1f

Please sign in to comment.