Контекст - это проблема Redis. У нас есть вызов wait3()
, который ожидает, что ретранслятор AOF создаст новую версию AOF на диске. Когда ребенок выполнен, родитель уведомляется через wait3()
, чтобы заменить старый AOF на новый.
Однако в контексте вышеупомянутой проблемы пользователь уведомил нас об ошибке. Я немного изменил реализацию Redis 3.0, чтобы четко регистрировать, когда wait3()
вернулся -1 вместо сбоя из-за этого неожиданного состояния. Так вот что происходит, по-видимому:
-
wait3()
вызывается, когда у нас есть ожидающие ожидания дети. -
SIGCHLD
должен быть установлен вSIG_DFL
, код не установлен вообще в Redis, поэтому это поведение по умолчанию. - Когда происходит первая перезапись AOF,
wait3()
успешно работает, как ожидалось. - Начиная с второй перезаписи AOF (второй созданный дочерний элемент),
wait3()
начинает возвращать -1.
AFAIK невозможно в текущем коде, который мы вызываем wait3()
, в то время как нет ожидающих дочерних элементов, так как при создании дочернего элемента AOF мы устанавливаем значение server.aof_child_pid
на значение pid, а reset это только после успешного вызова wait3()
.
Итак, wait3()
не должно иметь никаких причин для отказа с -1 и ECHILD
, но это так, поэтому, возможно, ребенок-зомби не создан по какой-то неожиданной причине.
Гипотеза 1. Возможно, что Linux во время определенных нечетных условий отбросит ребенка-зомби, например, из-за давления в памяти? Не выглядит разумным, так как зомби имеет только прикрепленные к нему метаданные, но кто знает.
Обратите внимание, что мы вызываем wait3()
с WNOHANG
. И учитывая, что по умолчанию SIGCHLD
установлен на SIG_DFL
, единственное условие, которое должно привести к сбою и возврату -1 и ECHLD
, не должно быть зомби, доступным для сообщения информации.
Гипотеза 2: Другая вещь, которая может произойти, но нет объяснений, если это произойдет, заключается в том, что после того, как первый ребенок умирает, обработчик SIGCHLD
установлен в SIG_IGN
, вызывая wait3()
для возврата -1 и ECHLD
.
Гипотеза 3: Есть ли способ удалить детей-зомби извне? Возможно, у этого пользователя есть какой-то script, который удаляет процессы зомби в фоновом режиме, чтобы затем информация больше не была доступна для wait3()
? Насколько мне известно, никогда не удастся удалить зомби, если родитель не ждет его (с помощью waitpid
или обработки сигнала), и если SIGCHLD
не игнорируется, но, возможно, существует определенный Linux-способ.
Гипотеза 4. В коде Redis есть ошибка, так что мы успешно wait3()
ребенок в первый раз без правильного сброса состояния, а затем мы снова и снова вызываем wait3()
но больше нет зомби, поэтому он возвращает -1. Анализ кода кажется невозможным, но, возможно, я ошибаюсь.
Еще одна важная вещь: мы никогда не замечали этого в прошлом. Это происходит только в этой конкретной системе Linux.
ОБНОВЛЕНИЕ. Йосси Готтлиб предположил, что SIGCHLD
по какой-либо причине получен другим потоком в процессе Redis (обычно это не происходит, только в этой системе). Мы уже маскируем SIGALRM
в bio.c
потоках, возможно, мы могли бы попытаться маскировать SIGCHLD
из потоков ввода-вывода, а также.
Приложение: выбранные части кода Redis
Где вызывается wait3():
/* Check if a background saving or AOF rewrite in progress terminated. */
if (server.rdb_child_pid != -1 || server.aof_child_pid != -1) {
int statloc;
pid_t pid;
if ((pid = wait3(&statloc,WNOHANG,NULL)) != 0) {
int exitcode = WEXITSTATUS(statloc);
int bysignal = 0;
if (WIFSIGNALED(statloc)) bysignal = WTERMSIG(statloc);
if (pid == -1) {
redisLog(LOG_WARNING,"wait3() returned an error: %s. "
"rdb_child_pid = %d, aof_child_pid = %d",
strerror(errno),
(int) server.rdb_child_pid,
(int) server.aof_child_pid);
} else if (pid == server.rdb_child_pid) {
backgroundSaveDoneHandler(exitcode,bysignal);
} else if (pid == server.aof_child_pid) {
backgroundRewriteDoneHandler(exitcode,bysignal);
} else {
redisLog(REDIS_WARNING,
"Warning, detected child with unmatched pid: %ld",
(long)pid);
}
updateDictResizePolicy();
}
} else {
Выбранные части backgroundRewriteDoneHandler
:
void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
if (!bysignal && exitcode == 0) {
int newfd, oldfd;
char tmpfile[256];
long long now = ustime();
mstime_t latency;
redisLog(REDIS_NOTICE,
"Background AOF rewrite terminated with success");
... more code to handle the rewrite, never calls return ...
} else if (!bysignal && exitcode != 0) {
server.aof_lastbgrewrite_status = REDIS_ERR;
redisLog(REDIS_WARNING,
"Background AOF rewrite terminated with error");
} else {
server.aof_lastbgrewrite_status = REDIS_ERR;
redisLog(REDIS_WARNING,
"Background AOF rewrite terminated by signal %d", bysignal);
}
cleanup:
aofClosePipes();
aofRewriteBufferReset();
aofRemoveTempFile(server.aof_child_pid);
server.aof_child_pid = -1;
server.aof_rewrite_time_last = time(NULL)-server.aof_rewrite_time_start;
server.aof_rewrite_time_start = -1;
/* Schedule a new rewrite if we are waiting for it to switch the AOF ON. */
if (server.aof_state == REDIS_AOF_WAIT_REWRITE)
server.aof_rewrite_scheduled = 1;
}
Как вы можете видеть, все пути кода должны выполнять код cleanup
, который reset server.aof_child_pid
равен -1.
Ошибки, зарегистрированные в Redis во время выпуска
21353: C 29 ноября 04: 00: 29.957 * Запись AOF: 8 МБ памяти, используемой при копировании на запись
27848: M 29 Nov 04: 00: 30.133 ^ @wait3() возвратил ошибку: никаких дочерних процессов. rdb_child_pid = -1, aof_child_pid = 21353
Как вы видите, aof_child_pid
не равен -1.