PHP pcntl_signal回调函数未被调用。

4
这是完整可重现的代码。
<?php
class console{
    public static function log($msg, $arr=array()){
        $str = vsprintf($msg, $arr);
        fprintf(STDERR, "$str\n");
    }
}
function cleanup(){
    echo "cleaning up\n";
}
function signal_handler($signo){
    console::log("Caught a signal %d", array($signo));
    switch ($signo) {
        case SIGTERM:
            // handle shutdown tasks
             cleanup();
            break;
        case SIGHUP:
            // handle restart tasks
            cleanup();
            break;
        default:
            fprintf(STDERR, "Unknown signal ". $signo);
    }
}

if(version_compare(PHP_VERSION, "5.3.0", '<')){
    // tick use required as of PHP 4.3.0
    declare(ticks = 1);
}

pcntl_signal(SIGTERM, "signal_handler");
pcntl_signal(SIGHUP, "signal_handler");

if(version_compare(PHP_VERSION, "5.3.0", '>=')){
    pcntl_signal_dispatch();
    console::log("Signal dispatched");
}

echo "Running an infinite loop\n";
while(true){
    sleep(1);
    echo date(DATE_ATOM). "\n";
}

当我运行这个程序时,每秒钟都会显示日期值。现在如果我按下Ctrl+C键,cleanup函数不会被调用。事实上,signal_handler也没有被调用。

以下是示例输出:

$ php testsignal.php 
Signal dispatched
Running an infinite loop
2012-10-04T13:54:22+06:00
2012-10-04T13:54:23+06:00
2012-10-04T13:54:24+06:00
2012-10-04T13:54:25+06:00
2012-10-04T13:54:26+06:00
2012-10-04T13:54:27+06:00
^C

1
错了,这是你的评论。;) - hakre
version_compare() 函数中的 PHP 版本号与其后的注释存在差异 - 请修复? - halfer
3个回答

11

CTRL+C 会触发 SIGINT 信号,而你没有安装处理程序:

<?php
...
function signal_handler($signo){
...
    case SIGINT:
        // handle restart tasks
        cleanup();
        break;
...
}
...
pcntl_signal(SIGINT, "signal_handler");
...
现在它可以工作了。
$ php testsignal.php
Signal dispatched
Running an infinite loop
2012-10-08T09:57:51+02:00
2012-10-08T09:57:52+02:00
^CCaught a signal 2
cleaning up
2012-10-08T09:57:52+02:00
2012-10-08T09:57:53+02:00
^\Quit

4
重要提示:了解declare的工作原理很重要:http://www.php.net/manual/en/control-structures.declare.php declare需要包含在“根”脚本(例如index.php或类似文件)中,以便影响到它所涉及的内容。
有趣的事实是,在declare(ticks = 1);周围的IF检查没有任何作用。如果您删除这3行,则脚本将停止工作;如果您将检查更改为if(false),则它将正常工作。看来declare在正常代码流之外进行评估。

这是正确的,我正要发布同样的更正。 - scipilot

0

在 Ruben de Vries 更正使用声明 ticks 的编译时,原始代码有两个问题(除了缺少 INT 信号之外)。

正如 Ruben 所说:

if(false) declare(ticks=1);

这个声明了 ticks 将会被处理。试一下!文档接着说你不能使用变量等,这是一个编译时 hack。

此外,使用 pcntl_signal_dispatch()不是直接取代声明 ticks 的方式 - 它必须在代码执行期间调用 - 就像 ticks 自动执行一样。因此,只在脚本头部调用它一次将仅处理任何在那个时刻挂起的信号。

要使 pcntl_signal_dispatch() 像使用 ticks 驱动它一样操作,您需要在代码中随处添加它......具体取决于您希望中断产生作用的精确位置/时间。

至少:

while(true){
    sleep(1);
    echo date(DATE_ATOM). "\n";
    pcntl_signal_dispatch();
}

所以原始代码实际上对于所有版本的PHP都使用ticks,并且如果版本大于5.3,则另外检查初始引导过程中是否接收到了一个信号。这不是OP想要的。

个人认为使用ticks来驱动PCNTL信号非常混乱和痛苦。在我的情况下,“循环”在第三方代码中,因此我无法添加调度方法,并且在顶层声明ticks除非我将其添加到每个文件中(一些自动加载、作用域或PHP7版本特定问题),否则它在生产中不起作用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接