Skip to content

Commit 23a1a63

Browse files
HypeMCfabpot
authored andcommitted
[Console] Add ability to schedule alarm signals and a console.alarm event
1 parent b4f6757 commit 23a1a63

File tree

7 files changed

+382
-14
lines changed

7 files changed

+382
-14
lines changed

Application.php

+47-12
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use Symfony\Component\Console\Completion\CompletionInput;
2323
use Symfony\Component\Console\Completion\CompletionSuggestions;
2424
use Symfony\Component\Console\Completion\Suggestion;
25+
use Symfony\Component\Console\Event\ConsoleAlarmEvent;
2526
use Symfony\Component\Console\Event\ConsoleCommandEvent;
2627
use Symfony\Component\Console\Event\ConsoleErrorEvent;
2728
use Symfony\Component\Console\Event\ConsoleSignalEvent;
@@ -88,6 +89,7 @@ class Application implements ResetInterface
8889
private bool $initialized = false;
8990
private ?SignalRegistry $signalRegistry = null;
9091
private array $signalsToDispatchEvent = [];
92+
private ?int $alarmInterval = null;
9193

9294
public function __construct(
9395
private string $name = 'UNKNOWN',
@@ -97,7 +99,7 @@ public function __construct(
9799
$this->defaultCommand = 'list';
98100
if (\defined('SIGINT') && SignalRegistry::isSupported()) {
99101
$this->signalRegistry = new SignalRegistry();
100-
$this->signalsToDispatchEvent = [\SIGINT, \SIGQUIT, \SIGTERM, \SIGUSR1, \SIGUSR2];
102+
$this->signalsToDispatchEvent = [\SIGINT, \SIGQUIT, \SIGTERM, \SIGUSR1, \SIGUSR2, \SIGALRM];
101103
}
102104
}
103105

@@ -128,6 +130,22 @@ public function setSignalsToDispatchEvent(int ...$signalsToDispatchEvent): void
128130
$this->signalsToDispatchEvent = $signalsToDispatchEvent;
129131
}
130132

133+
/**
134+
* Sets the interval to schedule a SIGALRM signal in seconds.
135+
*/
136+
public function setAlarmInterval(?int $seconds): void
137+
{
138+
$this->alarmInterval = $seconds;
139+
$this->scheduleAlarm();
140+
}
141+
142+
private function scheduleAlarm(): void
143+
{
144+
if (null !== $this->alarmInterval) {
145+
$this->getSignalRegistry()->scheduleAlarm($this->alarmInterval);
146+
}
147+
}
148+
131149
/**
132150
* Runs the current application.
133151
*
@@ -981,34 +999,47 @@ protected function doRunCommand(Command $command, InputInterface $input, OutputI
981999

9821000
$commandSignals = $command instanceof SignalableCommandInterface ? $command->getSubscribedSignals() : [];
9831001
if ($commandSignals || $this->dispatcher && $this->signalsToDispatchEvent) {
984-
if (!$this->signalRegistry) {
985-
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.');
986-
}
1002+
$signalRegistry = $this->getSignalRegistry();
9871003

9881004
if (Terminal::hasSttyAvailable()) {
9891005
$sttyMode = shell_exec('stty -g');
9901006

9911007
foreach ([\SIGINT, \SIGQUIT, \SIGTERM] as $signal) {
992-
$this->signalRegistry->register($signal, static fn () => shell_exec('stty '.$sttyMode));
1008+
$signalRegistry->register($signal, static fn () => shell_exec('stty '.$sttyMode));
9931009
}
9941010
}
9951011

9961012
if ($this->dispatcher) {
9971013
// We register application signals, so that we can dispatch the event
9981014
foreach ($this->signalsToDispatchEvent as $signal) {
999-
$event = new ConsoleSignalEvent($command, $input, $output, $signal);
1000-
1001-
$this->signalRegistry->register($signal, function ($signal) use ($event, $command, $commandSignals) {
1002-
$this->dispatcher->dispatch($event, ConsoleEvents::SIGNAL);
1003-
$exitCode = $event->getExitCode();
1015+
$signalEvent = new ConsoleSignalEvent($command, $input, $output, $signal);
1016+
$alarmEvent = \SIGALRM === $signal ? new ConsoleAlarmEvent($command, $input, $output) : null;
1017+
1018+
$signalRegistry->register($signal, function ($signal) use ($signalEvent, $alarmEvent, $command, $commandSignals, $input, $output) {
1019+
$this->dispatcher->dispatch($signalEvent, ConsoleEvents::SIGNAL);
1020+
$exitCode = $signalEvent->getExitCode();
1021+
1022+
if (null !== $alarmEvent) {
1023+
if (false !== $exitCode) {
1024+
$alarmEvent->setExitCode($exitCode);
1025+
} else {
1026+
$alarmEvent->abortExit();
1027+
}
1028+
$this->dispatcher->dispatch($alarmEvent);
1029+
$exitCode = $alarmEvent->getExitCode();
1030+
}
10041031

10051032
// If the command is signalable, we call the handleSignal() method
10061033
if (\in_array($signal, $commandSignals, true)) {
10071034
$exitCode = $command->handleSignal($signal, $exitCode);
10081035
}
10091036

1037+
if (\SIGALRM === $signal) {
1038+
$this->scheduleAlarm();
1039+
}
1040+
10101041
if (false !== $exitCode) {
1011-
$event = new ConsoleTerminateEvent($command, $event->getInput(), $event->getOutput(), $exitCode, $signal);
1042+
$event = new ConsoleTerminateEvent($command, $input, $output, $exitCode, $signal);
10121043
$this->dispatcher->dispatch($event, ConsoleEvents::TERMINATE);
10131044

10141045
exit($event->getExitCode());
@@ -1021,7 +1052,11 @@ protected function doRunCommand(Command $command, InputInterface $input, OutputI
10211052
}
10221053

10231054
foreach ($commandSignals as $signal) {
1024-
$this->signalRegistry->register($signal, function (int $signal) use ($command): void {
1055+
$signalRegistry->register($signal, function (int $signal) use ($command): void {
1056+
if (\SIGALRM === $signal) {
1057+
$this->scheduleAlarm();
1058+
}
1059+
10251060
if (false !== $exitCode = $command->handleSignal($signal)) {
10261061
exit($exitCode);
10271062
}

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ CHANGELOG
99
* [BC BREAK] Add silent verbosity (`--silent`/`SHELL_VERBOSITY=-2`) to suppress all output, including errors
1010
* Add `OutputInterface::isSilent()`, `Output::isSilent()`, `OutputStyle::isSilent()` methods
1111
* Add a configurable finished indicator to the progress indicator to show that the progress is finished
12+
* Add ability to schedule alarm signals and a `ConsoleAlarmEvent`
1213

1314
7.1
1415
---

Event/ConsoleAlarmEvent.php

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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+
final class ConsoleAlarmEvent extends ConsoleEvent
19+
{
20+
public function __construct(
21+
Command $command,
22+
InputInterface $input,
23+
OutputInterface $output,
24+
private int|false $exitCode = 0,
25+
) {
26+
parent::__construct($command, $input, $output);
27+
}
28+
29+
public function setExitCode(int $exitCode): void
30+
{
31+
if ($exitCode < 0 || $exitCode > 255) {
32+
throw new \InvalidArgumentException('Exit code must be between 0 and 255.');
33+
}
34+
35+
$this->exitCode = $exitCode;
36+
}
37+
38+
public function abortExit(): void
39+
{
40+
$this->exitCode = false;
41+
}
42+
43+
public function getExitCode(): int|false
44+
{
45+
return $this->exitCode;
46+
}
47+
}

SignalRegistry/SignalRegistry.php

+8
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,12 @@ public function handle(int $signal): void
5454
$signalHandler($signal, $hasNext);
5555
}
5656
}
57+
58+
/**
59+
* @internal
60+
*/
61+
public function scheduleAlarm(int $seconds): void
62+
{
63+
pcntl_alarm($seconds);
64+
}
5765
}

0 commit comments

Comments
 (0)