Контекст — это проблема с Redis. У нас есть вызов wait3()
, который ожидает, пока перезаписывающий дочерний AOF создаст новую версию AOF на диске. Когда дочерний процесс завершен, родитель уведомляется через wait3()
, чтобы заменить старый AOF новым.
Однако в контексте вышеупомянутой проблемы пользователь уведомил нас об ошибке. Я немного изменил реализацию Redis 3.0, чтобы четко регистрировать, когда wait3()
возвращает -1, а не сбой из-за этого неожиданного условия. Так вот что происходит видимо:
wait3()
вызывается, когда у нас есть ожидающие дочерние элементы.SIGCHLD
должно быть установлено наSIG_DFL
, в Redis вообще нет кода, устанавливающего этот сигнал, поэтому это поведение по умолчанию.- Когда происходит первая перезапись AOF,
wait3()
успешно работает, как и ожидалось. - Начиная со второй перезаписи AOF (создан второй дочерний элемент),
wait3()
начинает возвращать -1.
Насколько я знаю, в текущем коде невозможно вызвать wait3()
, пока нет ожидающих дочерних элементов, поскольку при создании дочернего элемента AOF мы устанавливаем server.aof_child_pid
значение pid и сбрасываем его только после успешного вызова wait3()
.
Таким образом, у wait3()
не должно быть причин сбоя с -1 и ECHILD
, но это так, поэтому, вероятно, дочерний зомби не создается по какой-то неожиданной причине.
Гипотеза 1: возможно ли, что Linux при определенных странных условиях отбрасывает дочерний элемент-зомби, например, из-за нехватки памяти? Выглядит неразумно, поскольку к зомби прикреплены только метаданные, но кто знает.
Обратите внимание, что мы вызываем wait3()
с помощью WNOHANG
. И учитывая, что SIGCHLD
по умолчанию установлено в SIG_DFL
, единственным условием, которое должно привести к сбою и возвращению -1 и ECHLD
, должно быть отсутствие зомби, доступных для сообщения информации.
Гипотеза 2. Другая вещь, которая может произойти, но нет никакого объяснения, если это произойдет, заключается в том, что после смерти первого потомка обработчик SIGCHLD
устанавливается на SIG_IGN
, в результате чего wait3()
возвращает -1 и ECHLD
.
Гипотеза 3. Есть ли способ удалить дочерние элементы-зомби извне? Может быть, у этого пользователя есть какой-то скрипт, который убирает зомби-процессы в фоновом режиме, чтобы потом информация была недоступна для 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
, который сбрасывает server.aof_child_pid
в -1.
Ошибки, зарегистрированные Redis во время проблемы
21353:C 29 ноября, 04:00:29.957 * Перезапись AOF: 8 МБ памяти используется копированием при записи
27848:M 29 ноя 04:00:30.133 ^@ wait3() вернул ошибку: Нет дочерних процессов. rdb_child_pid = -1, aof_child_pid = 21353
Как видите, aof_child_pid
не равно -1.
wait*()
? Я бы сказал, что вам предстоит гонка. - person alk   schedule 30.11.2015wait3()
, ребенок еще даже не запустился (полностью)? - person alk   schedule 30.11.2015pid==-1
/ECHLD
так же, какpid==0
, и запишите, найдет лиwait3()
рассматриваемый процесс позже. - person alk   schedule 30.11.2015signal(SIGCHLD, SIG_DFL);
? - person alk   schedule 30.11.2015man 7 signal
значение по умолчанию дляSIGCHLD
должно игнорироваться. - person alk   schedule 30.11.2015signal()
наsigaction()
. - person alk   schedule 30.11.2015SIG_DFL
) после первой обработки сигнала. Так что вполне возможно, что гипотеза 2 сбудется. Просто замените вызовsignal()
наsigaction()
(который не сбрасывается в SIG_DFL), чтобы убедиться, что это правда. - person P.P   schedule 30.11.2015wait4()
, чемwait3()
. Я сомневаюсь, что это решит проблему, о которой вы спрашивали, но это может избавить вас от горя позже. - person John Bollinger   schedule 01.12.2015rdb_child_pid /server.aof_child_pid
? - person nos   schedule 08.12.2015stopAppendOnly
вaof.c
также убивает и ждет3, особенно для рассматриваемого процесса. Можете ли вы быть уверены, что какой-то другой код уже не выиграл гонку за потомством? Это может быть отвлекающим маневром, но в вашем отчете об ошибках все ошибки происходят очень близко к пятиминутным границам — возможно, cron или какая-то другая запланированная работа по обслуживанию вызывает неожиданный урожай? - person pilcrow   schedule 08.12.2015strace(1)
, обязательно используйте-e
, чтобы вы видели только вызовыwait3(2)
. Это скажет вам, действительно ли код пытается получить один и тот же дочерний элемент более одного раза, или это что-то внешнее, которое стирает зомби (неправильно настроенное или ошибочное ядро, или другие странные скрипты, работающие на машине этого пользователя). - person Filipe Gonçalves   schedule 08.12.2015