Консольный скрипт. Дочерние процессы становятся зомби при использовании cUrl

Консольный скрипт выполняет импорт данных из внешнего API. Для ускорения загрузки импорта, выполняемой в параллельных процессах, которые создаются pcntl_fork команда.
Для связи с API используется cUrl. Связь осуществляется по протоколу https.

По какой-то неопределенной причине периодически некоторые дети становятся зомби. В консоли нет ошибок / предупреждений / уведомлений, а также нет логов. Уровень ошибок настроен соответствующим образом.

После расследования полагаю, что проблема в расширении curl, так как без него, с поддельным подключением, проблем нет.
Также, если запустить импорт в однопроцессном режиме — проблем вообще нет.

PHP: 7.2.4,
ОС: Debian 9,
Curl: 7.59.0 (x86_64-pc-linux-gnu) libcurl / 7.47.0 OpenSSL / 1.0.2g zlib / 1.2.8 libidn / 1.32 librtmp / 2.3

Может быть, кто-то сталкивался с подобной проблемой или знает возможные причины этого странного поведения?

Пример псевдокода дочерней логики (показана основная часть дочерней логики):

while (true) {
$socket->writeRawString(Signal::MESSAGE_REQUEST_DATA);
$response = $socket->readRawString();
if (Signal::MESSAGE_TERMINATE_PROCESS === $response) {
break;
}
$response = json_decode($response, true);
if (empty($response) || empty($response['deltaId'])) {
continue;
}
$delta = $this->providerConnection->getChanges($response['deltaId']);
if(empty($delta)) {
continue;
}
$xmlReader = new \XMLReader();
$xmlReader->XML($delta);
$xmlReader->read();
$xmlReader->read();
$hasNext = true;
while ($hasNext && 'updated' !== $xmlReader->name) {
$hasNext = $xmlReader->next();
}
if ('updated' !== $xmlReader->name) {
throw new \RuntimeException('Deltas file do not contain updated date.');
}
if (strtotime($xmlReader->readString()) < $endDateTimestamp) {
$socket->writeRawString(self::SIGNAL_END_DATE_REACHED);
continue;
}
}
posix_kill(\posix_getpid(), SIGTERM);

В providerConnection->getChanges($response['deltaId']); запрос выполнен через cUrl. Для работы с cUrl используется Класс Php cUrl расширение

-2

Решение

Как упоминалось в моих комментариях, ваша проблема, вероятно, состоит в том, что дочерние процессы, которые умерли / закончили, должны быть собраны родительским процессом, или они остаются как зомби.

Первое решение:

Установите обработчик сигнала в родительском. Что-то вроде этого:

pcntl_signal(SIGCHLD, [$this, 'handleSignals']);

С обработчиком сигнала, который может выглядеть так:

/**
* @param integer $signal
*/
public function handleSignals($signal) {
switch($signal) {
case SIGCHLD:
do {
$pid = pcntl_wait($status, WNOHANG);
} while($pid > 0);
break;
default:
//Nothing to do
}
}

Я обычно храню пайки раздвоенных детей и проверяю их все индивидуально pcntl_waitpid, но это может заставить вас идти.

Второе решение:

Используйте двойной ответвление для порождения дочерних процессов, если родителю не нужно ждать завершения всех подзадач. Двойная вилка выглядит так:

$pid = pcntl_fork();
if ($pid == -1) handleError();
elseif ($pid ==  0) { // child
$pid = pcntl_fork();
if ($pid == -1) handleChildError();
elseif($pid == 0) { // second level child
exit(startWork()); // init will take over this process
}
// exit first level child
exit(0);
} else {
// parent, wait for first level child
pcntl_wait($pid, $status); // forked child returns almost immediatly, so blocking wait is in order
}
0

Другие решения

Я отказался от использования cUrl для своей задачи. Сегодня я перешел на Guzzle с StreamHandler вместо cUrl, и это решило все мои проблемы.

Я предполагаю, что из-за некоторых внутренних ошибок в cUrl система убивала мои дочерние процессы.

Это не ответ на мой вопрос. Это просто обходной путь моей проблемы для тех, кто также может столкнуться с подобной проблемой.

Тема по-прежнему открыта для возможных предложений / объяснений.

0

По вопросам рекламы [email protected]