Skip to content

Commit 5c32d9c

Browse files
committed
[Console] Add ability to schedule alarm signals and a console.alarm event
1 parent 6584ff5 commit 5c32d9c

File tree

8 files changed

+389
-14
lines changed

8 files changed

+389
-14
lines changed

src/Symfony/Component/Console/Application.php

Lines changed: 44 additions & 12 deletions
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,19 @@ public function setSignalsToDispatchEvent(int ...$signalsToDispatchEvent): void
128130
$this->signalsToDispatchEvent = $signalsToDispatchEvent;
129131
}
130132

133+
public function setAlarmInterval(?int $interval): void
134+
{
135+
$this->alarmInterval = $interval;
136+
$this->scheduleAlarm();
137+
}
138+
139+
private function scheduleAlarm(): void
140+
{
141+
if (null !== $this->alarmInterval) {
142+
$this->getSignalRegistry()->scheduleAlarm($this->alarmInterval);
143+
}
144+
}
145+
131146
/**
132147
* Runs the current application.
133148
*
@@ -975,34 +990,47 @@ protected function doRunCommand(Command $command, InputInterface $input, OutputI
975990

976991
$commandSignals = $command instanceof SignalableCommandInterface ? $command->getSubscribedSignals() : [];
977992
if ($commandSignals || $this->dispatcher && $this->signalsToDispatchEvent) {
978-
if (!$this->signalRegistry) {
979-
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.');
980-
}
993+
$signalRegistry = $this->getSignalRegistry();
981994

982995
if (Terminal::hasSttyAvailable()) {
983996
$sttyMode = shell_exec('stty -g');
984997

985998
foreach ([\SIGINT, \SIGQUIT, \SIGTERM] as $signal) {
986-
$this->signalRegistry->register($signal, static fn () => shell_exec('stty '.$sttyMode));
999+
$signalRegistry->register($signal, static fn () => shell_exec('stty '.$sttyMode));
9871000
}
9881001
}
9891002

9901003
if ($this->dispatcher) {
9911004
// We register application signals, so that we can dispatch the event
9921005
foreach ($this->signalsToDispatchEvent as $signal) {
993-
$event = new ConsoleSignalEvent($command, $input, $output, $signal);
994-
995-
$this->signalRegistry->register($signal, function ($signal) use ($event, $command, $commandSignals) {
996-
$this->dispatcher->dispatch($event, ConsoleEvents::SIGNAL);
997-
$exitCode = $event->getExitCode();
1006+
$signalEvent = new ConsoleSignalEvent($command, $input, $output, $signal);
1007+
$alarmEvent = \SIGALRM === $signal ? new ConsoleAlarmEvent($command, $input, $output) : null;
1008+
1009+
$signalRegistry->register($signal, function ($signal) use ($signalEvent, $alarmEvent, $command, $commandSignals, $input, $output) {
1010+
$this->dispatcher->dispatch($signalEvent, ConsoleEvents::SIGNAL);
1011+
$exitCode = $signalEvent->getExitCode();
1012+
1013+
if (null !== $alarmEvent) {
1014+
if (false !== $exitCode) {
1015+
$alarmEvent->setExitCode($exitCode);
1016+
} else {
1017+
$alarmEvent->abortExit();
1018+
}
1019+
$this->dispatcher->dispatch($alarmEvent, ConsoleEvents::ALARM);
1020+
$exitCode = $alarmEvent->getExitCode();
1021+
}
9981022

9991023
// If the command is signalable, we call the handleSignal() method
10001024
if (\in_array($signal, $commandSignals, true)) {
10011025
$exitCode = $command->handleSignal($signal, $exitCode);
10021026
}
10031027

1028+
if (\SIGALRM === $signal) {
1029+
$this->scheduleAlarm();
1030+
}
1031+
10041032
if (false !== $exitCode) {
1005-
$event = new ConsoleTerminateEvent($command, $event->getInput(), $event->getOutput(), $exitCode, $signal);
1033+
$event = new ConsoleTerminateEvent($command, $input, $output, $exitCode, $signal);
10061034
$this->dispatcher->dispatch($event, ConsoleEvents::TERMINATE);
10071035

10081036
exit($event->getExitCode());
@@ -1015,7 +1043,11 @@ protected function doRunCommand(Command $command, InputInterface $input, OutputI
10151043
}
10161044

10171045
foreach ($commandSignals as $signal) {
1018-
$this->signalRegistry->register($signal, function (int $signal) use ($command): void {
1046+
$signalRegistry->register($signal, function (int $signal) use ($command): void {
1047+
if (\SIGALRM === $signal) {
1048+
$this->scheduleAlarm();
1049+
}
1050+
10191051
if (false !== $exitCode = $command->handleSignal($signal)) {
10201052
exit($exitCode);
10211053
}

src/Symfony/Component/Console/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ CHANGELOG
66

77
* Add support for `FORCE_COLOR` environment variable
88
* Add `verbosity` argument to `mustRun` process helper method
9+
* Add ability to schedule alarm signals and a `console.alarm` event
910

1011
7.1
1112
---

src/Symfony/Component/Console/ConsoleEvents.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Component\Console;
1313

14+
use Symfony\Component\Console\Event\ConsoleAlarmEvent;
1415
use Symfony\Component\Console\Event\ConsoleCommandEvent;
1516
use Symfony\Component\Console\Event\ConsoleErrorEvent;
1617
use Symfony\Component\Console\Event\ConsoleSignalEvent;
@@ -40,6 +41,14 @@ final class ConsoleEvents
4041
*/
4142
public const SIGNAL = 'console.signal';
4243

44+
/**
45+
* The ALARM event allows you to perform some actions
46+
* after the command received a SIGALRM signal.
47+
*
48+
* @Event("Symfony\Component\Console\Event\ConsoleAlarmEvent")
49+
*/
50+
public const ALARM = 'console.alarm';
51+
4352
/**
4453
* The TERMINATE event allows you to attach listeners after a command is
4554
* executed by the console.
@@ -67,6 +76,7 @@ final class ConsoleEvents
6776
ConsoleCommandEvent::class => self::COMMAND,
6877
ConsoleErrorEvent::class => self::ERROR,
6978
ConsoleSignalEvent::class => self::SIGNAL,
79+
ConsoleAlarmEvent::class => self::ALARM,
7080
ConsoleTerminateEvent::class => self::TERMINATE,
7181
];
7282
}
Lines changed: 47 additions & 0 deletions
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 <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+
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+
}

src/Symfony/Component/Console/SignalRegistry/SignalRegistry.php

Lines changed: 8 additions & 0 deletions
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)
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