У меня есть довольно большое приложение под присмотром. В рамках своей работы оно порождает несколько дочерних процессов и должно отслеживать их состояние (работает, упал).
Смерти дочерних процессов были обнаружены путем установки обработчика сигналов для SIGCHLD
использования signal(2)
. Некоторое время назад я перенес его на signalfd(2)
. То, что я сделал, было простым:
- удалил обработчик сигнала для
SIGCHLD
- заблокировал
SIGCHLD
и создалsignalfd(2)
для захватаSIGCHLD
Моя проблема в том, что созданный мной дескриптор файла, похоже, не захватывает SIGCHLD
. Однако, если я проигнорирую возвращаемое значение read(2)
вызова этого дескриптора и вызову waitpid(-1, &status, WNOHANG)
Iможетполучить информацию о завершенных дочерних процессах. Так что похоже, что уведомление доставлено, но мой signalfd(2)
дескриптор просто игнорирует его.
Я позаботился о том, чтобы в программе было ровно одно место, где read(2)
вызывается дескриптор signalfd(2)
, ровно одно место, где waitpid(2)
вызывается , и ровно одно место, где настраивается обработка сигнала.
Код установки выглядит так:
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGCHLD);
sigprocmask(SIG_BLOCK, &mask, nullptr);
int signal_fd = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC);
if (signal_fd == -1) {
/* log failure and exit */
} else {
/* log success */
}
Код чтения выглядит так:
signalfd_siginfo info;
memset(&info, 0, sizeof(info));
if (read(signal_fd, &info, sizeof(info)) == -1) {
/*
* Log failure and return.
* The file descriptor *always* returns EAGAIN, even in
* presence of dead child processes.
*/
return;
}
if (info.ssi_signo == SIGCHLD) {
int status = 0;
int child = waitpid(-1, &status, WNOHANG);
/*
* Process result of waitpid(2). The call is successful even if
* the read of signalfd above returned an error.
*/
}
Что я делаю не так?
Редактировать:Проблема в том, что это read(2)
не удается, EAGAIN
даже если есть мертвые дочерние процессы, готовые к waitpid(2)
-ed, что означает, что a SIGCHLD
должно быть доставлено моему главному процессу. Я знаю, что это read(2)
может вернуться EAGAIN
для неблокирующих файловых дескрипторов, и код учитывает это.
решение1
При переходе с обработки сигналов на основе signal(2)
или sigaction(2)
на signalfd(2)
вы меняете способ получения сигналов. Старый способ оставляет сигналы незаблокированными, новый требует их блокировки.
Если у вас есть области кода, в которых вы не хотите, чтобы вас беспокоили сигналы, вам необходимо заблокировать их:
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGFOO);
pthread_sigmask(SIG_BLOCK, &mask, nullptr);
{
/* not-to-be-disturbed code here */
}
Это требует, чтобы вы позжеразблокироватьих, потому что в противном случае signal(2)
или sigaction(2)
не будет возможности их забрать.
{
/* not-to-be-disturbed code here */
}
pthread_sigmask(SIG_UNBLOCK, &mask, nullptr);
Однако, для signalfd(2)
сигналов необходимо оставаться заблокированными. Если у вас есть длинный заброшенный путь кода, на который вы редко заглядываете, и он следует старому пути, т. е. блокирует и разблокирует некоторые сигналы, он может испортить ваш дескриптор файла чтения сигналов, который вы получили из signalfd(2)
.
TL;DRПроверьте свой код на наличие вызовов signal(2)
, sigaction(2)
и pthread_sigmask(2)
т. д. при переходе на , signalfd(2)
чтобы убедиться, что вы не забыли о каком-либо кодовом пути, который нарушает маску сигнала.
(Спустя два с половиной года может быть немного поздно, но, возможно, ответ кому-то поможет.)
решение2
Ваш read(2)
возврат EAGAIN
связан с тем, что вы открыли файл в неблокируемом режиме с помощью signalfd(..., SFD_NONBLOCK | ...)
( SFD_NONBLOCK
то же самое, что и O_NONBLOCK
).
Не открывайте и не переводите файловые дескрипторы в неблокируемый режим, если вы хотите выполнять блокирующие чтения для них.