¿Por qué ps * very * ocasionalmente no puede encontrar un proceso válido?

9

Me he encontrado con un problema extraño en el que un ps -o args -p <pid>comando muy ocasionalmente no puede encontrar el proceso en cuestión, aunque definitivamente se está ejecutando en el servidor en cuestión. Los procesos en cuestión son scripts de reinicio de larga duración utilizados para iniciar algunas aplicaciones Java.

Las ocurrencias "en estado salvaje" de la cuestión siempre parecen ocurrir temprano en la mañana, por lo que hay cierta evidencia de que se ha relacionado con la carga de disco en el servidor en cuestión, porque están muy fuertemente cargados entonces, pero ejecutando el psen pregunta en un bucle cerrado, eventualmente puedo replicar el problema: una vez cada cientos de ejecuciones obtengo un error.

Al ejecutar el siguiente script bash, he logrado generar resultados extraños tanto para una ejecución fallida como exitosa:

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

Comparando el resultado de fail.outy good.out, puedo ver que la getdentsllamada del sistema en la ejecución que falla de alguna manera devuelve un número mucho menor que el recuento real de procesos en el sistema (del orden de ~ 500 en comparación con ~ 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

... y esa lista más corta no incluye el pid real en cuestión, por lo que no se encuentra.

Puede ignorar esta sección, los errores ENOTTY se explican por el comentario de dave_thompson a continuación, y no están relacionados

Además, la ejecución fallida obtiene algunos ENOTTYerrores que no aparecen en la ejecución exitosa. Cerca del comienzo de la salida veo

ioctl (1, TIOCGWINSZ, 0x7fffe19db310) = -1 ENOTTY (ioctl inapropiado para dispositivo) ioctl (1, TCGETS, 0x7fffe19db280) = -1 ENOTTY (ioctl inapropiado para dispositivo)

Y al final veo un solo

ioctl (1, TCGETS, 0x7fffe19db0d0) = -1 ENOTTY (ioctl inapropiado para el dispositivo)

El error ioctlal final ocurre justo antes de las psdevoluciones, pero ocurre después de que psya ha impreso un conjunto de resultados vacío, por lo que no estoy seguro de si están relacionados. Sé que son consistentes en todos los resultados de strace fallidos que tengo, pero no aparecen en los exitosos.

No tengo absolutamente ninguna idea de por getdentsqué ocasionalmente no encontraría la lista completa de procesos, y ahora he llegado al punto en el que solo voy a aplicar una curita en todo el asunto cambiando el script de control que verifica el script de envoltura en cuestión para llamar por pssegunda vez si falla la primera, pero me interesaría saber si alguien tiene alguna idea de lo que está sucediendo aquí.

El sistema en cuestión ejecuta Kernel 4.16.13-1.el7.elrepo.x86_64 en CentOS 7 y procps-ng versión 3.3.10-17.el7_5.2.x86_64

James
fuente
1
Para su información, los ioctls tienen que ver con obtener la configuración del terminal (por ejemplo, el primero es encontrar el número de filas y columnas), por lo que es extraño que estén fallando, pero probablemente no sea una causa directa. Esto suena como un error del kernel ...
derobert
2
investigación relacionada de OpenBSD: https.www.google.com.tedunangst.com/flak/post/…
thrig
2
Tiene >/dev/nullen la invocación 'falla' (en el bucle) pero no la invocación 'buena', de ahí la ENOTTY en fd 1.
dave_thompson_085
Oh maldita sea. Gracias por atrapar a ese Dave, eso sin duda explica los ENOTTY.
James
Me alegra ver que no soy el único que tiene este problema. La forma de evitar esto es tener un try-catch que volverá a intentar si falla el comando, aunque sigue siendo molesto: /
Josh Correia

Respuestas:

7

Considere leer la información que necesita directamente del /procsistema de archivos en lugar de a través de una herramienta como ps. Encontrará la información que busca ("args") dentro del archivo /proc/$pid/cmdline, solo separada por bytes NUL en lugar de espacios.

Puede usar esta sedlínea para obtener los argumentos del proceso $pid:

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

Este comando es equivalente a:

ps -o args= -p "$pid"

(El uso de args=in psomitirá el encabezado).

El sedcomando primero buscará el último byte NUL final y lo reemplazará con una nueva línea, y luego reemplazará todos los demás bytes NUL (separando argumentos individuales) con espacios, finalmente produciendo el mismo formato que está viendo ps.


Con respecto a los procesos de listado en el sistema, lo pshace enumerando directorios /proc, pero hay condiciones de carrera inherentes a ese procedimiento, ya que los procesos comienzan y salen mientras psse ejecuta, por lo que lo que obtienes no es realmente una instantánea sino una aproximación. En particular, es posible que psmuestre procesos que ya han finalizado para el momento en que muestra sus resultados, u omita procesos que se iniciaron mientras se estaba ejecutando (pero que el núcleo no devolvió al enumerar el contenido de /proc).

Siempre supuse que si un proceso está allí antes de pscomenzar y sigue ahí después de que se haya hecho, entonces no se lo perderá, supuse que el núcleo garantizaría que siempre se incluirían, incluso si hay una gran cantidad de otros procesos siendo creado y destruido. Lo que estás describiendo implica que ese no es el caso. Todavía estoy escéptico sobre eso, pero dado que hay condiciones de carrera conocidas en cuanto a cómo psfunciona, supongo que es al menos plausible que la lista de PIDs /procpueda perder una existente debido a esas condiciones de carrera.

Sería posible verificar eso comprobando la fuente del kernel de Linux, pero no lo he hecho (todavía), así que realmente no puedo decir con certeza si existe una condición de carrera que fallaría un proceso de larga ejecución, como tú describes.


La otra parte es la forma en que psfunciona. Incluso si le está pasando un PID único con el -pargumento, sigue enumerando todos los PID existentes, aunque solo esté interesado en ese único. Definitivamente, podría tomar un atajo en ese caso y omitir la lista de las entradas /proce ir directamente a /proc/$pid.

No puedo decir por qué se implementó de esta manera. Quizás porque la mayoría de las psopciones son "filtros" en los procesos, por lo que implementarlo de -pla misma manera fue más fácil, tomar un atajo para ir directamente /proc/$pidpodría involucrar una ruta de código separada o duplicación de código ... Otra hipótesis es que algunos casos que incluyen -pmás opciones adicionales terminan requiriendo una lista, por lo que quizás sea complejo determinar qué casos exactos permitirían tomar el acceso directo y cuáles no.


Lo que nos lleva a la solución alternativa, yendo directamente /proc/$pid, sin enumerar el conjunto completo de PID del sistema, evitando todas las razas conocidas y simplemente obteniendo la información que necesita directamente de la fuente.

Es un poco feo, pero el problema que describe realmente existe, debería ser una forma confiable de obtener esa información.

filbranden
fuente
2
Gracias por eso, Filipe, he votado porque el comando sed es útil (y he cambiado nuestros scripts para que solo miren en / proc) y porque no me di cuenta de que agregar un '=' al ps dejaría caer el encabezado . No he aceptado la respuesta porque todavía tengo curiosidad por saber por qué no ve la lista completa de / proc y estoy esperando que alguien más lo sepa :)
James