2010-06-10 23 views
8

J'ai écrit un moteur de balayage Web que j'aimerais pouvoir arrêter via le clavier. Je ne veux pas que le programme meure quand je l'interromps; il doit d'abord vider ses données sur le disque. Je ne veux pas non plus attraper KeyboardInterruptedException, car les données persistantes pourraient être dans un état incohérent.Attraper/bloquer SIGINT lors de l'appel système

Ma solution actuelle est de définir un gestionnaire de signal qui attrape SIGINT et définit un drapeau; Chaque itération de la boucle principale vérifie ce drapeau avant de traiter l'URL suivante.

Cependant, j'ai trouvé que si le système arrive à être exécuter socket.recv() quand j'envoie l'interruption, je reçois ceci:

^C 
Interrupted; stopping... // indicates my interrupt handler ran 
Traceback (most recent call last): 
    File "crawler_test.py", line 154, in <module> 
    main() 
    ... 
    File "/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/socket.py", line 397, in readline 
    data = recv(1) 
socket.error: [Errno 4] Interrupted system call 

et le processus complètement sorti. Pourquoi cela arrive-t-il? Est-il possible d'empêcher l'interruption d'affecter l'appel système?

Répondre

7

socket.recv() appelle la fonction POSIX conforme recv sous-jacente dans la couche C, qui, à son tour, renvoie un code d'erreur EINTR lorsque le processus reçoit un SIGINT en attendant que les données entrantes en recv(). Ce code d'erreur peut être utilisé du côté C (si vous avez programmé en C) pour détecter que recv() est retourné non parce qu'il y a plus de données disponibles sur le socket mais parce que le processus a reçu un SIGINT. Quoi qu'il en soit, ce code d'erreur est transformé en exception par Python, et comme il n'est jamais intercepté, il termine votre application avec la traceback que vous voyez. La solution consiste simplement à attraper socket.error, vérifier le code d'erreur et s'il est égal à errno.EINTR, ignorer l'exception en mode silencieux. Quelque chose comme ceci:

import errno 

try: 
    # do something 
    result = conn.recv(bufsize) 
except socket.error as (code, msg): 
    if code != errno.EINTR: 
     raise 
+0

Grande explication, je vous remercie. – danben

+1

L'utilisation du nombre magique 4 à la place de 'EINTR' ou de tout identifiant fourni par Python est une très mauvaise pratique. Il est susceptible de casser certaines arches. –

+0

Bien sûr, vous avez raison. J'ai encore lu les docs de la bibliothèque Python et il semble que le module 'errno' fournisse ces constantes, donc je vais ajuster l'exemple. –

3

Si vous ne voulez pas que votre appel socket pour être interrompu désactiver le comportement d'interruption après avoir défini le gestionnaire de signal.

signal.signal(<your signal here>, <your signal handler function here>) 
signal.siginterrupt(<your signal here>, False) 

Dans la fonction de gestion de signal, réglez un drapeau, par ex. un threading.Event() et ensuite vérifier ce drapeau dans votre fonction de traitement principale et terminer votre robot d'exploration avec élégance.

Informations générales ici: