Неверный код выхода получен от wexitstatus

Я использую PCNTL для многопроцессорной обработки большого скрипта на PHP на сервере Ubuntu.
Вот код (упрощенный и прокомментированный)

function signalHandler($signo = null) {
    $pid = posix_getpid();
    switch ($signo) {
        case SIGTERM:
        case SIGINT:
        case SIGKILL:
            // a process is asked to stop (from user or father)
            exit(3);
            break;
        case SIGCHLD:
        case SIGHUP:
            // ignore signals
            break;
        case 10: // signal user 1
            // a process finished its work
            exit(0);
            break;
        case 12: // signal user 2
            // a process got an error.
            exit(3);
            break;
        default:
            // nothing
    }
}

public static function run($nbProcess, $nbTasks, $taskFunc, $args) {
    $pid = 0;
    // there will be $nbTasks tasks to do, and no more than $nbProcess children must work at the same time
    $MAX_PROCESS = $nbProcess;
    $pidFather = posix_getpid();

    $data = array();

    pcntl_signal(SIGTERM, "signalHandler");
    pcntl_signal(SIGINT, "signalHandler");
//  pcntl_signal(SIGKILL, "signalHandler"); // SIGKILL can't be overloaded
    pcntl_signal(SIGCHLD, "signalHandler");
    pcntl_signal(SIGHUP, "signalHandler");
    pcntl_signal(10, "signalHandler"); // user signal 1
    pcntl_signal(12, "signalHandler"); // user signal 2

    for ($indexTask = 0; $indexTask < $nbTasks ; $indexTask++) {
        $pid = pcntl_fork();
        // Father and new child both read code from here

        if ($pid == -1) {
            // log error
            return false;
        } elseif ($pid > 0) {
            // We are in father process
            // storing child id in an array
            $arrayPid[$pid] = $indexTask;
        } else {
            // We are in child, nothing to do now
        }

        if ($pid == 0) {
            // We are in child process

            $pidChild = posix_getpid();

            try {
                //$taskFunc is an array containing an object, and the method to call from that object
                $ret = (array) call_user_func($taskFunc, $indexTask, $args);// similar to $ret = (array) $taskFunc($indexTask, $args);

                $returnArray = array(
                                    "tasknb" => $indexTask,
                                    "time" => $timer,
                                    "data" => $ret,
                );
            } catch(Exception $e) {
                // some stuff to exit child
            }

            $pdata = array();
            array_push($pdata, $returnArray);
            $data_str = serialize($pdata);

            $shm_id = shmop_open($pidChild, "c", 0644, strlen($data_str));
            if (!$shm_id) {
                // log error
            } else {
                if(shmop_write($shm_id, $data_str, 0) != strlen($data_str)) {
                    // log error
                }
            }
            // We are in a child and job is done. Let's exit !
            posix_kill($pidChild, 10); // sending user signal 1 (OK)
            pcntl_signal_dispatch();
        } else {
            // we are in father process,
            // we check number of running children
            while (count($arrayPid) >= $MAX_PROCESS) {
                // There are more children than allowed
                // waiting for any child to send signal
                $pid = pcntl_wait($status);
                // A child sent a signal !

                if ($pid == -1) {
                    // log error
                }

                if (pcntl_wifexited($status)) {
                    $statusChild = pcntl_wexitstatus($status);
                } else
                    $statusChild = $status;

                // father ($pidFather) saw a child ($pid) exiting with status $statusChild (or $status ?)
                //                                                                ^^^^          ^^^^^^
                //                                                                (=3)  (= random number ?)
                if(isset($arrayPid[$pid])) {
                    // father knows this child
                    unset($arrayPid[$pid]);
                    if ($statusChild == 0 || $statusChild == 10 || $statusChild == 255) {
                        // Child did not report any error
                        $shm_id = shmop_open($pid, "a", 0, 0);
                        if ($shm_id === false)
                            // log error
                        else {
                            $shm_data = unserialize(shmop_read($shm_id, 0, shmop_size($shm_id)));
                            shmop_delete($shm_id);
                            shmop_close($shm_id);
                            $data = array_merge($data, $shm_data);
                        }
                        // kill (again) child
                        posix_kill($pid, 10);
                        pcntl_signal_dispatch();;
                    }
                    else {
                        // Child reported an error
                    }
                }
            }
        }
    }
}

Проблема, с которой я столкнулся, связана со значением, возвращаемым wexitstatus.
Чтобы упростить задачу, есть процесс-отец, который должен создать 200 потоков.
Он запускает процессы по одному и ждет для завершения процесса, если на самом деле запущено более 8 потоков.
Я добавил много журналов, поэтому вижу, что дочерний процесс завершил свою работу.
Я вижу, что он вызывает строку posix_kill($pidChild, 10);.
Я см., что обработчик сигнала вызывается с signal user 1 (что приводит к exit(0)).
Я вижу, как отец просыпается, но когда он получает возвращенный код от wexitstatus, он видит код 3 и поэтому думает, что ребенок получил ошибку , в то время как он завершился с кодом 0 !!.
PID - это правильный pid дочернего элемента.
Может быть, я неправильно понимаю, как работают сигналы... Есть подсказки?


person Random    schedule 22.05.2015    source источник
comment
Почему бы вам не установить расширение pthreads и просто использовать потоки? Это процессы, а не потоки. В любом случае, когда ваш дочерний процесс завершится, вызовите exit(0); - это означает успешное завершение программы. Не поднимайте сигналы самостоятельно, если не произойдет ошибка.   -  person N.B.    schedule 22.05.2015
comment
@Н.Б. Никогда не слышал о pthreads, но в любом случае у меня нет выбора, я должен использовать PCNTL (если нет реальной проблемы с PCNTL). Я иду проверять разницу между потоком и процессами, чтобы увидеть, что имеет значение... Я также попытаюсь выйти (0) напрямую и вернуться через несколько минут, спасибо! :)   -  person Random    schedule 22.05.2015
comment
Вот репозиторий GitHUB для расширения pthreads. К вашей проблеме: родительский процесс улавливает все сигналы, которые испускает дочерний процесс. Когда дочерний элемент закончит работу, он должен выйти со статусом 0, что указывает на успех. Все остальные коды выхода указывают на ошибку своего рода. Если я правильно помню, коды состояния 0 и 8 являются чистыми кодами завершения дочернего процесса. Теперь я хотел бы знать, чего вы пытаетесь достичь с помощью этого скрипта и почему каждый дочерний процесс пишет в отдельный блок общей памяти?   -  person N.B.    schedule 22.05.2015
comment
@Н.Б. каждый поток пишет и отправляет по электронной почте PDF-файл. В настоящее время мы занимались монообработкой, но при отправке электронной почты процесс просто ждал, так что это пустая трата времени. Теперь мы используем 8 процессов (на четырехъядерном процессоре) для решения этой проблемы. Общая память используется для получения данных о незначительных ошибках, произошедших в дочернем процессе (которые мы печатаем, когда все дочерние процессы завершают свою работу). Я не писал этот код, но меня попросили использовать этот код и заставить его работать для этой задачи. Итак, я много документировал о PCNTL (что, я думаю, хорошо понимаю), но не смотрел на shmop. Здесь что-то не так ?   -  person Random    schedule 22.05.2015
comment
Я просто спрашиваю о том, что вы делаете - у вас есть какая-то длительная задача, и вы пытаетесь выполнить ее асинхронно, и это нормально. Однако я спросил об общей памяти, потому что она должна быть общей, но каждый процесс получает свою собственную. В подходе нет ничего плохого, просто некоторые вещи не так ясны. Возможно, вы рассматривали возможность использования очереди сообщений и шаблона распределения задач? У вас есть процесс супервизора с дочерними рабочими процессами. Использование ZeroMQ и подхода разветвления требует гораздо меньше кода, чем то, что у вас есть, если, конечно, вы можете его изменить.   -  person N.B.    schedule 22.05.2015
comment
@Н.Б. Этот код используется в 3 разных приложениях, поэтому он должен сохранять один и тот же шаблон, у меня нет большой свободы в этом. Если я его изменю, мне придется изменить его и в других приложениях (что долго и рискованно...), и обосновать, почему это требуется (и, следовательно, более эффективно...). Что я могу сделать, так это «адаптировать» код для работы в моем приложении и выглядеть как другие приложения... Я также отредактировал свой вопрос, чтобы использовать process вместо thread   -  person Random    schedule 22.05.2015
comment
@N.B На самом деле было определено shutdown function. Таким образом, выход (0) вызвал эту функцию отключения и заставил код выхода быть 3. Спасибо за помощь!   -  person Random    schedule 25.05.2015
comment
Что ж, я должен признать - очень хорошо подмечено, это не совсем тривиальная вещь, поэтому примите мой голос, я уверен, что это поможет кому-то в будущем :)   -  person N.B.    schedule 25.05.2015


Ответы (1)


Я нашел проблему.
В моем приложении register_shutdown_function(myFrameworkShutdownFunction) использовался для "плавного" завершения работы скрипта.
Таким образом, exit(0) не останавливал дочерний процесс немедленно. Сначала он перешел в myFrameworkShutdownFunction и преобразовал код возврата 0 в код 3 (из-за неправильно настроенной переменной).

person Random    schedule 25.05.2015