PHPのお勉強!

PHP TOP

pcntl_signal

(PHP 4 >= 4.1.0, PHP 5, PHP 7, PHP 8)

pcntl_signalシグナルハンドラを設定する

説明

pcntl_signal(int $signal, callable|int $handler, bool $restart_syscalls = true): bool

pcntl_signal() 関数は、signal が指すシグナルに関するハンドラを新たに設定するか、既存のハンドラを置き換えます。

パラメータ

signal

シグナル番号。

handler

シグナルハンドラ。callable を渡すと、それを実行してシグナルを処理します。 あるいは、グローバル定数 SIG_IGN または SIG_DFL を渡すこともできます。それぞれ、シグナルを無視することとデフォルトのシグナルハンドラを復活させることを表します。

callable を渡す場合は、次のシグネチャを実装したものでなければいけません。

handler(int $signo, mixed $siginfo): void
signal
処理するシグナル。
siginfo
OS が siginfo_t 構造体をサポートしている場合、 これはシグナルに依存するシグナル情報の配列になります。

注意:

オブジェクトのメソッドをハンドラとして指定した場合には、 そのハンドラを別のものに変えたりスクリプトが終了したりするまでは オブジェクトのリファレンスカウントが増加したままであることに注意しましょう。

restart_syscalls

再起動のシステムコールに対応するかどうかを設定します。

戻り値

成功した場合に true を、失敗した場合に false を返します。

変更履歴

バージョン 説明
7.1.0 PHP 7.1.0 以降、 ハンドラコールバックは特定のシグナルの siginfo を含む 2 番目の引数を受け付けるようになりました。 このデータは、OS が siginfo_t 構造体を持つ場合のみ提供されます。 OS が siginfo_t を実装していない場合は NULL が提供されます。

例1 pcntl_signal() の例

<?php
// tick を使用しなければなりません
declare(ticks = 1);

// シグナルハンドラ関数
function sig_handler($signo)
{

switch (
$signo) {
case
SIGTERM:
// シャットダウンの処理
exit;
break;
case
SIGHUP:
// 再起動の処理
break;
case
SIGUSR1:
echo
"SIGUSR1 を受け取りました...\n";
break;
default:
// それ以外のシグナルの処理
}

}

echo
"シグナルハンドラを設定します...\n";

// シグナルハンドラを設定します
pcntl_signal(SIGTERM, "sig_handler");
pcntl_signal(SIGHUP, "sig_handler");
pcntl_signal(SIGUSR1, "sig_handler");

// あるいは、オブジェクトも指定できます
// pcntl_signal(SIGUSR1, array($obj, "do_something"));

echo "自分自身に SIGUSR1 シグナルを送信します...\n";

// SIGUSR1 をカレントのプロセス ID に送信します
// posix_* 関数を使うには posix 拡張モジュールが必要です
posix_kill(posix_getpid(), SIGUSR1);

echo
"終了\n";

?>

注意

pcntl_signal() は、既存のシグナルハンドラがある場合にはそれを上書きします。

参考

  • pcntl_fork() - 現在実行中のプロセスをフォークする
  • pcntl_waitpid() - 待つかフォークした子プロセスのステータスを返す

add a note

User Contributed Notes 28 notes

up
62
Ryan Jentzsch
7 years ago
If you are using PHP >= 7.1 then DO NOT use `declare(ticks=1)` instead use `pcntl_async_signals(true)`

There's no performance hit or overhead with `pcntl_async_signals()`. See this blog post https://blog.pascal-martin.fr/post/php71-en-other-new-things.html for a simple example of how to use this.
up
31
kuba at valentine dot dev
5 years ago
Remember that signal handlers are called immediately, so every time when your process receives registered signal blocking functions like sleep() and usleep() are interrupted (or more like ended) like it was their time to finish.

This is expected behaviour, but not mentioned in this documentation. Sleep interruption can be very unfortunate when for example you run a daemon that is supposed to execute some important task exactly every X seconds/minutes - this task could be called too early or too often and it would be hard to debug why it's happening exactly.

Simply remember that signal handlers might interfere with other parts of your code.
Here is example workaround ensuring that function gets executed every minute no matter how many times our loop gets interrupted by incoming signal (SIGUSR1 in this case).

<?php

pcntl_async_signals
(TRUE);

pcntl_signal(SIGUSR1, function($signal) {
// do something when signal is called
});

function
everyMinute() {
// do some important stuff
}

$wait = 60;
$next = 0;

while (
TRUE) {
$stamp = time();
do {
if (
$stamp >= $next) { break; }
$diff = $next - $stamp;
sleep($diff);
$stamp = time();
} while (
$stamp < $next);

everyMinute();

$next = $stamp + $wait;
sleep($wait);
}

?>

So in this infinite loop do {} while() calculates and adds missing sleep() to ensure that our everyMinute() function is not called too early. Both sleep() functions here are covered so everyMinute() will never be executed before it's time even if process receives multiple SIGUSR1 signals during it's runtime.
up
18
Zsolt Szilagyi
11 years ago
Remember that declaring a tick handler can become really expensive in terms of CPU cycles: Every n ticks the signal handling overhead will be executed.

So instead of declaring tick=1, test if tick=100 can do the job. If so, you are likely to gain fivefold speed.

As your script might always might miss some signals due to blocking operations like cURL downloads, call pcntl_signal_dispatch() on vital spots, e.g. at the beginning of your main loop.
up
25
webmaster at ajeux dot com
15 years ago
For PHP >= 5.3.0, instead of declare(ticks = 1), you should now use pcntl_ signal_ dispatch().
up
20
rbotzer at yahoo dot com
16 years ago
You cannot assign a signal handler for SIGKILL (kill -9).
up
6
benjamin at josefus dot /NO+SPAM/ net
15 years ago
Since php >= 5.3 handles Closures, you are now able to define the Callback directly.
Try this:

<?php
declare(ticks = 1);

pcntl_signal(SIGUSR1, function ($signal) {
echo
'HANDLE SIGNAL ' . $signal . PHP_EOL;
});

posix_kill(posix_getpid(), SIGUSR1);

die;
?>
up
8
ieure at php dot net
19 years ago
Some weird signal interactions going on here. I'm running PHP 4.3.9.

sleep() calls seem to be interrupted when any signal is received by the PHP script. But things get weird when you sleep() inside a signal handler.

Ordinarily, signal handlers are non-reentrant. That is, if the signal handler is running, sending another signal has no effect. However, sleep() seems to override PHP's signal handling. If you sleep() inside a signal handler, the signal is received and the sleep() is interrupted.

This can be worked around like this:

function handler($signal)
{
// Ignore this signal
pcntl_signal($signal, SIG_IGN);

sleep(10);

// Reinstall signal handler
pcntl_signal($signal, __FUNCTION__);
}

I don't see any mention of this behavior in the documentation.
up
1
Aurelien Marchand
13 years ago
I was having some issues with processing a signal using an object method I use for something else as well. In particular, I wanted to handle SIGCHLD with my own method "do_reap()" which I also call after a stream_select timeout and that uses a non-blocking pcntl_waitpid function.

The method was called when the signal was received but it couldn't reap the child.

The only way it worked was by creating a new handler that itself called do_reap().

So in other words, the following does not work:

<?php
class Parent {
/* ... */
private function do_reap(){
$p = pcntl_waitpid(-1,$status,WNOHANG);
if(
$p > 0){
echo
"\nReaped zombie child " . $p;
}

public function
run(){
/* ... */
pcntl_signal(SIGCHLD,array(&$this,"do_reap"));
$readable = @stream_select($read,$null,$null,5); // 5 sec timeout
if($readable === 0){
// we got a timeout
$this->do_reap();
}
}
?>

But this work:

<?php
class Parent {
/* ... */
private function do_reap(){
$p = pcntl_waitpid(-1,$status,WNOHANG);
if(
$p > 0){
echo
"\nReaped zombie child " . $p;
}

public function
run(){
/* ... */
pcntl_signal(SIGCHLD,array(&$this,"child_died"));
$readable = @stream_select($read,$null,$null,5); // 5 sec timeout
if($readable === 0){
// we got a timeout
$this->do_reap();
}

private function
child_died(){
$this->do_reap();
}
}
?>
up
3
rob at robertjohnkaper dot com
18 years ago
Tip: when using objects, don't set the signal handler from the constructor or even a method called from the constructor - your internal variables will be uninitialised.
up
1
wally at soggysoftware dot co dot uk
8 years ago
A word of caution around the third parameter (restart_syscalls) in pcntl_signal(...).

I kept having a repeated issue where (seemingly randomly) my script would "exit unexpectedly" (_exit_, not crash: the exit code was always 0) while tracking forked children using signal handlers.

It appears that the signal handling is not at fault (indeed, PHP isn't "at fault"). Having "restart_syscalls" set to FALSE seemed to be the problem's cause.

I haven't debugged the issue extensively - except to observe that the issue was highly intermittent and seemed to relate to my use of usleep() in conjunction with restart_syscalls=FALSE.

My theory is the usleep() was wrongly tracking time - as described over here: http://man7.org/linux/man-pages/man2/restart_syscall.2.html

Long story short, I re-enabled (which is the default value) restart_syscalls, and the issue went away.

If you're curious - register_shutdown_function was still being handled correctly - so PHP definitely was _NOT_ crashing. Interestingly however, my procedural code never "resumed" after the signal was handled.

I do not believe this is a bug. I believe it was ignorant user error. YMMV.
up
2
nate at example dot com
18 years ago
If you have a script that needs certain sections to not be interrupted by a signal (especially SIGTERM or SIGINT), but want to make your script ready to process that signal ASAP, there's only one way to do it. Flag the script as having received the signal, and wait for your script to say its ready to process it.

Here's a sample script:

<?
$allow_exit = true; // are we permitting exit?
$force_exit = false; // do we need to exit?

declare(ticks = 1);
register_tick_function('check_exit');
pcntl_signal(SIGTERM, 'sig_handler');
pcntl_signal(SIGINT, 'sig_handler');

function sig_handler () {
global $allow_exit, $force_exit;

if ($allow_exit)
exit;
else
$force_exit = true;
}

function check_exit () {
global $allow_exit, $force_exit;

if ($force_exit && $allow_exit)
exit;
}

$allow_exit = false;

$i = 0;
while (++$i) {
echo "still going (${i})\n";
if ($i == 10)
$allow_exit = true;

sleep(2);
}
?>

You set $allow_exit to true at all times when it is perfectly acceptable that your script could exit without warning. In sections where you really need the script to continue running, you set $allow_exit to false. Any signals received while $allow_exit is false will not take effect until you set $allow_exit to true.

<?
$allow_exit = true;

// unimportant stuff here. exiting will not harm anything

$allow_exit = false;

// really important stuff not to be interrupted

$allow_exit = true;

// more unimportant stuff. if signal was received during
// important processing above, script will exit here
?>
up
-1
Aurelien Marchand
6 years ago
To be clear, the phrase "pcntl_signal() doesn't stack the signal handlers, but replaces them. " means that you can still have different functions for different signals but only one function per signal.

In other words, this will work as expected

<?php

pcntl_async_signals
(true);
pcntl_signal(SIGUSR1,function(){echo "received SIGUSR1";});
pcntl_signal(SIGUSR2,function(){echo "received SIGUSR2";});
posix_kill(posix_getpid(),SIGUSR1);
posix_kill(posix_getpid(),SIGUSR2);

// returns "received SIGUSR1" and then "received SIGUSR2"

?>
up
1
aeolianmeson at NOSPAM dot blitzeclipse dot com
18 years ago
In at least version 5.1.4, the parameter passed to the handler is not a strict integer.

I have had such problems as trying to add the signal to an array, but the array is completely screwed up when viewed (but not viewed immediately after being added). This occurs when the handler is a method (array($this, 'methodname')), or a traditional functions.

To avoid this bug, typecast the parameter to an integer:
(note that each newline may appear to just be 'n'.)

<?php
print("pid= " . posix_getpid() . "\n");
declare(
ticks=1);
$arrsignals = array();

function
handler($nsig)
{
global
$arrsignals;
$arrsignals[] = (int)$nsig;
print(
"Signal caught and registered.\n");
var_dump($arrsignals);
}

pcntl_signal(SIGTERM, 'handler');

// Wait for signals from the command-line (just a simple 'kill (pid)').
$n = 15;
while(
$n)
{
sleep(1);
$n--;
}

print(
"terminated.\n\n");
var_dump($arrsignals);
?>

Dustin
up
1
wm161 at wm161 dot net
18 years ago
When you are running a script inside of a loop that checks a socket, and it hangs on that checking (Either by flaw or design), it can't handle signals until some data is received.

A suggested workaround would be to use the stream_set_blocking function, or stream_select on the offending reads.
up
0
Anonymous
17 years ago
Multiple children return less than the number of children exiting at a given moment SIGCHLD signals is normal behavior for Unix (POSIX) systems. SIGCHLD might be read as "one or more children changed status -- go examine your children and harvest their status values". Signals are implemented as bit masks in most Unix systems, so there can only be 1 SIGCHLD bit set in any given kernel tick for a process.