Skip to content

Commit 4c1d8eb

Browse files
committed
feature symfony#53968 [Process] allow to ignore signals when executing a process (joelwurtz)
This PR was merged into the 7.1 branch. Discussion ---------- [Process] allow to ignore signals when executing a process | Q | A | ------------- | --- | Branch? | 7.1 | Bug fix? | no | New feature? | yes | Deprecations? | no | Issues | | License | MIT This PR allow to a process to ignore signals dispatched by PHP This can be useful when using symfony messenger where the handler would execute a Process and receives a SIGTERM, actually the handler would fail with a `ProcessSignaledException` instead of waiting for the handler to terminate and gracefully shutdown. Please note that ignoring a signal will also disallow to send this signal with the `posix_kill` function Commits ------- 7bb6ecf feat(process): allow to ignore signals when executing a process
2 parents fdedd3b + 7bb6ecf commit 4c1d8eb

File tree

3 files changed

+69
-0
lines changed

3 files changed

+69
-0
lines changed

src/Symfony/Component/Process/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
7.1
5+
---
6+
7+
* Add `Process::setIgnoredSignals()` to disable signal propagation to the child process
8+
49
6.4
510
---
611

src/Symfony/Component/Process/Process.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ class Process implements \IteratorAggregate
7777
private bool $tty = false;
7878
private bool $pty;
7979
private array $options = ['suppress_errors' => true, 'bypass_shell' => true];
80+
private array $ignoredSignals = [];
8081

8182
private WindowsPipes|UnixPipes $processPipes;
8283

@@ -346,9 +347,23 @@ public function start(?callable $callback = null, array $env = []): void
346347

347348
return true;
348349
});
350+
351+
$oldMask = [];
352+
353+
if (\function_exists('pcntl_sigprocmask')) {
354+
// we block signals we want to ignore, as proc_open will use fork / posix_spawn which will copy the signal mask this allow to block
355+
// signals in the child process
356+
pcntl_sigprocmask(\SIG_BLOCK, $this->ignoredSignals, $oldMask);
357+
}
358+
349359
try {
350360
$process = @proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $envPairs, $this->options);
351361
} finally {
362+
if (\function_exists('pcntl_sigprocmask')) {
363+
// we restore the signal mask here to avoid any side effects
364+
pcntl_sigprocmask(\SIG_SETMASK, $oldMask);
365+
}
366+
352367
restore_error_handler();
353368
}
354369

@@ -1206,6 +1221,20 @@ public function setOptions(array $options): void
12061221
}
12071222
}
12081223

1224+
/**
1225+
* Defines a list of posix signals that will not be propagated to the process.
1226+
*
1227+
* @param list<\SIG*> $signals
1228+
*/
1229+
public function setIgnoredSignals(array $signals): void
1230+
{
1231+
if ($this->isRunning()) {
1232+
throw new RuntimeException('Setting ignored signals while the process is running is not possible.');
1233+
}
1234+
1235+
$this->ignoredSignals = $signals;
1236+
}
1237+
12091238
/**
12101239
* Returns whether TTY is supported on the current operating system.
12111240
*/
@@ -1455,6 +1484,11 @@ private function resetProcessData(): void
14551484
*/
14561485
private function doSignal(int $signal, bool $throwException): bool
14571486
{
1487+
// Signal seems to be send when sigchild is enable, this allow blocking the signal correctly in this case
1488+
if ($this->isSigchildEnabled() && \in_array($signal, $this->ignoredSignals)) {
1489+
return false;
1490+
}
1491+
14581492
if (null === $pid = $this->getPid()) {
14591493
if ($throwException) {
14601494
throw new LogicException('Cannot send signal on a non running process.');

src/Symfony/Component/Process/Tests/ProcessTest.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1663,6 +1663,36 @@ public function testNotTerminableInputPipe()
16631663
$this->assertFalse($process->isRunning());
16641664
}
16651665

1666+
public function testIgnoringSignal()
1667+
{
1668+
if (!\function_exists('pcntl_signal')) {
1669+
$this->markTestSkipped('pnctl extension is required.');
1670+
}
1671+
1672+
$process = $this->getProcess('sleep 10');
1673+
$process->setIgnoredSignals([\SIGTERM]);
1674+
1675+
$process->start();
1676+
$process->stop(timeout: 0.2);
1677+
1678+
$this->assertNotSame(\SIGTERM, $process->getTermSignal());
1679+
}
1680+
1681+
// This test ensure that the previous test is reliable, in case of the sleep command ignoring the SIGTERM signal
1682+
public function testNotIgnoringSignal()
1683+
{
1684+
if (!\function_exists('pcntl_signal')) {
1685+
$this->markTestSkipped('pnctl extension is required.');
1686+
}
1687+
1688+
$process = $this->getProcess('sleep 10');
1689+
1690+
$process->start();
1691+
$process->stop(timeout: 0.2);
1692+
1693+
$this->assertSame(\SIGTERM, $process->getTermSignal());
1694+
}
1695+
16661696
private function getProcess(string|array $commandline, ?string $cwd = null, ?array $env = null, mixed $input = null, ?int $timeout = 60): Process
16671697
{
16681698
if (\is_string($commandline)) {

0 commit comments

Comments
 (0)