Skip to content

Commit 6ea4da7

Browse files
ext/pcntl: keep the signals a throwing handler left in the queue
When a handler threw, the signals queued behind it were recycled without ever being delivered. Put them back on the queue instead, and re-arm the interrupt so that the engine dispatches them once the exception has been handled, rather than leaving them to wait for another signal to come in. Not calling further handlers while the exception propagates is unchanged.
1 parent 0b4037e commit 6ea4da7

4 files changed

Lines changed: 119 additions & 11 deletions

File tree

‎NEWS‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ PHP NEWS
6868
- PCNTL:
6969
. Fixed pcntl_signal_dispatch() dropping the queued signals when it runs while
7070
an exception is pending. (nicolas-grekas)
71+
. Fixed pcntl_signal_dispatch() dropping the signals queued behind a handler
72+
that throws. (nicolas-grekas)
7173

7274
- PDO:
7375
. Fixed PDOStatement::getColumnMeta() reading out of bounds for an invalid

‎ext/pcntl/pcntl.c‎

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1365,6 +1365,8 @@ void pcntl_signal_dispatch(void)
13651365

13661366
/* Allocate */
13671367
while (queue) {
1368+
bool handler_threw = false;
1369+
13681370
if ((handle = zend_hash_index_find(&PCNTL_G(php_signal_table), queue->signo)) != NULL) {
13691371
if (Z_TYPE_P(handle) != IS_LONG) {
13701372
ZVAL_NULL(&retval);
@@ -1383,24 +1385,19 @@ void pcntl_signal_dispatch(void)
13831385
#ifdef HAVE_STRUCT_SIGINFO_T
13841386
zval_ptr_dtor(&params[1]);
13851387
#endif
1386-
if (EG(exception)) {
1387-
break;
1388-
}
1388+
handler_threw = NULL != EG(exception);
13891389
}
13901390
}
13911391

13921392
next = queue->next;
13931393
queue->next = PCNTL_G(spares);
13941394
PCNTL_G(spares) = queue;
13951395
queue = next;
1396-
}
13971396

1398-
/* drain the remaining in case of exception thrown */
1399-
while (queue) {
1400-
next = queue->next;
1401-
queue->next = PCNTL_G(spares);
1402-
PCNTL_G(spares) = queue;
1403-
queue = next;
1397+
/* No other handler can be called while the exception propagates */
1398+
if (handler_threw) {
1399+
break;
1400+
}
14041401
}
14051402

14061403
if (old_exception) {
@@ -1415,7 +1412,25 @@ void pcntl_signal_dispatch(void)
14151412
}
14161413
}
14171414

1418-
PCNTL_G(pending_signals) = 0;
1415+
if (UNEXPECTED(queue)) {
1416+
/* Put back what the throwing handler did not get to, instead of dropping it, and ask
1417+
* the engine to come back once the exception has been handled. Signals are still
1418+
* blocked here, so PCNTL_G(head) cannot have been repopulated in the meantime. */
1419+
next = queue;
1420+
1421+
while (next->next) {
1422+
next = next->next;
1423+
}
1424+
1425+
PCNTL_G(head) = queue;
1426+
PCNTL_G(tail) = next;
1427+
1428+
if (PCNTL_G(async_signals)) {
1429+
zend_atomic_bool_store_ex(&EG(vm_interrupt), true);
1430+
}
1431+
} else {
1432+
PCNTL_G(pending_signals) = 0;
1433+
}
14191434

14201435
/* Re-enable queue */
14211436
PCNTL_G(processing_signal_queue) = 0;
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
--TEST--
2+
pcntl_signal_dispatch() keeps the signals left in the queue by a throwing handler
3+
--EXTENSIONS--
4+
pcntl
5+
posix
6+
--FILE--
7+
<?php
8+
9+
$called = [];
10+
11+
pcntl_signal(SIGUSR1, function ($signo) use (&$called) {
12+
$called[] = 'SIGUSR1';
13+
throw new \Exception('Exception in signal handler');
14+
});
15+
16+
pcntl_signal(SIGUSR2, function ($signo) use (&$called) {
17+
$called[] = 'SIGUSR2';
18+
});
19+
20+
pcntl_signal(SIGHUP, function ($signo) use (&$called) {
21+
$called[] = 'SIGHUP';
22+
});
23+
24+
posix_kill(posix_getpid(), SIGUSR1);
25+
posix_kill(posix_getpid(), SIGUSR2);
26+
posix_kill(posix_getpid(), SIGHUP);
27+
28+
try {
29+
pcntl_signal_dispatch();
30+
} catch (\Exception $e) {
31+
echo $e->getMessage() . "\n";
32+
}
33+
34+
echo "Handlers called: " . implode(', ', $called) . "\n";
35+
36+
pcntl_signal_dispatch();
37+
38+
echo "Handlers called: " . implode(', ', $called) . "\n";
39+
40+
?>
41+
--EXPECT--
42+
Exception in signal handler
43+
Handlers called: SIGUSR1
44+
Handlers called: SIGUSR1, SIGUSR2, SIGHUP
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
--TEST--
2+
pcntl_signal_dispatch() delivers the signals a throwing handler left behind once its exception is handled
3+
--EXTENSIONS--
4+
pcntl
5+
posix
6+
--FILE--
7+
<?php
8+
9+
$called = [];
10+
11+
pcntl_signal(SIGUSR1, function ($signo) use (&$called) {
12+
$called[] = 'SIGUSR1';
13+
throw new \Exception('Exception in signal handler');
14+
});
15+
16+
pcntl_signal(SIGUSR2, function ($signo) use (&$called) {
17+
$called[] = 'SIGUSR2';
18+
});
19+
20+
pcntl_signal(SIGHUP, function ($signo) use (&$called) {
21+
$called[] = 'SIGHUP';
22+
});
23+
24+
// Queued, not dispatched: asynchronous signals are off
25+
posix_kill(posix_getpid(), SIGUSR1);
26+
posix_kill(posix_getpid(), SIGUSR2);
27+
28+
pcntl_async_signals(true);
29+
30+
try {
31+
// Delivered asynchronously, so the whole queue is dispatched
32+
posix_kill(posix_getpid(), SIGHUP);
33+
echo "Not reached\n";
34+
} catch (\Exception $e) {
35+
echo $e->getMessage() . "\n";
36+
}
37+
38+
// No explicit dispatch: the engine delivers what the throwing handler left behind
39+
// on its own, as soon as the exception has been handled
40+
usleep(1000);
41+
42+
echo "Handlers called: " . implode(', ', $called) . "\n";
43+
44+
?>
45+
--EXPECT--
46+
Exception in signal handler
47+
Handlers called: SIGUSR1, SIGUSR2, SIGHUP

0 commit comments

Comments
 (0)