Depuración de procesos Python bloqueados con pystack

Situación inicial

En aplicaciones Python con múltiples procesos, es común enfrentar situaciones donde:

  • Un proceso queda hangueado sin continuar su ejecución
  • No se genera ninguna excepción ni mensaje de error
  • Los logs tradicionales no proporcionan información útil
  • Necesitamos identificar exactamente dónde está el bloqueo

En el ecosistema Java, herramientas como jstack y jmap permiten analizra el estado de los procesos. Para Python existe una alternativa similar llamada pystack.

Caso práctico

El problema identificado fue: una tarea que esperaba respuesta de un servicio HTTP sin configurar timeout, provocando que el proceso quedara en espera indefinida ante respuestas lentas del servidor.

Instalación de pystack

Requisitos previos

yum install gdb
pip install pystack-debugger

Instalación en entornos sin acceso externo

Para servidores en redes aisladas, se puede realizar una instalación manual:

# Descargar los paquetes necesarios
pip download pystack-debugger

# Instalar dependencias desde servidor interno
wget -c -t 10 http://servidor-interno:8080/packages/click-8.1.7-py2.py3-none-any.whl
wget -c -t 10 http://servidor-interno:8080/packages/pystack_debugger-0.9.0-py2-none-any.whl

# Instalar en orden
pip install ./click-8.1.7-py2.py3-none-any.whl
pip install ./pystack_debugger-0.9.0-py2-none-any.whl

Utilización de pystack

Análisis de hilos

Para obtener un dump de los hilos activos en un proceso:

pystack 22374

Resultado esperado:

pystack 22374
Dumping Threads....


  File "/usr/lib64/python2.7/threading.py", line 784, in __bootstrap
    self.__bootstrap_inner()
  File "/usr/lib64/python2.7/threading.py", line 811, in __bootstrap_inner
    self.run()
  File "/usr/lib64/python2.7/threading.py", line 764, in run
    self.__target(*self.__args, **self.__kwargs)
  File "/proyectos/servicio/procesamiento/tareas/_base_job.py", line 157, in wait
    time.sleep(self.watch_dog_duration)

---------------

  File "/proyectos/servicio/ejecutor/tareas_ejecutor.py", line 17, in <module>
    proceso.ejecutar()
  File "/proyectos/servicio/procesamiento/tareas/_base_job.py", line 175, in ejecutar
    self.job_execute()
  File "/proyectos/servicio/procesamiento/tareas/_base_job.py", line 521, in job_execute
    (codigo_respuesta, salida, error) = helpers.ejecutar_shell("bash " + self.nombre_script)
  File "/proyectos/servicio/procesamiento/utils/helpers.py", line 218, in ejecutar_shell
    stdout, stderr = p.communicate()
  File "/usr/lib64/python2.7/subprocess.py", line 800, in communicate
    return self._communicate(input)
  File "/usr/lib64/python2.7/subprocess.py", line 1401, in _communicate
    stdout, stderr = self._communicate_with_poll(input)
  File "/usr/lib64/python2.7/subprocess.py", line 1455, in _communicate_with_poll
    ready = poller.poll()
  File "<string>", line 1, in <module>
  File "<string>", line 1, in <module>

Análisis de greenlets

Para aplicaciones que utilizan la biblioteca greenlet (común en frameworks asíncronos):

pystack 22374 --include-greenlet

Este comando muestra tanto los hilos del sistema como los greenlets activos:

pystack 22374 --include-greenlet
Dumping Threads....


  File "/usr/lib64/python2.7/threading.py", line 784, in __bootstrap
    self.__bootstrap_inner()
  File "/usr/lib64/python2.7/threading.py", line 811, in __bootstrap_inner
    self.run()
  File "/usr/lib64/python2.7/threading.py", line 764, in run
    self.__target(*self.__args, **self.__kwargs)
  File "/proyectos/servicio/procesamiento/tareas/_base_job.py", line 157, in wait
    time.sleep(self.watch_dog_duration)

---------------

  File "/proyectos/servicio/ejecutor/tareas_ejecutor.py", line 17, in <module>
    proceso.ejecutar()
  File "/proyectos/servicio/procesamiento/tareas/_base_job.py", line 175, in ejecutar
    self.job_execute()
  File "/proyectos/servicio/procesamiento/tareas/_base_job.py", line 521, in job_execute
    (codigo_respuesta, salida, error) = helpers.ejecutar_shell("bash " + self.nombre_script)
  File "/proyectos/servicio/procesamiento/utils/helpers.py", line 218, in ejecutar_shell
    stdout, stderr = p.communicate()
  File "/usr/lib64/python2.7/subprocess.py", line 800, in communicate
    return self._communicate(input)
  File "/usr/lib64/python2.7/subprocess.py", line 1401, in _communicate
    stdout, stderr = self._communicate_with_poll(input)
  File "/usr/lib64/python2.7/subprocess.py", line 1455, in _communicate_with_poll
    ready = poller.poll()
  File "<string>", line 1, in <module>
  File "<string>", line 1, in <module>

Dumping Greenlets....


  File "/proyectos/servicio/ejecutor/tareas_ejecutor.py", line 17, in <module>
    proceso.ejecutar()
  File "/proyectos/servicio/procesamiento/tareas/_base_job.py", line 175, in ejecutar
    self.job_execute()
  File "/proyectos/servicio/procesamiento/tareas/_base_job.py", line 521, in job_execute
    (codigo_respuesta, salida, error) = helpers.ejecutar_shell("bash " + self.nombre_script)
  File "/proyectos/servicio/procesamiento/utils/helpers.py", line 218, in ejecutar_shell
    stdout, stderr = p.communicate()
  File "/usr/lib64/python2.7/subprocess.py", line 800, in communicate
    return self._communicate(input)
  File "/usr/lib64/python2.7/subprocess.py", line 1401, in _communicate
    stdout, stderr = self._communicate_with_poll(input)
  File "/usr/lib64/python2.7/subprocess.py", line 1455, in _communicate_with_poll
    ready = poller.poll()
  File "<string>", line 1, in <module>
  File "<string>", line 1, in <module>
  File "<string>", line 1, in <genexpr>

Interpretación de resultados

El análisis revela que el proceso está bloqueado en subprocess.communicate(), esperando que un comando shell finalize su ejecución. Este comportamiento es típico cuando:

  • Se llama a un servicio externo sin timeout configurado
  • Un proceso hijo queda en estado zombie
  • Existe un deadlock en la comunicación entre procesos

Recomendaciones

Para evitar este tipo de bloqueos:

  1. Siempre configurar timeouts en llamadas a servicios externos
  2. Utilizar mecanismos de timeout en operaciones de subprocess
  3. Implementar watchdog que termine procesos que excedan tiempo límite
  4. Monitorear procseos con herramientas como pystack de forma periódica

Etiquetas: Python debugging pystack GDB troubleshooting

Publicado el 6-16 03:58