使用自定义信号处理程序的PHP SIGINT不能杀死CURL

时间:2018-04-10 17:12:59

标签: php curl sigint

我有一个带有自定义关闭处理程序的PHP命令行应用程序:

<?php
declare(ticks=1);

$shutdownHandler = function () {
    echo 'Exiting';
    exit();
};

pcntl_signal(SIGINT, $shutdownHandler); 

while (true) {
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, 'http://blackhole.webpagetest.org');
    curl_exec($ch);
    curl_close($ch);
}

如果我在执行CURL请求时使用 Ctrl + C 终止脚本,则它无效。命令只是挂起。如果我删除自定义关闭处理程序, Ctrl + C 会立即终止CURL请求。

为什么在定义SIGINT处理程序时CURL无法启动?

4 个答案:

答案 0 :(得分:8)

有什么用?

真正有用的是给整个事物一些空间来处理它的信号处理魔法。这样的空间似乎是通过启用cURL的进度处理来提供的,同时还设置了一个&#34; userland&#34;进展回调:

解决方案1 ​​

while (true) {
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_NOPROGRESS, false); // "true" by default
    curl_setopt($ch, CURLOPT_PROGRESSFUNCTION, function() {
        usleep(100);
    });
    curl_setopt($ch, CURLOPT_URL, 'http://blackhole.webpagetest.org');
    curl_exec($ch);
    curl_close($ch);
}

似乎需要&#34;某事&#34;在进程回调函数中。空体似乎不起作用,因为它可能不会给PHP花费大量时间进行信号处理(硬核猜测)

解决方案2

即使pcntl_signal_dispatch()上没有declare(ticks=1);,将PHP 7.1置于回调中似乎也能正常工作。

...
curl_setopt($ch, CURLOPT_PROGRESSFUNCTION, function() {
    pcntl_signal_dispatch();
});
...

解决方案3❤(PHP 7.1 +)

使用pcntl_async_signals(true)而不是declare(ticks=1);即使有空的进度回调函数正文也可以

这可能是我个人使用的,所以我将把完整的代码放在这里:

<?php

pcntl_async_signals(true);

$shutdownHandler = function() {
    die("Exiting\n");
};

pcntl_signal(SIGINT, $shutdownHandler);

while (true) {
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_NOPROGRESS, false);
    curl_setopt($ch, CURLOPT_PROGRESSFUNCTION, function() {});
    curl_setopt($ch, CURLOPT_URL, 'http://blackhole.webpagetest.org');
    curl_exec($ch);
    curl_close($ch);
}

所有这三种解决方案都会导致PHP 7.1 在点击CTRL+C 后几乎立即退出。

答案 1 :(得分:8)

发生了什么事?

当您发送Ctrl + C命令时,PHP会在退出前尝试完成当前操作。

为什么我的(OP&#39; s)代码不会退出?

在最后看到最后的想法以获得更详细的解释

您的代码未退出,因为cURL尚未完成,因此PHP无法退出,直到完成当前操作。

您为此练习选择的网站永远不会加载。

如何修复

要修复此问题,请将URL替换为加载的内容,例如https://google.com,例如

证明

我编写了自己的代码示例,以准确向我展示PHP决定退出的时间/地点:

<?php
declare(ticks=1);

$t = 1;
$shutdownHandler = function () {
    exit("EXITING NOW\n");
};

pcntl_signal(SIGINT, $shutdownHandler);

while (true) {
    print "$t\n";
    $t++;
}

在终端中运行时,您可以更清楚地了解PHP的运行方式: enter image description here

在上图中,您可以看到当我通过Ctrl + C发出SIGINT命令时(如箭头所示),它完成了它正在执行的操作,然后退出。

这意味着如果我的修复是正确的,那么在OP代码中杀死curl所需要的只是一个简单的URL更改:

<?php

declare(ticks=1);
$t = 1;
$shutdownHandler = function () {
    exit("\nEXITING\n");
};

pcntl_signal(SIGINT, $shutdownHandler);


while (true) {
    echo "CURL$t\n";
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, 'https://google.com');
    curl_exec($ch);
    curl_close($ch);
}

然后跑步: enter image description here

Viola!正如预期的那样,脚本在当前进程完成后终止,就像它应该的那样。

最后的想法

您尝试卷曲的网站实际上是在无休止的旅行中发送您的代码。能够停止该过程的唯一操作是CTRL + Xmax execution time设置或CURLOPT_TIMEOUT cURL选项。当你取出CTRL+Cpcntl_signal(SIGINT, $shutdownHandler);工作的原因是因为PHP不再有内部函数正常关闭的负担。由于PHP不是并发的,当你确实有处理程序时,它必须在它执行之前等待它 - 它永远不会得到它,因为cURL任务永远不会完成,因此留下你的永无止境的结果。

我希望这会有所帮助!

答案 2 :(得分:4)

如果可以升级到PHP 7.1.X或更高,我会使用@Smuuf发布的Solution 3。那很干净。

但如果您无法升级,则需要使用变通方法。问题发生在下面的SO线程

pcntl_signal function not being hit and CTRL+C doesn't work when using sockets

PHP忙于阻塞IO,它无法处理您为其定制处理程序的待处理信号。无论什么时候发出信号并且你有与它相关的处理程序,PHP都会排队。一旦PHP完成当前操作,它就会执行你的处理程序。

但不幸的是,在这种情况下它永远不会有机会,你没有为你的CURL指定一个超时,它只是一个PHP无法逃脱的黑洞。

因此,如果您可以让一个php进程负责处理SIGTERM并且一个子进程必须处理工作,那么它将起作用,因为父进程将能够捕获信号并处理即使孩子被同一个人占用也会回调

以下是演示相同

的代码
<?php
    $pid = pcntl_fork();
    if ($pid == -1) {
        die('could not fork');
    } else if ($pid) {
        // we are the parent
        declare (ticks = 1);
        //pcntl_async_signals(true);
        $shutdownHandler = function()
        {
            echo 'Exiting' . PHP_EOL;
        };
        pcntl_signal(SIGINT, $shutdownHandler);
        pcntl_wait($status); //Protect against Zombie children
    } else {
        // we are the child
        echo "hanging now" . PHP_EOL;
        while (true) {
            $ch = curl_init();
            curl_setopt($ch, CURLOPT_URL, 'http://blackhole.webpagetest.org');
            curl_exec($ch);
            curl_close($ch);
        }
    }

以下是行动

PHP Kill

答案 3 :(得分:-2)

我已经对这个问题感到头疼了一段时间,并且没有找到任何使用pcntl的最佳解决方案,但根据您的命令的预期用途,您可以考虑:

  • 使用超时:
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15);
    您可能已经知道有many options来控制curl的行为,包括设置progress callback function
  • 使用file_get_contents()代替curl。

A solution using the Ev extension :

对于信号处理代码,它可以像这样简单:

$w = new EvSignal(SIGINT, function (){
        exit("SIGINT received\n");
});
Ev::run();

Ev扩展安装的快速摘要:
sudo pecl install Ev
您需要通过添加到php cli的php.ini文件来启用扩展,可能是/etc/php/7.0/cli/php.ini
extension="ev.so"
如果pecl抱怨缺少phpize,请安装php7.0-dev。