8000 bug #45333 [Console] Fix ConsoleEvents::SIGNAL subscriber dispatch (G… · symfony/symfony@db7c211 · GitHub
[go: up one dir, main page]

Skip to content

Commit db7c211

Browse files
bug #45333 [Console] Fix ConsoleEvents::SIGNAL subscriber dispatch (GwendolenLynch)
This PR was merged into the 5.4 branch. Discussion ---------- [Console] Fix ConsoleEvents::SIGNAL subscriber dispatch | Q | A | ------------- | --- | Branch? | 5.4 | Bug fix? | yes | New feature? | no | Deprecations? | no | Tickets | Fix #45332 | License | MIT Commits ------- 2b46650 Extract dispatching console signal handling and include subscribers
2 parents 252666e + 2b46650 commit db7c211

File tree

2 files changed

+158
-38
lines changed

2 files changed

+158
-38
lines changed

src/Symfony/Component/Console/Application.php

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -983,22 +983,31 @@ protected function doRunCommand(Command $command, InputInterface $input, OutputI
983983
}
984984
}
985985

986-
if ($command instanceof SignalableCommandInterface && ($this->signalsToDispatchEvent || $command->getSubscribedSignals())) {
987-
if (!$this->signalRegistry) {
988-
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.');
989-
}
986+
if ($this->signalsToDispatchEvent) {
987+
$commandSignals = $command instanceof SignalableCommandInterface ? $command->getSubscribedSignals() : [];
988+
$dispatchSignals = $this->dispatcher && $this->dispatcher->hasListeners(ConsoleEvents::SIGNAL);
990989

991-
if (Terminal::hasSttyAvailable()) {
992-
$sttyMode = shell_exec('stty -g');
990+
if ($commandSignals || $dispatchSignals) {
991+
if (!$this->signalRegistry) {
992+
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.');
993+
}
993994

994-
foreach ([\SIGINT, \SIGTERM] as $signal) {
995-
$this->signalRegistry->register($signal, static function () use ($sttyMode) {
996-
shell_exec('stty '.$sttyMode);
997-
});
995+
if (Terminal::hasSttyAvailable()) {
996+
$sttyMode = shell_exec('stty -g');
997+
998+
foreach ([\SIGINT, \SIGTERM] as $signal) {
999+
$this->signalRegistry->register($signal, static function () use ($sttyMode) {
1000+
shell_exec('stty '.$sttyMode);
1001+
});
1002+
}
1003+
}
1004+
1005+
foreach ($commandSignals as $signal) {
1006+
$this->signalRegistry->register($signal, [$command, 'handleSignal']);
9981007
}
9991008
}
10001009

1001-
if ($this->dispatcher) {
1010+
if ($dispatchSignals) {
10021011
foreach ($this->signalsToDispatchEvent as $signal) {
10031012
$event = new ConsoleSignalEvent($command, $input, $output, $signal);
10041013

@@ -1014,10 +1023,6 @@ protected function doRunCommand(Command $command, InputInterface $input, OutputI
10141023
});
10151024
}
10161025
}
1017-
1018-
foreach ($command->getSubscribedSignals() as $signal) {
1019-
$this->signalRegistry->register($signal, [$command, 'handleSignal']);
1020-
}
10211026
}
10221027

10231028
if (null === $this->dispatcher) {

src/Symfony/Component/Console/Tests/ApplicationTest.php

Lines changed: 138 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass;
2222
use Symfony\Component\Console\Event\ConsoleCommandEvent;
2323
use Symfony\Component\Console\Event\ConsoleErrorEvent;
24+
use Symfony\Component\Console\Event\ConsoleSignalEvent;
2425
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
2526
use Symfony\Component\Console\Exception\CommandNotFoundException;
2627
use Symfony\Component\Console\Exception\NamespaceNotFoundException;
@@ -42,6 +43,8 @@
4243
use Symfony\Component\Console\Tester\ApplicationTester;
4344
use Symfony\Component\DependencyInjection\ContainerBuilder;
4445
use Symfony\Component\EventDispatcher\EventDispatcher;
46+
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
47+
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
4548
use Symfony\Component\Process\Process;
4649

4750
class ApplicationTest extends TestCase
@@ -1862,39 +1865,107 @@ public function testCommandNameMismatchWithCommandLoaderKeyThrows()
18621865
/**
18631866
* @requires extension pcntl
18641867
*/
1865-
public function testSignal()
1868+
public function testSignalListenerNotCalledByDefault()
18661869
{
1867-
$command = new SignableCommand();
1870+
$command = new SignableCommand(false);
18681871

18691872
$dispatcherCalled = false;
18701873
$dispatcher = new EventDispatcher();
18711874
$dispatcher->addListener('console.signal', function () use (&$dispatcherCalled) {
18721875
$dispatcherCalled = true;
18731876
});
18741877

1875-
$application = new Application();
1876-
$application->setAutoExit(false);
1877-
$application->setDispatcher($dispatcher);
1878-
$application->setSignalsToDispatchEvent(\SIGALRM);
1879-
$application->add(new LazyCommand('signal', [], '', false, function () use ($command) { return $command; }, true));
1880-
1881-
$this->assertFalse($command->signaled);
1882-
$this->assertFalse($dispatcherCalled);
1878+
$application = $this->createSignalableApplication($command, $dispatcher);
18831879

18841880
$this->assertSame(0, $application->run(new ArrayInput(['signal'])));
18851881
$this->assertFalse($command->signaled);
18861882
$this->assertFalse($dispatcherCalled);
1883+
}
1884+
1885+
/**
1886+
* @requires extension pcntl
1887+
*/
1888+
public function testSignalListener()
1889+
{
1890+
$command = new SignableCommand();
1891+
1892+
$dispatcherCalled = false;
1893+
$dispatcher = new EventDispatcher();
1894+
$dispatcher->addListener('console.signal', function () use (&$dispatcherCalled) {
1895+
$dispatcherCalled = true;
1896+
});
1897+
1898+
$application = $this->createSignalableApplication($command, $dispatcher);
18871899

1888-
$command->loop = 100000;
1889-
pcntl_alarm(1);
18901900
$this->assertSame(1, $application->run(new ArrayInput(['signal'])));
1891-
$this->assertTrue($command->signaled);
18921901
$this->assertTrue($dispatcherCalled);
1902+
$this->assertTrue($command->signaled);
1903+
}
1904+
1905+
/**
1906+
* @requires extension pcntl
1907+
*/
1908+
public function testSignalSubscriberNotCalledByDefault()
1909+
{
1910+
$command = new BaseSignableCommand(false);
1911+
1912+
$subscriber = new SignalEventSubscriber();
1913+
$dispatcher = new EventDispatcher();
1914+
$dispatcher->addSubscriber($subscriber);
1915+
1916+
$application = $this->createSignalableApplication($command, $dispatcher);
1917+
1918+
$this->assertSame(0, $application->run(new ArrayInput(['signal'])));
1919+
$this->assertFalse($subscriber->signaled);
1920+
}
1921+
1922+
/**
1923+
* @requires extension pcntl
1924+
*/
1925+
public function testSignalSubscriber()
1926+
{
1927+
$command = new BaseSignableCommand();
1928+
1929+
$subscriber1 = new SignalEventSubscriber();
1930+
$subscriber2 = new SignalEventSubscriber();
1931+
1932+
$dispatcher = new EventDispatcher();
1933+
$dispatcher->addSubscriber($subscriber1);
1934+
$dispatcher->addSubscriber($subscriber2);
1935+
1936+
$application = $this->createSignalableApplication($command, $dispatcher);
1937+
1938+
$this->assertSame(1, $application->run(new ArrayInput(['signal'])));
1939+
$this->assertTrue($subscriber1->signaled);
1940+
$this->assertTrue($subscriber2->signaled);
1941+
}
1942+
1943+
/**
1944+
* @requires extension pcntl
1945+
*/
1946+
public function testSetSignalsToDispatchEvent()
1947+
{
1948+
$command = new BaseSignableCommand();
1949+
1950+
$subscriber = new SignalEventSubscriber();
1951+
1952+
$dispatcher = new EventDispatcher();
1953+
$dispatcher->addSubscriber($subscriber);
1954+
1955+
$application = $this->createSignalableApplication($command, $dispatcher);
1956+
$application->setSignalsToDispatchEvent(\SIGUSR2);
1957+
$this->assertSame(0, $application->run(new ArrayInput(['signal'])));
1958+
$this->assertFalse($subscriber->signaled);
1959+
1960+
$application = $this->createSignalableApplication($command, $dispatcher);
1961+
$application->setSignalsToDispatchEvent(\SIGUSR1);
1962+
$this->assertSame(1, $application->run(new ArrayInput(['signal'])));
1963+
$this->assertTrue($subscriber->signaled);
18931964
}
18941965

18951966
public function testSignalableCommandInterfaceWithoutSignals()
18961967
{
1897-
$command = new SignableCommand();
1968+
$command = new SignableCommand(false);
18981969

18991970
$dispatcher = new EventDispatcher();
19001971
$application = new Application();
@@ -1936,6 +2007,18 @@ public function testSignalableRestoresStty()
19362007

19372008
$this->assertSame($previousSttyMode, $sttyMode);
19382009
}
2010+
2011+
private function createSignalableApplication(Command $command, ?EventDispatcherInterface $dispatcher): Application
2012+
{
2013+
$application = new Application();
2014+
$application->setAutoExit(false);
2015+
if ($dispatcher) {
2016+
$application->setDispatcher($dispatcher);
2017+
}
2018+
$application->add(new LazyCommand('signal', [], '', false, function () use ($command) { return $command; }, true));
2019+
2020+
return $application;
2021+
}
19392022
}
19402023

19412024
class CustomApplication extends Application
@@ -1990,25 +2073,26 @@ public function isEnabled(): bool
19902073
}
19912074
}
19922075

1993-
class SignableCommand extends Command implements SignalableCommandInterface
2076+
class BaseSignableCommand extends Command
19942077
{
19952078
public $signaled = false;
1996-
public $loop = 100;
2079+
public $loop = 1000;
2080+
private $emitsSignal;
19972081

19982082
protected static $defaultName = 'signal';
19992083

2000-
public function getSubscribedSignals(): array
2084+
public function __construct(bool $emitsSignal = true)
2001 1241 2085
{
2002-
return SignalRegistry::isSupported() ? [\SIGALRM] : [];
2003-
}
2004-
2005-
public function handleSignal(int $signal): void
2006-
{
2007-
$this->signaled = true;
2086+
parent::__construct();
2087+
$this->emitsSignal = $emitsSignal;
20082088
}
20092089

20102090
protected function execute(InputInterface $input, OutputInterface $output): int
20112091
{
2092+
if ($this->emitsSignal) {
2093+
posix_kill(posix_getpid(), SIGUSR1);
2094+
}
2095+
20122096
for ($i = 0; $i < $this->loop; ++$i) {
20132097
usleep(100);
20142098
if ($this->signaled) {
@@ -2019,3 +2103,34 @@ protected function execute(InputInterface $input, OutputInterface $output): int
20192103
return 0;
20202104
}
20212105
}
2106+
2107+
class SignableCommand extends BaseSignableCommand implements SignalableCommandInterface
2108+
{
2109+
protected static $defaultName = 'signal';
2110+
2111+
public function getSubscribedSignals(): array
2112+
{
2113+
return SignalRegistry::isSupported() ? [\SIGUSR1] : [];
2114+
}
2115+
2116+
public function handleSignal(int $signal): void
2117+
{
2118+
$this->signaled = true;
2119+
}
2120+
}
2121+
2122+
class SignalEventSubscriber implements EventSubscriberInterface
2123+
{
2124+
public $signaled = false;
2125+
2126+
public function onSignal(ConsoleSignalEvent $event): void
2127+
{
2128+
$this->signaled = true;
2129+
$event->getCommand()->signaled = true;
2130+
}
2131+
2132+
public static function getSubscribedEvents(): array
2133+
{
2134+
return ['console.signal' => 'onSignal'];
2135+
}
2136+
}

0 commit comments

Comments
 (0)
0