Sweetohm

Michel Casabianca


En cas de problème dans une application Python (boucle infinie ou blocage par exemple), il peut être utile d’afficher la trace de tous les threads en cours d’exécution.

Pour ce faire, on utilisera le module faulthandler qui permet d’afficher la trace des threads d’un programme Python.

On peut l’appeler depuis le code Python lui-même, mais cet usage est rarement pertinent. Il est plus utile de le déclencher par un signal Unix (SIGSEGV, SIGFPE, SIGABRT, SIGBUS ou SIGILL).

Considérons le code suivant :

#!/usr/bin/env python

import sys
import time


def main():
    while True:
        print(".", end='')
        sys.stdout.flush()
        time.sleep(1.0)


if __name__ == '__main__':
    main()

Si nous souhaitons afficher la trace des threads en cours d’exécution, nous devons le lancer en activant le module faulthandler. Pour ce faire nous pouvons :

  • Définir la variable d’environnement PYTHONFAULTHANDLER, par la commande export PYTHONFAULTHANDLER=true, avant de lancer le programme.
  • Nous pouvons aussi passer l’option -X faulthandler sur la ligne de commande, on lancera alors le script par la commande python -X faulthandler test.py.

Lorsque le script tourne, nous devons déterminer la process ID de la VM Python. Nous pouvons taper la commande :

$ ps ax | grep python
86:  806 pts/0    S+     0:00 python -X faulthandler test.py
88:  808 pts/1    S+     0:00 grep -n -d skip python
119: 1606 ?        Sl     0:00 /usr/bin/python /usr/share/system-config-printer/applet.py

Ainsi nous voyons que la VM a pour PID 806. Pour déclencher l’affichage des traces, nous devons donc envoyer un signal Unix à ce processus, par la commande kill -SIGILL 806. Nous obtenons alors la trace suivante sur le terminal où tourne le script :

$ python -X faulthandler test.py 
....................Fatal Python error: Illegal instruction

Current thread 0x00007fab99316700 (most recent call first):
  File "test.py", line 11 in main
  File "test.py", line 15 in <module>
Instruction non permise

La trace de tous les threads est affichée (il n’y en a qu’un ici) et le programme est interrompu avec une erreur arbitraire qui dépend du signal que nous avons envoyé.

Si nous ne voulons pas interrompre le programme, il nous faut traper le signal que nous envoyons au processus, nous pouvons alors afficher la trace des threads, comme dans le code suivant :

#!/usr/bin/env python

import sys
import time
import signal
import faulthandler


def handler(signal, frame):
    faulthandler.dump_traceback()


def main():
    while True:
        print(".", end='')
        sys.stdout.flush()
        time.sleep(1.0)


if __name__ == '__main__':
    signal.signal(signal.SIGILL, handler)
    main()

La ligne signal.signal(signal.SIGILL, handler) indique que nous souhaitons appeler la fonction handler() lorsque nous interceptons le signal SIGILL. Dans le corps de cette fonction, nous affichons la trace des threads.

A noter que nous n’avons plus besoin d’installer le faulthandler en définissant la variable d’environnement PYTHONFAULTHANDLER ou en passant l’argument -X faulthandler sur la ligne de commande.

Lorsque nous envoyons le signal au programme, il affiche la trace des threads comme suit :

$ python test.py
................Current thread 0x00007f3c23e4f700 (most recent call first):
  File "/home/casa/dsk/test.py", line 10 in handler
  File "/home/casa/dsk/test.py", line 17 in main
  File "/home/casa/dsk/test.py", line 22 in <module>
.........................................

Lors de l’appel à la fonction dump_traceback(), nous pouvons passer un fichier dans lequel le faulthandler écrira la trace des threads. Nous pouvons ainsi, à tout moment, enregistrer la trace des threads de nos applications.

Enjoy!