Skip to content

Commit 7490104

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 ------- 2b46650b9c Extract dispatching console signal handling and include subscribers
2 parents 535846c + 5b066d6 commit 7490104

File tree

2 files changed

+158
-38
lines changed

2 files changed

+158
-38
lines changed

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) {

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)
20012085
{
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy