Не удалось получить waitpid() для возврата правильного значения WEXITSTATUS в случае ошибки

У меня есть команда и некоторые входные данные, которые при запуске в командной строке будут возвращать ошибку со связанным кодом ошибки 1:

$ foo bar
[some useful error message...]
$ echo $?
1

Я пытаюсь поймать этот код ошибки с waitpid():

...
char *proc_cmd = "foo bar"
pid_t proc = popen4(proc_cmd, in_fd, out_fd, err_fd, POPEN4_FLAG_NONE);
...
if (waitpid(proc, &global_foo_status, WNOHANG | WUNTRACED) == -1) {
    /* process failed */
}
...
pthread_create(&proc_thread, NULL, perform_foo_function, bar_data);
pthread_join(proc_thread, (void **) NULL);
...

Моя тема будет работать perform_foo_function() пока нет больше bar_data обрабатывать, или пока процесс не завершится неудачей из-за ошибки в данных:

static void * perform_foo_function (data *bar_data) {
    /* check before */
    if (WIFEXITED(global_foo_status)) {
        int exit_status = WEXITSTATUS(global_foo_status);
        if (exit_status != 0) 
            /* process failed */
    }

    /* do stuff with bar_data */
    while (bar_data) {
        /* causes error ... */
    }

    /* check after */
    if (WIFEXITED(global_foo_status)) {
        int exit_status = WEXITSTATUS(global_foo_status);
        if (exit_status != 0) 
            /* process failed */
    }

    pthread_exit(NULL);
}

У меня вопрос как поймать статус ошибки этого процесса? В процессе отладки WEXITSTATUS всегда равен нулю, намеренно ли я создаю ситуацию с ошибкой или предоставляю законный ввод.

Что я неправильно понимаю waitpid() и связанные проверки кода состояния, и какие изменения я должен сделать, чтобы заставить это работать?

Следовать за

Следующий код работает без блокировки:

...
char *proc_cmd = "foo bar"
pid_t global_foo_pid = popen4(proc_cmd, in_fd, out_fd, err_fd, POPEN4_FLAG_NONE);
...
if (waitpid(global_foo_pid, &global_foo_status, WNOHANG | WUNTRACED) == -1) {
    /* process failed */
}
...
pthread_create(&proc_thread, NULL, perform_foo_function, bar_data);
pthread_join(proc_thread, (void **) NULL);
...

static void * perform_foo_function (data *bar_data) 
{
    /* do stuff with bar_data */
    while (bar_data) {
        /* causes error ... */
    }

    /* check after */
    if (WIFEXITED(global_foo_status)) {
        waitpid(global_foo_pid, &global_foo_status, WUNTRACED);
        int exit_status = WEXITSTATUS(global_foo_status);
        if (exit_status != 0) 
            /* process failed */
    }

    pthread_exit(NULL);
}

Я предполагаю, что "проверка после" waitpid() вызов не зависает, потому что процесс уже завершился на этом шаге.

1 ответ

Пара вещей, здесь.

Во-первых, ваш global_foo_status переменная будет обновлена ​​после и только после вызова waitpid() или друзья. В представленном коде вы звоните только waitpid() один раз, прежде чем создавать свою тему. Так что все эти WIFEXITED а также WEXITSTATUS используемые вами макросы работают с тем же значением global_foo_status что вы получили на этом первоначальном звонке waitpid(), Это почти наверняка, почему вы всегда видите нулевое значение при отладке, потому что вы никогда не получите обновленное значение после завершения вашего процесса, и вы просто проверяете это начальное значение снова и снова. Если вы хотите проверить, завершился ли процесс, вам нужно будет позвонить waitpid() снова каждый раз.

Во-вторых, WIFEXITED оценивается как истина, если процесс завершился нормально, но это не единственный способ, которым процесс может завершиться. Есть еще один макрос, WIFSIGNALED это будет иметь значение true, если процесс был прерван из-за получения сигнала. Если вы используете только WIFEXITED чтобы проверить завершение, и ваш процесс ненормально завершается сигналом, вы будете безуспешно проверять вечность. Лучше использовать возврат от waitpid() чтобы узнать, умер ли процесс по какой-либо причине.

Ваша функция должна выглядеть примерно так:

static void * perform_foo_function (data *bar_data) {

    /* check before */

    pid_t status = waitpid(global_foo_pid, &global_foo_status, WNOHANG);
    if ( status == -1 ) {
        perror("error calling waitpid()");
        exit(EXIT_FAILURE);
    }
    else if ( status == global_foo_pid ) {

        /*  Process terminated  */

        if ( WIFEXITED(global_foo_status) ) {

            /*  Process terminated normally  */

            int exit_status = WEXITSTATUS(global_foo_status);
            if ( exit_status ) {
                /*  Process failed  */

                return NULL;
            }
            else {
                /*  Process terminated normally and successfully  */

                return NULL;
            }
        }
        else {

            /*  Process terminated abnormally  */

                return NULL;
        }
    }

    /*  Process is still running if we got here  */

    /* do stuff with bar_data */

    while (bar_data) {
        /* causes error ... */
    }

    /*  Check after - if getting an error from doing stuff
        with bar_data implies the process should always
        shortly terminate, then you probably don't want
        WNOHANG in the following line.                       */

    status = waitpid(global_foo_pid, &global_foo_status, WNOHANG);
    if ( status == -1 ) {
        perror("error calling waitpid()");
        exit(EXIT_FAILURE);
    }
    else if ( status == global_foo_pid ) {

        /*  Process terminated  */

        if ( WIFEXITED(global_foo_status) ) {

            /*  Process terminated normally  */

            int exit_status = WEXITSTATUS(global_foo_status);
            if ( exit_status ) {
                /*  Process failed  */

                return NULL;
            }
            else {
                /*  Process terminated normally and successfully  */

               return NULL;
            }
        }
        else {
            /*  Process terminated abnormally  */

                return NULL;
        }
    }

    pthread_exit(NULL);
}

Эта проверка всего процесса также является основным кандидатом на выделение в отдельную функцию.

Если у вас есть несколько запущенных потоков perform_foo_function() в то же время, то waitpid() только вернется соответственно в одном из них. Вы, вероятно, захотите отдельную переменную, global_foo_has_finished или подобное, что потоки могут проверить перед попыткой вызова waitpid(), Вы также хотели бы синхронизировать доступ ко всем этим глобальным переменным или изменить дизайн, чтобы они не были необходимы (вы могли бы передать global_foo_pid прямо в вашу функцию потока, например, и global_foo_status не должен быть глобальным, так как к нему никогда больше нет доступа).

Другие вопросы по тегам