Equivalent Waitpid avec timeout?

Imaginez que j’ai un processus qui lance plusieurs processus enfants. Le parent doit savoir quand un enfant sort.

Je peux utiliser waitpid , mais si / quand le parent a besoin de quitter, je n’ai aucun moyen de dire au thread qui est bloqué par waitpid de quitter élégamment et de le rejoindre. C’est bien de faire le ménage, mais ce n’est peut-être pas si grave.

Je peux utiliser waitpid avec WNOHANG , puis dormir pendant un temps arbitraire pour éviter une attente chargée. Cependant, je ne peux savoir que si un enfant est sorti de temps en temps. Dans mon cas, il n’est peut-être pas très critique que je sache quand un enfant sort tout de suite, mais je voudrais savoir dès que possible …

Je peux utiliser un gestionnaire de signal pour SIGCHLD , et dans le gestionnaire de signal, faire ce que je voulais faire quand un enfant quitte, ou envoyer un message à un autre thread pour faire quelque chose. Mais utiliser un gestionnaire de signaux obscurcit un peu le stream du code.

Ce que je voudrais vraiment faire, c’est d’utiliser waitpid sur un certain délai, par exemple 5 sec. Étant donné que la sortie du processus n’est pas une opération critique, je peux paresseusement signaler au thread de quitter, tout en le waitpid bloqué dans waitpid le rest du temps, toujours prêt à réagir. Y at-il un tel appel dans Linux? Parmi les alternatives, laquelle est la meilleure?


MODIFIER:

Une autre méthode basée sur les réponses serait de bloquer SIGCHLD dans tous les threads avec pthread \ _sigmask() . Puis, dans un seul thread, continuez à appeler sigtimedwait() en recherchant SIGCHLD . Cela signifie que je peux expirer lors de cet appel et vérifier si le thread doit quitter et, dans le cas contraire, restr bloqué dans l’attente du signal. Une fois qu’un SIGCHLD est livré à ce thread, nous pouvons y réagir immédiatement, et en ligne du thread d’attente, sans utiliser de gestionnaire de signal.

La fonction peut être interrompue par un signal, vous pouvez donc définir un temporisateur avant d’appeler waitpid () et il sortira avec un EINTR lorsque le signal de la timer sera déclenché. Edit: Cela devrait être aussi simple que d’appeler alarm (5) avant d’appeler waitpid ().

Ne pas mélanger l’ alarm() avec l’ wait() . Vous pouvez perdre des informations d’erreur de cette façon.

Utilisez le tour de passe-passe. Cela transforme n’importe quel signal en un événement select() capable:

 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); } 

Ensuite, votre fonction de type waitpid ressemble à ceci:

 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; } 

Vous pouvez voir dans selfpipe_waitpid() comment vous pouvez contrôler le timeout et même mélanger avec d’autres E / S basées sur select() .

Fourchez un enfant intermédiaire, qui pousse le véritable enfant et un processus de temporisation et attend tous (les deux) ses enfants. Quand on sort, ça va tuer l’autre et sortir.

 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); 

Étonnamment simple 🙂

Vous pouvez même omettre l’enfant intermédiaire si vous êtes sûr qu’aucun autre module du programme ne lui est propre.

C’est une question intéressante. J’ai trouvé que sigtimedwait peut le faire.

EDIT 2016/08/29: Merci pour la suggestion de Mark Edington. Je vais tester votre exemple sur Ubuntu 16.04, ça marche comme prévu.

Remarque: cela ne fonctionne que pour les processus enfants. C’est une pitié qui ne semble pas être un moyen équivalent à WaitForSingleObject de Windows (unrelated_process_handle, timeout) dans Linux / Unix pour être averti de la fin du processus sans rapport dans le délai imparti.

OK, l’exemple de code de Mark Edington est ici :

 /* 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  #include  #include  #include  #include  #include  #include  #include  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; } 

Si vous utilisez des signaux de toute façon (selon la suggestion de Steve), vous pouvez simplement envoyer le signal manuellement lorsque vous souhaitez quitter. Cela causera waitpid pour renvoyer EINTR et le thread peut alors quitter. Pas besoin d’une alarme / redémarrage périodique.

Je pensais que select renverrait EINTR lorsque SIGCHLD signalé par l’enfant. Je crois que cela devrait fonctionner:

 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 } 

Remarque: vous pouvez utiliser pselect pour ignorer le sigmask actuel et éviter les interruptions des signaux inutiles.

En raison des circonstances, j’avais absolument besoin de cela pour s’exécuter dans le thread principal et il n’était pas très simple d’utiliser le tour de rôle ou eventfd, car ma boucle epoll fonctionnait dans un autre thread. Je suis donc arrivé à ceci en fouillant ensemble d’autres gestionnaires de débordement de stack. Notez qu’en général, il est beaucoup plus sûr de le faire autrement, mais c’est simple. Si quelqu’un a envie de commenter à quel point c’est vraiment grave, alors je suis tout à fait prêt.

NOTE : Il est absolument nécessaire de bloquer la gestion des signaux dans n’importe quel thread sauf pour celui sur lequel vous voulez l’exécuter. Je le fais par défaut car je pense qu’il est compliqué de gérer les signaux dans des threads aléatoires.

 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 : Je suis depuis passé à utiliser une solution qui s’intègre bien avec mon événement eventloop basé sur epoll (), en utilisant timerfd. Je ne perds pas vraiment d’indépendance vis-à-vis de la plate-forme puisque j’utilisais epoll de toute façon et je prends un peu de repos car je sais que la combinaison impie de signaux multi-threads et UNIX ne nuira pas à mon programme.

Je peux utiliser un gestionnaire de signal pour SIGCHLD, et dans le gestionnaire de signal, faire ce que je voulais faire quand un enfant quitte, ou envoyer un message à un autre thread pour faire quelque chose. Mais utiliser un gestionnaire de signaux obscurcit un peu le stream du code.

Pour éviter les conditions de course, évitez de faire quelque chose de plus complexe que de changer un indicateur volatile dans un gestionnaire de signal.

Je pense que la meilleure option dans votre cas est d’envoyer un signal au parent. waitpid () définira alors errno sur EINTR et retournera. À ce stade, vous vérifiez la valeur de retour waitpid et errno, notez que vous avez reçu un signal et prenez les mesures appropriées.