Thread et sigterm
Par alexg le 18/06/2011 01:12
Catégories : autre
Modules python : signal,thread
Version Python : >2.5
Dans le monde POSIX, la commande kill envoie un signal (par défaut SIGTERM) au processus. Ce dernier doit avoir défini un traitement correspondant (handler), sans quoi il meurt.
En python si on veut pouvoir réagir au kill, on utilise le module signal, comme la documentation officielle en donne l'exemple
En voici un simpliste:
import signal, os, time
def handler(signum, frame):
print 'Arrrgh !'
exit(0)
print 'I am %d, ready to die !' % os.getpid()
signal.signal(signal.SIGTERM, handler)
while True:
time.sleep(0.1)
Si je lance le programme, puis que je lance une commande kill dessus, il s'arrête, comme prévu:
$ python /tmp/tmp1.py & [1] 16330 I am 16330, ready to die ! $ kill 16330 Arrrgh ! $ [1] Done
Mais si à ça on mélange des thread:
import signal, os, time, threading
def leisure():
while True:
time.sleep(0.1)
t = threading.Thread(target=leisure)
def handler(signum, frame):
print 'Arrrgh !'
exit(0)
print 'I am %d, kill me if you can !' % os.getpid()
signal.signal(signal.SIGTERM, handler)
t.start()
# and me too
leisure()
Le handler est exécuté mais le programme ne s'arrête plus:
$ python /tmp/tmp2.py & [2] 16351 I am 16351, kill me if you can ! $ kill 16351 Arrrgh ! $ # remarquez que pas de Done ! $ ps 16351 python $ kill -KILL 16351 # on ne peut resister à KILL $ [2]+ Processus arrêté python /tmp/tmp2.py
Ceci s'explique par le fait que le ``exit(0)`` de mon handler ne tue que le thread principal et pas le second.
Pour régler le problème j'ai deux solutions. La première est de déclarer que mon deuxième thread est peu important en lui mettant l'indicateur daemon, et dans ce cas lors de l'arrêt du thread principal, l'interpréteur arrêtera unilatéralement ce thread:
t = threading.Thread(target=leisure) t.daemon = True t.start()
La seconde possibilité est de gérer l'arrêt de mon thread moi-même grâce au handler (la solution sera spécifique). Par exemple:
import signal, os, time, threading
terminate = False
def leisure():
while not terminate:
time.sleep(0.1)
t = threading.Thread(target=leisure)
def handler(signum, frame):
print 'Arrrgh !'
global terminate
terminate = True
t.join()
exit(0)
print 'I am %d, I know my dutty !' % os.getpid()
signal.signal(signal.SIGTERM, handler)
t.start()
# and me too
leisure()
Ça fonctionne:
$ python /tmp/tmp3.py & [1] 16631 I am 16631, I know my dutty ! alex@tignasse: ~$ kill 16631 Arrrgh ! alex@tignasse: ~$ [1]+ Done python /tmp/tmp3.py
Mais il y a encore un piège : la fonction de traitement du signal, le handler, ne peut s'exécuter qu'entre deux instructions de la machine virtuelle python.
Imaginez que votre thread principal attende un verrou:
import signal, os, time, threading
terminate = False
lock = threading.Lock()
def leisure():
with lock:
while not terminate:
time.sleep(0.1)
t = threading.Thread(target=leisure)
def handler(signum, frame):
print 'Arrrgh !'
global terminate
terminate = True
t.join()
exit(0)
print 'I am %d, too occupied to die !' % os.getpid()
signal.signal(signal.SIGTERM, handler)
t.start()
# and me too, a bit later to let t take the lock
time.sleep(0.1)
leisure()
Le handler n'est jamais appelé ! Il attend que le thread principal soit libre mais ça n'arrive jamais:
$ python /tmp/tmp4.py & [1] 16710 I am 16710, too occupied to die ! $ kill 16710 $ $ kill -KILL 16710 $ [1]+ Processus arrêté python /tmp/tmp4.py
La solution ? Le mieux est que le thread principal ne s'occupe que d'attendre les signaux (et justement le module signal propose pause pour ça). Créez d'autre thread pour les tâches à faire:
import signal, os, time, threading
terminate = False
lock = threading.Lock()
def leisure():
with lock:
while not terminate:
time.sleep(0.1)
t = threading.Thread(target=leisure)
t2 = threading.Thread(target=leisure)
def handler(signum, frame):
print 'Arrrgh !'
global terminate
terminate = True
t.join()
t2.join()
exit(0)
print "I am %d, I know I won't last !" % os.getpid()
signal.signal(signal.SIGTERM, handler)
t.start()
t2.start()
signal.pause()
Ça marche:
$ python /tmp/tmp5.py & [1] 16786 I am 16786, I know I won't last ! $ kill 16786 Arrrgh ! $ [1]+ Done python /tmp/tmp5.py




