87 votes

Comment limiter le temps d'exécution d'un appel de fonction ?

Il y a un appel de fonction lié à la socket dans mon code, cette fonction provient d'un autre module et n'est donc pas sous mon contrôle, le problème est qu'elle bloque pendant des heures de temps en temps, ce qui est totalement inacceptable, comment puis-je limiter le temps d'exécution de la fonction depuis mon code ? Je suppose que la solution doit utiliser un autre thread.

0 votes

Duplicata possible de Timeout sur un appel de fonction

125voto

Josh Lee Points 53741

Une amélioration de la réponse de @rik.the.vik serait d'utiliser l'option with déclaration pour donner à la fonction timeout un peu de 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!")

2 votes

try: yield \n finally: signal.alarm(0)

0 votes

Pour être ultra pointilleux, le signal.alarm(seconds) a-t-il sa place dans le try ?

0 votes

Eh bien, il n'est pas documenté comme soulevant des exceptions, donc probablement pas.

49voto

rik.the.vik Points 604

Je ne suis pas sûr de la compatibilité de ce système avec d'autres plates-formes, mais l'utilisation de signaux et d'alarmes pourrait être une bonne façon de voir les choses. Avec un peu de travail, vous pourriez également rendre ce système complètement générique et l'utiliser dans n'importe quelle situation.

http://docs.python.org/library/signal.html

Votre code va donc ressembler à quelque chose comme 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!"

0 votes

Et si j'utilise l'alarme pour autre chose, ailleurs ? :-)

2 votes

Oui, ça vaut probablement la peine de choisir l'un ou l'autre. Je suis moi-même un gars du processus/signal. Après avoir regardé blip.tv/file/2232410 Je fais de moins en moins confiance au modèle de fil de Python.

10 votes

De plus, cela ne désactive pas l'alarme après coup.

7voto

Glenn Maynard Points 24451

Faire cela à partir d'un gestionnaire de signaux est dangereux : vous pourriez vous trouver dans un gestionnaire d'exception 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 serait que les exceptions asynchrones soient reportées jusqu'à ce que le code ne soit pas à l'intérieur d'un code de gestion des exceptions (un bloc except ou finally), mais Python ne le fait pas.

Notez que cela n'interrompra rien pendant l'exécution du code natif ; cela ne l'interrompra qu'au retour de la fonction, donc cela peut ne pas aider ce cas particulier. (SIGALRM lui-même pourrait interrompre l'appel qui se bloque - mais le code socket se contente généralement de réessayer après un EINTR).

Il est préférable de le faire avec des threads, car c'est plus portable que les signaux. Puisque vous démarrez un fil de travail et bloquez jusqu'à ce qu'il se termine, il n'y a aucun des problèmes habituels de concurrence. Malheureusement, il n'existe aucun moyen de transmettre une exception de manière asynchrone à un autre thread en Python (d'autres API de threads peuvent le faire). Il y aura également le même problème avec l'envoi d'une exception pendant un gestionnaire d'exception, et nécessitera la même correction.

0 votes

Le code natif peut appeler PyErr_CheckSignals() avant de redémarrer sur EINTR, pour permettre à un gestionnaire de signaux Python de s'exécuter.

5voto

Lasse V. Karlsen Points 148037

La seule façon "sûre" de le faire, dans n'importe quel langage, est d'utiliser un processus secondaire pour faire ce "timeout", sinon vous devez construire votre code de telle sorte qu'il se termine de lui-même en toute sécurité, par exemple en vérifiant le temps écoulé dans une boucle ou similaire. Si la modification de la méthode n'est pas envisageable, un thread ne suffira pas.

Pourquoi ? Parce que vous risquez de laisser les choses dans un mauvais état quand vous le faites. Si le thread est simplement tué au milieu de la méthode, les verrous retenus, etc. seront simplement retenus, et ne pourront pas être libérés.

Alors regardez le processus, faites pas Regardez le fil de discussion.

5voto

Dickon Reed Points 1835

Vous n'avez pas besoin d'utiliser des fils. Vous pouvez utiliser un autre processus pour effectuer le travail de blocage, par exemple, en utilisant la fonction sous-processus module. Si vous souhaitez partager des structures de données entre différentes parties de votre programme, alors Torsadé est une excellente bibliothèque pour vous permettre de contrôler cela, et je la recommande si vous vous souciez du blocage et si vous vous attendez à avoir souvent ce problème. La mauvaise nouvelle avec Twisted est que vous devez réécrire votre code pour éviter tout blocage, et il y a une bonne courbe d'apprentissage.

Vous peut utiliser des threads pour éviter le blocage, mais je considère 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 les threads en production, par exemple "Concurrent Systems" de Jean Bacon. Je travaille avec un groupe de personnes qui font des choses très cool et très performantes avec les threads, et nous n'introduisons pas les threads dans les projets à moins que nous n'en ayons vraiment besoin.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X