8000 [Console] add console.signal event · symfony/symfony@1574956 · GitHub
[go: up one dir, main page]

Skip to content

Commit 1574956

Browse files
mariechalasr
authored andcommitted
[Console] add console.signal event
1 parent 6a6f478 commit 1574956

File tree

6 files changed

+253
-0
lines changed

6 files changed

+253
-0
lines changed

src/Symfony/Bundle/FrameworkBundle/Resources/config/services.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Symfony\Component\Config\ResourceCheckerConfigCacheFactory;
1717
use Symfony\Component\Console\Event\ConsoleCommandEvent;
1818
use Symfony\Component\Console\Event\ConsoleErrorEvent;
19+
use Symfony\Component\Console\Event\ConsoleSignalEvent;
1920
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
2021
use Symfony\Component\DependencyInjection\Config\ContainerParametersResourceChecker;
2122
use Symfony\Component\DependencyInjection\EnvVarProcessor;
@@ -67,6 +68,7 @@
6768
$container->parameters()->set('event_dispatcher.event_aliases', [
6869
ConsoleCommandEvent::class => 'console.command',
6970
ConsoleErrorEvent::class => 'console.error',
71+
ConsoleSignalEvent::class => 'console.signal',
7072
ConsoleTerminateEvent::class => 'console.terminate',
7173
PreSubmitEvent::class => 'form.pre_submit',
7274
SubmitEvent::class => 'form.submit',

src/Symfony/Component/Console/Application.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Symfony\Component\Console\CommandLoader\CommandLoaderInterface;
1818
use Symfony\Component\Console\Event\ConsoleCommandEvent;
1919
use Symfony\Component\Console\Event\ConsoleErrorEvent;
20+
use Symfony\Component\Console\Event\ConsoleSignalEvent;
2021
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
2122
use Symfony\Component\Console\Exception\CommandNotFoundException;
2223
use Symfony\Component\Console\Exception\ExceptionInterface;
@@ -39,6 +40,7 @@
3940
use Symfony\Component\Console\Output\ConsoleOutput;
4041
use Symfony\Component\Console\Output\ConsoleOutputInterface;
4142
use Symfony\Component\Console\Output\OutputInterface;
43+
use Symfony\Component\Console\SignalRegistry\SignalRegistry;
4244
use Symfony\Component\Console\Style\SymfonyStyle;
4345
use Symfony\Component\ErrorHandler\ErrorHandler;
4446
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
@@ -76,6 +78,7 @@ class Application implements ResetInterface
7678
private $defaultCommand;
7779
private $singleCommand = false;
7880
private $initialized;
81+
private $signalRegistry;
7982

8083
public function __construct(string $name = 'UNKNOWN', string $version = 'UNKNOWN')
8184
{
@@ -98,6 +101,11 @@ public function setCommandLoader(CommandLoaderInterface $commandLoader)
98101
$this->commandLoader = $commandLoader;
99102
}
100103

104+
public function setSignalRegistry(SignalRegistry $signalRegistry)
105+
{
106+
$this->signalRegistry = $signalRegistry;
107+
}
108+
101109
/**
102110
* Runs the current application.
103111
*
@@ -260,6 +268,17 @@ public function doRun(InputInterface $input, OutputInterface $output)
260268
$command = $this->find($alternative);
261269
}
262270

271+
if ($this->signalRegistry) {
272+
foreach ($this->signalRegistry->getHandlingSignals() as $handlingSignal) {
273+
$event = new ConsoleSignalEvent($command, $input, $output, $handlingSignal);
274+
$onSignalHandler = function () use ($event) {
275+
$this->dispatcher->dispatch($event, ConsoleEvents::SIGNAL);
276+
};
277+
278+
$this->signalRegistry->register($handlingSignal, $onSignalHandler);
279+
}
280+
}
281+
263282
$this->runningCommand = $command;
264283
$exitCode = $this->doRunCommand($command, $input, $output);
265284
$this->runningCommand = null;

src/Symfony/Component/Console/ConsoleEvents.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ final class ConsoleEvents
2727
*/
2828
const COMMAND = 'console.command';
2929

30+
/**
31+
* The SIGNAL event allows you to perform some actions
32+
* after the command execution was interrupted.
33+
*
34+
* @Event("Symfony\Component\Console\Event\ConsoleSignalEvent")
35+
*/
36+
const SIGNAL = 'console.signal';
37+
3038
/**
3139
* The TERMINATE event allows you to attach listeners after a command is
3240
* executed by the console.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Console\Event;
13+
14+
use Symfony\Component\Console\Command\Command;
15+
use Symfony\Component\Console\Input\InputInterface;
16+
use Symfony\Component\Console\Output\OutputInterface;
17+
18+
/**
19+
* @author marie <marie@users.noreply.github.com>
20+
*/
21+
final class ConsoleSignalEvent extends ConsoleEvent
22+
{
23+
private $handlingSignal;
24+
25+
public function __construct(Command $command, InputInterface $input, OutputInterface $output, int $handlingSignal)
26+
{
27+
parent::__construct($command, $input, $output);
28+
$this->handlingSignal = $handlingSignal;
29+
}
30+
31+
public function getHandlingSignal(): int
32+
{
33+
return $this->handlingSignal;
34+
}
35+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Console\SignalRegistry;
13+
14+
final class SignalRegistry
15+
{
16+
private $registeredSignals = [];
17+
18+
private $handlingSignals = [];
19+
20+
public function __construct()
21+
{
22+
pcntl_async_signals(true);
23+
}
24+
25+
public function register(int $signal, callable $signalHandler): void
26+
{
27+
if (!isset($this->registeredSignals[$signal])) {
28+
$previousCallback = pcntl_signal_get_handler($signal);
29+
30+
if (\is_callable($previousCallback)) {
31+
$this->registeredSignals[$signal][] = $previousCallback;
32+
}
33+
}
34+
35+
$this->registeredSignals[$signal][] = $signalHandler;
36+
pcntl_signal($signal, [$this, 'handle']);
37+
}
38+
39+
/**
40+
* @internal
41+
*/
42+
public function handle(int $signal): void
43+
{
44+
foreach ($this->registeredSignals[$signal] as $signalHandler) {
45+
$signalHandler($signal);
46+
}
47+
}
48+
49+
public function addHandlingSignals(int ...$signals): void
50+
{
51+
foreach ($signals as $signal) {
52+
$this->handlingSignals[$signal] = true;
53+
}
54+
}
55+
56+
public function getHandlingSignals(): array
57+
{
58+
return array_keys($this->handlingSignals);
59+
}
60+
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Console\Tests\SignalRegistry;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Console\SignalRegistry\SignalRegistry;
16+
17+
/**
18+
* @requires extension pcntl
19+
*/
20+
class SignalRegistryTest extends TestCase
21+
{
22+
public function tearDown(): void
23+
{
24+
pcntl_async_signals(false);
25+
pcntl_signal(SIGUSR1, SIG_DFL);
26+
pcntl_signal(SIGUSR2, SIG_DFL);
27+
}
28+
29+
public function testOneCallbackForASignal_signalIsHandled()
30+
{
31+
$signalRegistry = new SignalRegistry();
32+
33+
$isHandled = false;
34+
$signalRegistry->register(SIGUSR1, function () use (&$isHandled) {
35+
$isHandled = true;
36+
});
37+
38+
posix_kill(posix_getpid(), SIGUSR1);
39+
40+
$this->assertTrue($isHandled);
41+
}
42+
43+
public function testTwoCallbacksForASignal_bothCallbacksAreCalled()
44+
{
45+
$signalRegistry = new SignalRegistry();
46+
47+
$isHandled1 = false;
48+
$signalRegistry->register(SIGUSR1, function () use (&$isHandled1) {
49+
$isHandled1 = true;
50+
});
51+
52+
$isHandled2 = false;
53+
$signalRegistry->register(SIGUSR1, function () use (&$isHandled2) {
54+
$isHandled2 = true;
55+
});
56+
57+
posix_kill(posix_getpid(), SIGUSR1);
58+
59+
$this->assertTrue($isHandled1);
60+
$this->assertTrue($isHandled2);
61+
}
62+
63+
public function testTwoSignals_signalsAreHandled()
64+
{
65+
$signalRegistry = new SignalRegistry();
66+
67+
$isHandled1 = false;
68+
$isHandled2 = false;
69+
70+
$signalRegistry->register(SIGUSR1, function () use (&$isHandled1) {
71+
$isHandled1 = true;
72+
});
73+
74+
posix_kill(posix_getpid(), SIGUSR1);
75+
76+
$this->assertTrue($isHandled1);
77+
$this->assertFalse($isHandled2);
78+
79+
$signalRegistry->register(SIGUSR2, function () use (&$isHandled2) {
80+
$isHandled2 = true;
81+
});
82+
83+
posix_kill(posix_getpid(), SIGUSR2);
84+
85+
$this->assertTrue($isHandled2);
86+
}
87+
88+
public function testTwoCallbacksForASignal_previousAndRegisteredCallbacksWereCalled()
89+
{
90+
$signalRegistry = new SignalRegistry();
91+
92+
$isHandled1 = false;
93+
pcntl_signal(SIGUSR1, function () use (&$isHandled1) {
94+
$isHandled1 = true;
95+
});
96+
97+
$isHandled2 = false;
98+
$signalRegistry->register(SIGUSR1, function () use (&$isHandled2) {
99+
$isHandled2 = true;
100+
});
101+
102+
posix_kill(posix_getpid(), SIGUSR1);
103+
104+
$this->assertTrue($isHandled1);
105+
$this->assertTrue($isHandled2);
106+
}
107+
108+
public function testTwoCallbacksForASignal_previousCallbackFromAnotherRegistry()
109+
{
110+
$signalRegistry1 = new SignalRegistry();
111+
112+
$isHandled1 = false;
113+
$signalRegistry1->register(SIGUSR1, function () use (&$isHandled1) {
114+
$isHandled1 = true;
115+
});
116+
117+
$signalRegistry2 = new SignalRegistry();
118+
119+
$isHandled2 = false;
120+
$signalRegistry2->register(SIGUSR1, function () use (&$isHandled2) {
121+
$isHandled2 = true;
122+
});
123+
124+
posix_kill(posix_getpid(), SIGUSR1);
125+
126+
$this->assertTrue($isHandled1);
127+
$this->assertTrue($isHandled2);
128+
}
129+
}

0 commit comments

Comments
 (0)
0