2010-09-14 3 views
5

J'ai vu beaucoup de questions liées à cela ... mais mon code fonctionne sur python 2.6.2 et échoue échoue pour travailler sur python 2.6.5. Est-ce que je me trompe en pensant que l'ensemble des fonctions atexit enregistrées via ce module ne sont pas appelées quand le programme est tué par un signal? Cette chose ne devrait pas compter ici parce que j'attrape le signal et que je quitte proprement? Que se passe t-il ici? Quelle est la bonne façon de faire cela?python 2.6.x theading/signals/atexit échoue sur certaines versions?

import atexit, sys, signal, time, threading 

terminate = False 
threads = [] 

def test_loop(): 
    while True: 
     if terminate: 
      print('stopping thread') 
      break 
     else: 
      print('looping') 
      time.sleep(1) 

@atexit.register 
def shutdown(): 
    global terminate 
    print('shutdown detected') 
    terminate = True 
    for thread in threads: 
     thread.join() 

def close_handler(signum, frame): 
    print('caught signal') 
    sys.exit(0) 

def run(): 
    global threads 
    thread = threading.Thread(target=test_loop) 
    thread.start() 
    threads.append(thread) 

    while True: 
     time.sleep(2) 
     print('main') 

signal.signal(signal.SIGINT, close_handler) 

if __name__ == "__main__": 
    run() 

python 2.6.2:

$ python halp.py 
looping 
looping 
looping 
main 
looping 
main 
looping 
looping 
looping 
main 
looping 
^Ccaught signal 
shutdown detected 
stopping thread 

python 2.6.5:

$ python halp.py 
looping 
looping 
looping 
main 
looping 
looping 
main 
looping 
looping 
main 
^Ccaught signal 
looping 
looping 
looping 
looping 
... 
looping 
looping 
Killed <- kill -9 process at this point 

Le fil principal 2.6.5 semble ne jamais exécuter les fonctions de AtExit.

+0

J'ai essayé le code sur Python 2.6.5 et Python 2.6.1 sous OS X 10.6, et ils se comportent comme décrit dans la question (2.6.5 n'exécutant pas l'atexit alors que 2.6.1 le fait). J'espère que les gens plus versés dans le code source de Python conseilleraient sur ce qui a changé. –

+0

peut avoir perdu l'intérêt pour cela ou trouvé une solution de contournement, mais je suis toujours intéressé par ce qui a changé entre les deux versions de Python pour déclencher cela. Plutôt que de poser à nouveau la même question, je vais commencer une prime là-dessus. J'espère que ça ne le dérange pas. –

Répondre

7

La différence de racine ici est en fait indépendante des deux signaux et atexit, mais plutôt un changement dans le comportement de sys.exit. Avant environ 2.6.5, sys.exit (plus précisément, SystemExit étant intercepté au niveau supérieur) provoquerait la sortie de l'interpréteur; Si les threads étaient encore en cours d'exécution, ils seraient terminés, comme avec les threads POSIX. Autour de 2.6.5, le comportement a changé: l'effet de sys.exit est maintenant essentiellement le même que le retour de la fonction principale du programme. Lorsque vous faites que - dans les deux versions - l'interpréteur attend que tous les threads soient joints avant de quitter.

Le changement important est que Py_Finalize appelle maintenant wait_for_thread_shutdown() près du sommet, où il n'a pas auparavant.

Ce changement de comportement semble incorrect, principalement parce qu'il ne fonctionne plus comme documenté, qui est simplement: "Exit from Python". L'effet pratique n'est plus de quitter Python, mais simplement de quitter le thread. (Comme une note de côté, sys.exit n'a jamais quitté Python lorsqu'il est appelé d'un autre thread, mais que Divergence obscur comportement documenté ne justifie pas beaucoup plus grand.)

Je peux voir l'appel du nouveau comportement: plutôt que deux façons de quitter le thread principal ("exit and wait for threads" et "exit immédiatement"), il n'y en a qu'une, car sys.exit est essentiellement identique au simple retour de la fonction top. Cependant, c'est un changement de rupture et diverge du comportement documenté, qui l'emporte de loin sur cela.

En raison de ce changement, après sys.exit à partir du gestionnaire de signal ci-dessus, l'interpréteur s'assoit en attendant que les threads se terminent, puis exécute les gestionnaires atexit après cela. Puisque c'est le gestionnaire lui-même qui dit aux threads de sortir, le résultat est un blocage.

+1

Merci beaucoup, Glenn. Maintenant que je sais ce qu'il faut rechercher, je trouve le rapport de problème Python [ici] (http://bugs.python.org/issue1722344). Je suis d'accord que c'est un grand changement qui aurait dû être fait dans plus d'une mise à jour mineure. –

0

Je ne sais pas si cela a été entièrement changé, mais voilà comment je l'ai fait dans mon atexit 2.6.5


atexit.register(goodbye) 

def goodbye(): 
    print "\nStopping..." 
+1

depuis 2.6 atexit.register peut être utilisé comme décorateur. – lostincode

+0

Hmm, c'est bizarre. Etes-vous sûr que vous utilisez le même code et qu'il n'est pas caché ailleurs ou quelque chose de bizarre comme ça? – Falmarri

3

due Fermeture à un signal est pas la même que la sortie de au sein de un gestionnaire de signal. Attraper un signal et quitter avec sys.exit est une sortie propre, pas une sortie due à un gestionnaire de signal. Donc, oui, je suis d'accord que ça devrait fonctionner avec des manutentionnaires atexit ici - du moins en principe. Cependant, il y a quelque chose de compliqué dans les gestionnaires de signaux: ils sont complètement asynchrones. Ils peuvent interrompre le flux du programme à tout moment, entre n'importe quel opcode VM. Prenez ce code, par exemple. (Traiter cela comme la même forme que votre code ci-dessus, j'ai le code omis par souci de concision.)

import threading 
lock = threading.Lock() 
def test_loop(): 
    while not terminate: 
     print('looping') 
     with lock: 
      print "Executing synchronized operation" 
     time.sleep(1) 
    print('stopping thread') 

def run(): 
    while True: 
     time.sleep(2) 
     with lock: 
      print "Executing another synchronized operation" 
     print('main') 

Il y a un sérieux problème: un signal peut être reçu pendant l'exécution() est (par exemple^C.) maintenant lock. Si cela se produit, votre gestionnaire de signal sera exécuté avec le verrou toujours maintenu. Il attendra alors que test_loop sorte, et si ce thread attend le verrou, vous serez bloqué.

Ceci est une catégorie entière de problèmes, et c'est pourquoi un lot d'API dit ne pas les appeler depuis les gestionnaires de signaux. Au lieu de cela, vous devez définir un indicateur pour indiquer au thread principal de s'arrêter à un moment approprié.

do_shutdown = False 
def close_handler(signum, frame): 
    global do_shutdown 
    do_shutdown = True 
    print('caught signal') 

def run(): 
    while not do_shutdown: 
     ... 

Ma préférence est d'éviter de quitter le programme avec sys.exit entièrement et à faire explicitement le nettoyage au point de sortie principale (par exemple. La fin de course()), mais vous pouvez utiliser atexit ici si vous voulez .