Il y a un appel de fonction lié à la socket dans mon code, cette fonction est d'un autre module donc hors de mon contrôle, le problème est qu'il bloque parfois pendant des heures, ce qui est totalement inacceptable, comment puis-je limiter le temps d'exécution de la fonction à partir de mon code? Je suppose que la solution doit utiliser un autre fil.
Je ne sais pas comment cela pourrait être multiplateforme, mais l'utilisation de signaux et d'alarme pourrait être une bonne façon de voir les choses. Avec un peu de travail, vous pourriez également rendre ce produit complètement générique et utilisable dans n'importe quelle situation.
http://docs.python.org/library/signal.html
Donc, votre code va ressembler à ceci.
import signal
def signal_handler(signum, frame):
raise Exception("Timed out!")
signal.signal(signal.SIGALRM, signal_handler)
signal.alarm(10) # Ten seconds
try:
long_function_call()
except Exception, msg:
print "Timed out!"
Une amélioration de la réponse de @ rik.the.vik serait d'utiliser la commande with
pour donner à la fonction timeout du sucre syntaxique:
import signal
from contextlib import contextmanager
class TimeoutException(Exception): pass
@contextmanager
def time_limit(seconds):
def signal_handler(signum, frame):
raise TimeoutException("Timed out!")
signal.signal(signal.SIGALRM, signal_handler)
signal.alarm(seconds)
try:
yield
finally:
signal.alarm(0)
try:
with time_limit(10):
long_function_call()
except TimeoutException as e:
print("Timed out!")
Voici une façon Linux/OSX de limiter le temps d'exécution d'une fonction. C'est au cas où vous ne souhaitez pas utiliser de threads et que votre programme attend la fin de la fonction ou l'expiration du délai.
from multiprocessing import Process
from time import sleep
def f(time):
sleep(time)
def run_with_limited_time(func, args, kwargs, time):
"""Runs a function with time limit
:param func: The function to run
:param args: The functions args, given as Tuple
:param kwargs: The functions keywords, given as dict
:param time: The time limit in seconds
:return: True if the function ended successfully. False if it was terminated.
"""
p = Process(target=func, args=args, kwargs=kwargs)
p.start()
p.join(time)
if p.is_alive():
p.terminate()
return False
return True
if __== '__main__':
print run_with_limited_time(f, (1.5, ), {}, 2.5) # True
print run_with_limited_time(f, (3.5, ), {}, 2.5) # False
Je préfère une approche de gestionnaire de contexte car elle permet l'exécution de plusieurs instructions python dans une instruction with time_limit
. Parce que le système Windows n'a pas SIGALARM
, un plus portable et une méthode peut-être plus simple pourrait être d'utiliser un Timer
from contextlib import contextmanager
import threading
import _thread
class TimeoutException(Exception):
def __init__(self, msg=''):
self.msg = msg
@contextmanager
def time_limit(seconds, msg=''):
timer = threading.Timer(seconds, lambda: _thread.interrupt_main())
timer.start()
try:
yield
except KeyboardInterrupt:
raise TimeoutException("Timed out for operation {}".format(msg))
finally:
# if the action ends in specified time, timer is canceled
timer.cancel()
import time
# ends after 5 seconds
with time_limit(5, 'sleep'):
for i in range(10):
time.sleep(1)
# this will actually end after 10 seconds
with time_limit(5, 'sleep'):
time.sleep(10)
La technique clé ici est l'utilisation de _thread.interrupt_main
Pour interrompre le thread principal du thread timer. Une mise en garde est que le thread principal ne répond pas toujours rapidement au KeyboardInterrupt
soulevé par le Timer
. Par exemple, time.sleep()
appelle une fonction système afin qu'un KeyboardInterrupt
soit traité après l'appel sleep
.
Faire cela à partir d'un gestionnaire de signaux est dangereux: vous pourriez être à l'intérieur d'un gestionnaire d'exceptions au moment où l'exception est levée et laisser les choses dans un état cassé. Par exemple,
def function_with_enforced_timeout():
f = open_temporary_file()
try:
...
finally:
here()
unlink(f.filename)
Si votre exception est levée ici (), le fichier temporaire ne sera jamais supprimé.
La solution ici consiste à reporter les exceptions asynchrones jusqu'à ce que le code ne se trouve pas dans le code de gestion des exceptions (un bloc excepté ou finalement), mais Python ne fait pas cela.
Notez que cela n'interrompt rien pendant l'exécution de code natif; cela ne l'interrompra que lorsque la fonction reviendra, donc cela peut ne pas aider ce cas particulier. (SIGALRM lui-même peut interrompre l'appel bloquant - mais le code de socket réessaye généralement simplement après un EINTR.)
Faire cela avec des threads est une meilleure idée, car il est plus portable que les signaux. Puisque vous démarrez un thread de travail et le bloquez jusqu'à ce qu'il se termine, il n'y a pas de soucis de concurrence habituels. Malheureusement, il n'y a aucun moyen de délivrer une exception de manière asynchrone à un autre thread dans Python (d'autres API de thread peuvent le faire). Il y aura également le même problème avec l'envoi d'une exception lors d'un gestionnaire d'exceptions, et nécessitent le même correctif.
Vous n'êtes pas obligé d'utiliser des threads. Vous pouvez utiliser un autre processus pour effectuer le travail de blocage, par exemple, en utilisant peut-être le module sous-processus . Si vous souhaitez partager des structures de données entre différentes parties de votre programme, alors Twisted est une excellente bibliothèque pour vous donner le contrôle de cela, et je le recommanderais si vous vous souciez du blocage et prévoyez d'avoir ce problème beaucoup. La mauvaise nouvelle avec Twisted est que vous devez réécrire votre code pour éviter tout blocage, et il y a une courbe d'apprentissage équitable.
Vous pouvez utiliser des threads pour éviter le blocage, mais je considérerais cela comme un dernier recours, car cela vous expose à tout un monde de douleur. Lisez un bon livre sur la concurrence avant même de penser à utiliser des threads dans la production, par exemple "Concurrent Systems" de Jean Bacon. Je travaille avec un tas de gens qui font vraiment des trucs très performants avec des threads, et nous n'introduisons pas de threads dans les projets à moins d'en avoir vraiment besoin.
La seule façon "sûre" de le faire, dans n'importe quelle langue, est d'utiliser un processus secondaire pour faire cette temporisation, sinon vous devez construire votre code de manière à ce qu'il expire en toute sécurité par lui-même, par exemple en vérifier le temps écoulé dans une boucle ou similaire. Si changer la méthode n'est pas une option, un thread ne suffira pas.
Pourquoi? Parce que vous risquez de laisser les choses en mauvais état lorsque vous le faites. Si le thread est simplement tué à mi-chemin, les verrous maintenus, etc. seront simplement maintenus et ne pourront pas être libérés.
Regardez donc le chemin du processus, faites pas regardez le chemin du fil.
Voici une fonction de délai d'attente que je pense avoir trouvée via google et cela fonctionne pour moi.
De: http://code.activestate.com/recipes/473878/
def timeout(func, args=(), kwargs={}, timeout_duration=1, default=None):
'''This function will spwan a thread and run the given function using the args, kwargs and
return the given default value if the timeout_duration is exceeded
'''
import threading
class InterruptableThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.result = default
def run(self):
try:
self.result = func(*args, **kwargs)
except:
self.result = default
it = InterruptableThread()
it.start()
it.join(timeout_duration)
if it.isAlive():
return it.result
else:
return it.result
Je préfère généralement utiliser un gestionnaire de contexte comme suggéré par @ josh-lee
Mais au cas où quelqu'un souhaiterait que cela soit implémenté comme décorateur, voici une alternative.
Voici à quoi cela ressemblerait:
import time
from timeout import timeout
class Test(object):
@timeout(2)
def test_a(self, foo, bar):
print foo
time.sleep(1)
print bar
return 'A Done'
@timeout(2)
def test_b(self, foo, bar):
print foo
time.sleep(3)
print bar
return 'B Done'
t = Test()
print t.test_a('python', 'rocks')
print t.test_b('timing', 'out')
Et c'est le timeout.py
module:
import threading
class TimeoutError(Exception):
pass
class InterruptableThread(threading.Thread):
def __init__(self, func, *args, **kwargs):
threading.Thread.__init__(self)
self._func = func
self._args = args
self._kwargs = kwargs
self._result = None
def run(self):
self._result = self._func(*self._args, **self._kwargs)
@property
def result(self):
return self._result
class timeout(object):
def __init__(self, sec):
self._sec = sec
def __call__(self, f):
def wrapped_f(*args, **kwargs):
it = InterruptableThread(f, *args, **kwargs)
it.start()
it.join(self._sec)
if not it.is_alive():
return it.result
raise TimeoutError('execution expired')
return wrapped_f
Le résultat:
python
rocks
A Done
timing
Traceback (most recent call last):
...
timeout.TimeoutError: execution expired
out
Notez que même si le TimeoutError
est levé, la méthode décorée continuera à s'exécuter dans un thread différent. Si vous souhaitez également que ce thread soit "arrêté", voyez: Existe-t-il un moyen de tuer un thread en Python?