Подтвердить что ты не робот

Эквивалент Waitpid с таймаутом?

Представьте, что у меня есть процесс, который запускает несколько дочерних процессов. Родитель должен знать, когда выйдет ребенок.

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

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

Я могу использовать обработчик сигнала для SIGCHLD, а в обработчике сигналов делать то, что я собирался делать, когда ребенок выходит, или отправить сообщение в другой поток, чтобы сделать какое-то действие. Но использование обработчика сигнала немного запутывает поток кода.

Что я действительно хотел бы сделать, это использовать waitpid в некоторый тайм-аут, скажем 5 секунд. Поскольку выход из процесса не является критическим по времени операцией, я могу лениво сигнализировать о выходе потока, сохраняя при этом, что он заблокирован в waitpid в остальное время, всегда готов реагировать. Есть ли такой вызов в Linux? Из альтернатив, какой из них лучше?


EDIT:

Другим методом, основанным на ответах, будет блокировать SIGCHLD во всех потоках с помощью pthread\_sigmask(). Затем в одном потоке продолжайте звонить sigtimedwait(), ища SIGCHLD. Это означает, что я могу отключить этот вызов и проверить, должен ли поток выйти, а если нет, оставаться заблокированным в ожидании сигнала. Как только a SIGCHLD доставлен в этот поток, мы можем сразу реагировать на него и в строке потока ожидания, не используя обработчик сигнала.

4b9b3361

Ответ 1

Функция может быть прервана сигналом, поэтому вы можете установить таймер перед вызовом waitpid(), и он выйдет с EINTR, когда сигнал таймера будет поднят. Изменить: это должно быть так же просто, как вызвать будильник (5) перед вызовом waitpid().

Ответ 2

Не смешивайте alarm() с wait(). Вы можете потерять информацию об ошибках таким образом.

Используйте трюк с собственной трубкой. Это превращает любой сигнал в событие select():

int selfpipe[2];
void selfpipe_sigh(int n)
{
    int save_errno = errno;
    (void)write(selfpipe[1], "",1);
    errno = save_errno;
}
void selfpipe_setup(void)
{
    static struct sigaction act;
    if (pipe(selfpipe) == -1) { abort(); }

    fcntl(selfpipe[0],F_SETFL,fcntl(selfpipe[0],F_GETFL)|O_NONBLOCK);
    fcntl(selfpipe[1],F_SETFL,fcntl(selfpipe[1],F_GETFL)|O_NONBLOCK);
    memset(&act, 0, sizeof(act));
    act.sa_handler = selfpipe_sigh;
    sigaction(SIGCHLD, &act, NULL);
}

Затем ваша функция waitpid похожа на следующую:

int selfpipe_waitpid(void)
{
    static char dummy[4096];
    fd_set rfds;
    struct timeval tv;
    int died = 0, st;

    tv.tv_sec = 5;
    tv.tv_usec = 0;
    FD_ZERO(&rfds);
    FD_SET(selfpipe[0], &rfds);
    if (select(selfpipe[0]+1, &rfds, NULL, NULL, &tv) > 0) {
       while (read(selfpipe[0],dummy,sizeof(dummy)) > 0);
       while (waitpid(-1, &st, WNOHANG) != -1) died++;
    }
    return died;
}

В selfpipe_waitpid() вы можете видеть, как вы можете контролировать таймаут и даже смешивать с другим IO select().

Ответ 3

Вставьте промежуточного ребенка, который разворачивает реального ребенка и процесс ожидания и ждет всех (обоих) его детей. Когда кто-то выйдет, он убьет другого и выйдет.

pid_t intermediate_pid = fork();
if (intermediate_pid == 0) {
    pid_t worker_pid = fork();
    if (worker_pid == 0) {
        do_work();
        _exit(0);
    }

    pid_t timeout_pid = fork();
    if (timeout_pid == 0) {
        sleep(timeout_time);
        _exit(0);
    }

    pid_t exited_pid = wait(NULL);
    if (exited_pid == worker_pid) {
        kill(timeout_pid, SIGKILL);
    } else {
        kill(worker_pid, SIGKILL); // Or something less violent if you prefer
    }
    wait(NULL); // Collect the other process
    _exit(0); // Or some more informative status
}
waitpid(intermediate_pid, 0, 0);

Удивительно просто:)

Вы даже можете исключить промежуточного дочернего элемента, если вы уверены, что ни один другой модуль в программе не разжигает дочерние процессы.

Ответ 4

Это интересный вопрос. Я нашел sigtimedwait.

EDIT 2016/08/29: Спасибо за предложение Марка Эдингтона. Я проверил ваш пример на Ubuntu 16.04, он работает так, как ожидалось.

Примечание: это работает только для дочерних процессов. Жаль, что в Linux/Unix нет эквивалентного способа Window WaitForSingleObject (unrelated_process_handle, timeout), чтобы получать уведомление о несвязанной завершении процесса в течение таймаута.

ОК, пример кода Марка Эдингтона здесь:

/* The program creates a child process and waits for it to finish. If a timeout
 * elapses the child is killed. Waiting is done using sigtimedwait(). Race
 * condition is avoided by blocking the SIGCHLD signal before fork().
 */
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

static pid_t fork_child (void)
{
    int p = fork ();

    if (p == -1) {
        perror ("fork");
        exit (1);
    }

    if (p == 0) {
        puts ("child: sleeping...");
        sleep (10);
        puts ("child: exiting");
        exit (0);
    }

    return p;
}

int main (int argc, char *argv[])
{
    sigset_t mask;
    sigset_t orig_mask;
    struct timespec timeout;
    pid_t pid;

    sigemptyset (&mask);
    sigaddset (&mask, SIGCHLD);

    if (sigprocmask(SIG_BLOCK, &mask, &orig_mask) < 0) {
        perror ("sigprocmask");
        return 1;
    }

    pid = fork_child ();

    timeout.tv_sec = 5;
    timeout.tv_nsec = 0;

    do {
        if (sigtimedwait(&mask, NULL, &timeout) < 0) {
            if (errno == EINTR) {
                /* Interrupted by a signal other than SIGCHLD. */
                continue;
            }
            else if (errno == EAGAIN) {
                printf ("Timeout, killing child\n");
                kill (pid, SIGKILL);
            }
            else {
                perror ("sigtimedwait");
                return 1;
            }
        }

        break;
    } while (1);

    if (waitpid(pid, NULL, 0) < 0) {
        perror ("waitpid");
        return 1;
    }

    return 0;
}

Ответ 5

Если вы собираетесь использовать сигналы в любом случае (согласно предложению Стива), вы можете просто отправить сигнал вручную, когда хотите выйти. Это заставит waitpid возвращать EINTR, и поток может выйти. Нет необходимости в периодическом аварийном/перезапуске.

Ответ 6

Я думал, что select вернет EINTR, когда SIGCHLD будет сигнализироваться дочерним элементом. Я верю, что это должно работать:

while(1)
{
  int retval = select(0, NULL, NULL, NULL, &tv, &mask);
  if (retval == -1 && errno == EINTR) // some signal
  { 
      pid_t pid = (waitpid(-1, &st, WNOHANG) == 0);
      if (pid != 0) // some child signaled
  }
  else if (retval == 0)
  {
      // timeout
      break;
  }
  else // error
}

Примечание: вы можете использовать pselect для переопределения текущего sigmask и предотвращения прерываний от ненужных сигналов.

Ответ 7

Из-за обстоятельств я абсолютно нуждался в этом, чтобы работать в главном потоке, и было не очень просто использовать трюк с самописетом или eventfd, потому что мой цикл epoll работал в другом потоке. Поэтому я придумал это, объединив другие обработчики. Обратите внимание, что в целом гораздо безопаснее делать это другими способами, но это просто. Если кто-то хочет прокомментировать, как это действительно плохо, я все уши.

ПРИМЕЧАНИЕ. Абсолютно необходимо блокировать обработку сигналов в любом потоке, сохраняемом для того, для которого вы хотите запустить это. Я делаю это по умолчанию, поскольку считаю, что он беспорядочен для обработки сигналов в случайных потоках.

static void ctlWaitPidTimeout(pid_t child, useconds_t usec, int *timedOut) {
    int rc = -1;

    static pthread_mutex_t alarmMutex = PTHREAD_MUTEX_INITIALIZER;

    TRACE("ctlWaitPidTimeout: waiting on %lu\n", (unsigned long) child);

    /**
     * paranoid, in case this was called twice in a row by different
     * threads, which could quickly turn very messy.
     */
    pthread_mutex_lock(&alarmMutex);

    /* set the alarm handler */
    struct sigaction alarmSigaction;
    struct sigaction oldSigaction;

    sigemptyset(&alarmSigaction.sa_mask);
    alarmSigaction.sa_flags   = 0;
    alarmSigaction.sa_handler = ctlAlarmSignalHandler;
    sigaction(SIGALRM, &alarmSigaction, &oldSigaction);

    /* set alarm, because no alarm is fired when the first argument is 0, 1 is used instead */
    ualarm((usec == 0) ? 1 : usec, 0);

    /* wait for the child we just killed */
    rc = waitpid(child, NULL, 0);

    /* if errno == EINTR, the alarm went off, set timedOut to true */
    *timedOut = (rc == -1 && errno == EINTR);

    /* in case we did not time out, unset the current alarm so it doesn't bother us later */
    ualarm(0, 0);

    /* restore old signal action */
    sigaction(SIGALRM, &oldSigaction, NULL);

    pthread_mutex_unlock(&alarmMutex);

    TRACE("ctlWaitPidTimeout: timeout wait done, rc = %d, error = '%s'\n", rc, (rc == -1) ? strerror(errno) : "none");
}

static void ctlAlarmSignalHandler(int s) {
    TRACE("ctlAlarmSignalHandler: alarm occured, %d\n", s);
}

EDIT: с тех пор я перешел к использованию решения, которое хорошо интегрируется с существующим eventollup на основе epoll(), используя timerfd. Я не теряю никакой независимости от платформы, так как в любом случае я использую epoll, и я получаю дополнительный сон, потому что я знаю, что нечестивая комбинация многопоточных и UNIX-сигналов больше не повредит моей программе.

Ответ 8

Я могу использовать обработчик сигналов для SIGCHLD, а в обработчике сигналов делать то, что я собираюсь делать, когда ребенок выходит, или отправить сообщение в другой поток, чтобы сделать какое-то действие. Но использование обработчика сигнала немного запутывает поток кода.

Чтобы избежать условий гонки, вам следует избегать делать что-либо более сложное, чем изменять флажок volatile в обработчике сигналов.

Я думаю, что лучший вариант в вашем случае - отправить сигнал родителям. waitpid() затем установит errno в EINTR и вернется. На этом этапе вы проверяете возвращаемое значение waitpid и errno, заметите, что вам был отправлен сигнал и предприняли соответствующие действия.

Ответ 9

Вместо прямого вызова waitpid(), вы можете вызвать sigtimedwait() с SIGCHLD (который будет отправлен родительскому процессу после выхода из дочернего процесса) и подождать, пока он будет доставлен в текущий поток, как и было предложено именем функции, параметр тайм-аута поддерживается.

пожалуйста, проверьте следующий фрагмент кода для деталей


static bool waitpid_with_timeout(pid_t pid, int timeout_ms, int* status) {
    sigset_t child_mask, old_mask;
    sigemptyset(&child_mask);
    sigaddset(&child_mask, SIGCHLD);

    if (sigprocmask(SIG_BLOCK, &child_mask, &old_mask) == -1) {
        printf("*** sigprocmask failed: %s\n", strerror(errno));
        return false;
    }

    timespec ts;
    ts.tv_sec = MSEC_TO_SEC(timeout_ms);
    ts.tv_nsec = (timeout_ms % 1000) * 1000000;
    int ret = TEMP_FAILURE_RETRY(sigtimedwait(&child_mask, NULL, &ts));
    int saved_errno = errno;

    // Set the signals back the way they were.
    if (sigprocmask(SIG_SETMASK, &old_mask, NULL) == -1) {
        printf("*** sigprocmask failed: %s\n", strerror(errno));
        if (ret == 0) {
            return false;
        }
    }
    if (ret == -1) {
        errno = saved_errno;
        if (errno == EAGAIN) {
            errno = ETIMEDOUT;
        } else {
            printf("*** sigtimedwait failed: %s\n", strerror(errno));
        }
        return false;
    }

    pid_t child_pid = waitpid(pid, status, WNOHANG);
    if (child_pid != pid) {
        if (child_pid != -1) {
            printf("*** Waiting for pid %d, got pid %d instead\n", pid, child_pid);
        } else {
            printf("*** waitpid failed: %s\n", strerror(errno));
        }
        return false;
    }
    return true;
}

См. Https://android.googlesource.com/platform/frameworks/native/+/master/cmds/dumpstate/DumpstateUtil.cpp#46.