Le contexte est ce Redis question. Nous avons un wait3()
appel qui attend l'AOF réécriture de l'enfant pour créer la nouvelle AOF version sur le disque. Lorsque l'enfant est fait, le parent est informé par wait3()
afin de remplacer l'ancienne AOF avec le nouveau.
Toutefois, dans le contexte du problème ci-dessus, l'utilisateur nous a informés au sujet d'un bug. J'ai un peu modifié la mise en œuvre de Redis 3.0 afin de clairement journal lorsqu' wait3()
retourné -1 au lieu de s'écraser en raison de cette condition inattendue. Donc, c'est ce qui se passe, apparemment:
-
wait3()
est appelée lorsque nous avons en attendant les enfants à attendre. - l'
SIGCHLD
doit être réglé à l'SIG_DFL
, il n'y a pas de code de ce signal à tous dans le Redis, c'est le comportement par défaut. - Lors de la première AOF réécriture qui se passe,
wait3()
réussi fonctionne comme prévu. - À partir de la deuxième AOF réécriture (le deuxième enfant de sa création)
wait3()
commence à retourner -1.
Autant que je sache, il n'est pas possible dans le code actuel, que nous appelons wait3()
alors qu'il n'y a pas dans l'attente des enfants, depuis quand l'AOF enfant est créé, nous avons mis en server.aof_child_pid
de la valeur du pid, et nous le réinitialiser seulement après la réussite de l' wait3()
appel.
Donc, wait3()
devrait avoir aucune raison d'échouer avec -1 ECHILD
, mais il le fait, alors, probablement, le zombie enfant n'est pas créé, pour des raisons imprévues.
Hypothèse 1: Il est possible que Linux lors de certaines bizarre conditions aura pour effet de rejeter le zombie enfant, par exemple en raison de la pression de la mémoire? N'a pas l'air raisonnable depuis le zombie vient de métadonnées attachées à elle, mais qui sait.
Notez que nous appelons wait3()
avec WNOHANG
. Et étant donné que l' SIGCHLD
est définie à l' SIG_DFL
par défaut, la seule condition qui doit conduire à l'échec et le retour de -1 ECHLD
devrait être pas de zombie disponible pour les renseignements.
Hypothèse 2: Autre chose qui pourrait arriver, mais il n'y a pas d'explication si cela arrive, c'est qu'après le premier enfant meurt, l' SIGCHLD
gestionnaire d' SIG_IGN
, provoquant wait3()
de retour -1 ECHLD
.
Hypothèse 3: Est-il possible de supprimer le zombie enfants à l'extérieur? Peut-être que cet utilisateur a une sorte de script qui supprime zombie processus en arrière-plan de sorte que l'information n'est plus disponible pour l' wait3()
? À ma connaissance, il devrait ne jamais être possible de supprimer le zombie si le parent ne l'attendez pas (avec waitpid
ou de la manipulation du signal) et si l' SIGCHLD
n'est pas ignorée, mais peut-être il ya certains Linux de manière spécifique.
Hypothèse 4: Il y a effectivement quelques bug dans le Redis code, de sorte que nous avons avec succès wait3()
l'enfant la première fois sans correctement la réinitialisation de l'état, et plus tard nous appelons wait3()
encore et encore, mais il n'y a plus de zombies, elle retourne -1. En analysant le code, il semble impossible, mais peut-être que je me trompe.
Une autre chose importante: nous n'avons jamais observé dans le passé. Ne se produit que dans ce système Linux apparemment.
Mise à JOUR: Yossi Gottlieb proposé que l' SIGCHLD
est reçu par un autre thread dans le Redis processus pour une raison quelconque (ne se fait pas normalement, uniquement sur ce système). Nous avons déjà masque SIGALRM
en bio.c
threads, peut-être que nous pourrions essayer de masquage SIGCHLD
de I/O fils.
Annexe: une sélection de pièces de Redis code
Où wait3() est appelée:
/* 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 {
Les parties sélectionnées d' 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;
}
Comme vous pouvez le voir tous les chemins de code doit exécuter l' cleanup
code de réinitialisation server.aof_child_pid
à -1.
Les erreurs consignées par le Redis lors de l'émission
21353:C 29 Nov 04:00:29.957 * AOF réécriture: 8 MO de mémoire utilisée par copy-on-write
27848:M 29 Nov 04:00:30.133 ^@ wait3() a renvoyé une erreur: Pas de processus enfants. rdb_child_pid = -1, aof_child_pid = 21353
Comme vous pouvez le voir aof_child_pid
n'est pas -1.