Pourquoi ps * very * ne parviendrait-il pas occasionnellement à trouver un processus valide?

9

J'ai rencontré un problème étrange dans lequel une ps -o args -p <pid>commande échoue très occasionnellement à trouver le processus en question, même s'il s'exécute définitivement sur le serveur en question. Les processus en question sont des scripts wrapper de longue durée utilisés pour lancer certaines applications Java.

Les « dans la nature » occurrences de la question semblent toujours arriver tôt le matin, donc il y a des preuves qu'il est lié à la charge du disque sur le serveur en question, car ils sont assez fortement chargés alors, mais en exécutant l' psen question dans une boucle serrée, je peux éventuellement reproduire le problème - une fois toutes les quelques centaines d'exécutions, j'obtiens une erreur.

En exécutant le script bash suivant, j'ai réussi à générer une sortie strace pour une exécution échouée et réussie:

while [ $? == 0 ] ; do strace -o fail.out ps -o args -p <pid> >/dev/null ; done ; strace -o good.out ps -o args -p <pid>

En comparant la sortie de fail.outet good.out, je peux voir que l' getdentsappel système lors de l'exécution qui échoue renvoie en quelque sorte un nombre beaucoup plus petit que le nombre réel de processus sur le système (de l'ordre de ~ 500 par rapport à ~ 1100)

grep getdents good.out
  getdents(5, /* 1174 entries */, 32768)  = 32760
  getdents(5, /* 31 entries */, 32768)    = 992
  getdents(5, /* 0 entries */, 32768)     = 0

grep getdents fail.out
  getdents(5, /* 673 entries */, 32768)   = 16728
  getdents(5, /* 0 entries */, 32768)     = 0

... et cette liste plus courte n'inclut pas le pid en question, il n'est donc pas trouvé.

Vous pouvez ignorer cette section, les erreurs ENOTTY sont expliquées par le commentaire de dave_thompson ci-dessous, et ne sont pas liées

En outre, l'exécution a échoué obtient des ENOTTYerreurs qui n'apparaissent pas dans l'exécution réussie. Vers le début de la sortie je vois

ioctl (1, TIOCGWINSZ, 0x7fffe19db310) = -1 ENOTTY (ioctl inapproprié pour le périphérique) ioctl (1, TCGETS, 0x7fffe19db280) = -1 ENOTTY (ioctl inapproprié pour le périphérique)

Et à la fin, je vois un seul

ioctl (1, TCGETS, 0x7fffe19db0d0) = -1 ENOTTY (ioctl inapproprié pour le périphérique)

L'échec ioctlà la fin se produit juste avant les psretours, mais il se produit après que le psa déjà imprimé un ensemble de résultats vide, donc je ne suis pas sûr s'ils sont liés. Je sais qu'ils sont cohérents dans toutes les sorties d'échec échouées que j'ai, mais n'apparaissent pas dans celles réussies.

Je n'ai absolument aucune idée pourquoi getdentsne trouverait pas occasionnellement la liste complète des processus, et j'ai maintenant atteint le point où je vais juste gifler un pansement sur le tout en changeant le script de contrôle qui vérifie le script wrapper en question d'appeler psune deuxième fois si la première échoue, mais je serais intéressé de savoir si quelqu'un a des idées sur ce qui se passe ici.

Le système en question exécute le noyau 4.16.13-1.el7.elrepo.x86_64 sur CentOS 7 et la version procps-ng 3.3.10-17.el7_5.2.x86_64

James
la source
1
Pour info, les ioctls ont à voir avec l'obtention des paramètres du terminal (par exemple, le premier est de trouver le nombre de lignes et de colonnes) - il est donc étrange qu'ils échouent, mais probablement pas une cause directe. Cela ressemble à un bug du noyau ...
derobert
2
recherche connexe d'OpenBSD: https.www.google.com.tedunangst.com/flak/post/…
jusqu'au
2
Vous avez >/dev/nullsur l'invocation «échec» (dans la boucle) mais pas sur l'invocation «bonne», d'où l'ENOTTY sur le fd 1.
dave_thompson_085
Oh merde. Merci d'avoir attrapé celui-là Dave, cela explique certainement les ENOTTYs.
James
Heureux de voir que je ne suis pas le seul à avoir ce problème. La façon dont je contourne cela est d'avoir un try-catch qui réessayera si la commande échoue, toujours ennuyeux cependant: /
Josh Correia

Réponses:

7

Pensez à lire les informations dont vous avez besoin directement depuis le /procsystème de fichiers plutôt que via un outil tel que ps. Vous trouverez les informations que vous recherchez ("args") dans le fichier /proc/$pid/cmdline, uniquement séparées par des octets NUL au lieu d'espaces.

Vous pouvez utiliser ce sedone-liner pour obtenir les arguments du processus $pid:

sed -e 's/\x00\?$/\n/' -e 's/\x00/ /g' "/proc/$pid/cmdline"

Cette commande équivaut à:

ps -o args= -p "$pid"

(L'utilisation de args=in pssupprimera l'en-tête.)

La sedcommande recherchera d'abord le dernier octet NUL de fin et le remplacera par une nouvelle ligne, puis remplacera tous les autres octets NUL (séparant les arguments individuels) par des espaces, pour finalement produire le même format que celui que vous voyez ps.


Concernant les processus de listage dans le système, le psfait-il en listant les répertoires/proc , mais il existe des conditions de concurrence inhérentes à cette procédure, car les processus démarrent et se terminent pendant l' psexécution, donc ce que vous obtenez n'est pas vraiment un instantané mais une approximation. En particulier, il est possible que psles processus soient déjà terminés au moment où ils affichent leurs résultats, ou omettent les processus qui ont démarré pendant son exécution (mais qui n'ont pas été retournés par le noyau lors de la liste du contenu de /proc.)

J'ai toujours supposé que si un processus est là avant le psdébut et est toujours là après qu'il soit terminé, il ne le manquera pas, j'ai supposé le noyau garantirait que ceux-ci seraient toujours inclus, même s'il y a beaucoup de désabonnement d'autres processus en cours de création et de destruction. Ce que vous décrivez implique que ce n'est pas le cas. Je suis toujours sceptique à ce sujet, mais étant donné qu'il existe des conditions de course connues dans la façon dont cela psfonctionne, je suppose qu'il est au moins plausible que la liste des PID /procpuisse en manquer une existante en raison de ces conditions de course.

Il serait possible de vérifier cela en vérifiant la source du noyau Linux, mais je ne l'ai pas (encore) fait, donc je ne peux pas vraiment dire avec certitude si une telle condition de concurrence existe qui manquerait un processus de longue durée, comme tu décris.


L'autre partie est la façon dont psfonctionne. Même si vous lui passez un seul PID avec l' -pargument, il répertorie toujours tous les PID existants, même si vous n'êtes intéressé que par ce seul. Il pourrait certainement prendre un raccourci dans ce cas et ignorer la liste des entrées /procet aller directement à /proc/$pid.

Je ne peux pas dire pourquoi il a été mis en œuvre de cette façon. Peut-être parce que la plupart des psoptions sont des "filtres" sur les processus, donc l'implémentation de -pla même manière était plus facile, prendre un raccourci pour aller directement /proc/$pidpourrait impliquer un chemin de code séparé ou une duplication de code ... Une autre hypothèse est que certains cas, y compris -pplus d'options supplémentaires, seraient finissent par nécessiter une inscription, il est donc peut-être complexe de déterminer quels cas exacts permettraient de prendre le raccourci et lesquels ne le permettraient pas.


Ce qui nous amène à la solution de contournement, en allant directement à /proc/$pid, sans répertorier l'ensemble complet des PID du système, en évitant toutes les races connues et en obtenant simplement les informations dont vous avez besoin directement à la source.

C'est un peu moche, mais le problème que vous décrivez existe bel et bien, ce devrait être un moyen fiable de récupérer ces informations.

filbranden
la source
2
Merci pour ce Filipe, j'ai voté positivement parce que la commande sed est utile (et j'ai changé nos scripts pour simplement regarder dans / proc) et parce que je ne savais pas que l'ajout d'un '=' au ps ferait tomber l'en-tête . Je n'ai pas accepté la réponse parce que je suis toujours très curieux de savoir pourquoi il ne voit pas toute la liste de / proc et j'espère que quelqu'un d'autre le sait :)
James