У меня есть приложение командной строки 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);
}
Если я убью сценарий с Ctrl+С пока выполняется запрос CURL, он не имеет никакого эффекта. Команда просто висит. Если я удалю свой пользовательский обработчик выключения, Ctrl+С убивает запрос CURL немедленно.
Почему CURL неубиваем, когда я определяю SIGINT
обработчик?
То, что действительно похоже на работу, дает всему пространству некоторое пространство для магии обработки сигналов. Такое пространство, по-видимому, обеспечивается за счет включения обработки прогресса cURL, а также установки обратного вызова прогресса «пользовательского пространства»:
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);
}
Похоже, должно быть что-то в функции обратного вызова прогресса. Пустое тело, похоже, не работает, так как, вероятно, просто не дает PHP много времени для обработки сигналов. (жесткое предположение).
Ввод pcntl_signal_dispatch()
в обратном вызове, кажется, работает даже без declare(ticks=1);
на PHP 7.1
,
...
curl_setopt($ch, CURLOPT_PROGRESSFUNCTION, function() {
pcntl_signal_dispatch();
});
...
С помощью 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
.
Когда вы отправляете Ctrl + C
команда, PHP
пытается завершить текущее действие перед выходом.
ПОСМОТРЕТЬ ЗАКЛЮЧИТЕЛЬНЫЕ МЫСЛИ В КОНЦЕ ДЛЯ БОЛЬШЕ ПОДРОБНОГО ОБЪЯСНЕНИЯ
Ваш код не выходит, потому что 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:
На изображении выше вы можете видеть, что когда я выполняю команду SIGINT через Ctrl + C
(показано стрелкой), завершает действие, которое выполняет, затем завершает работу.
Это означает, что если мое исправление верное, все, что нужно, чтобы убить curl в коде OP, это просто 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);
}
Виола! Как и ожидалось, скрипт завершился после завершения текущего процесса, как и предполагалось.
Сайт, который вы пытаетесь свернуть, эффективно отправляет ваш код в путешествие, которое не имеет конца. Единственные действия, способные остановить процесс CTRL + X
, максимальное время выполнения настройка или CURLOPT_TIMEOUT
вариант cURL. Причина CTRL+C
работает, когда вы берете pcntl_signal(SIGINT, $shutdownHandler);
потому что PHP больше не несет бремени постепенного отключения внутренней функцией. Поскольку PHP не работает одновременно, когда у вас есть обработчик, он должен ждать своей очереди, прежде чем он будет выполнен — что он никогда не получит, потому что cURL
задание никогда не закончится, оставляя вас с бесконечными результатами.
Надеюсь, это поможет!
Если возможно обновить до PHP 7.1.X
или выше, я бы использовал Solution 3
что @Smuuf опубликовал. Это чисто.
Но если вы не можете обновить, то вам нужно использовать обходной путь. Проблема происходит, как объяснено в приведенной ниже ветке SO
Функция pcntl_signal не была нажата, а CTRL + C не работает при использовании сокетов
PHP занят блокирующим вводом-выводом и не может обработать ожидающий сигнал, для которого вы настроили обработчик. Каждый раз, когда возникает сигнал, и у вас есть связанный с ним обработчик, 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);
}
}
И ниже это в действии
Некоторое время я ломал голову над этим и не нашел оптимальных решений с использованием pcntl, но в зависимости от предполагаемого использования вашей команды вы можете рассмотреть следующие вопросы:
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15);
file_get_contents()
вместо скручивания.Решение с использованием расширения Ev:
Для кода обработки сигналов это может быть так просто:
$w = new EvSignal(SIGINT, function (){
exit("SIGINT received\n");
});
Ev::run();
Краткий обзор установки расширения Ev:
sudo pecl install Ev
Вам нужно будет включить расширение, добавив в php.ini файл php cli, может быть /etc/php/7.0/cli/php.ini
extension="ev.so"
Если pecl жалуется на отсутствие phpize, установите php7.0-dev.