Timeout on a function call

后端 未结 18 1300
挽巷
挽巷 2020-11-21 04:53

I\'m calling a function in Python which I know may stall and force me to restart the script.

How do I call the function or what do I wrap it in so that if it takes

相关标签:
18条回答
  • 2020-11-21 05:03

    I have a different proposal which is a pure function (with the same API as the threading suggestion) and seems to work fine (based on suggestions on this thread)

    def timeout(func, args=(), kwargs={}, timeout_duration=1, default=None):
        import signal
    
        class TimeoutError(Exception):
            pass
    
        def handler(signum, frame):
            raise TimeoutError()
    
        # set the timeout handler
        signal.signal(signal.SIGALRM, handler) 
        signal.alarm(timeout_duration)
        try:
            result = func(*args, **kwargs)
        except TimeoutError as exc:
            result = default
        finally:
            signal.alarm(0)
    
        return result
    
    0 讨论(0)
  • 2020-11-21 05:04

    timeout-decorator don't work on windows system as , windows didn't support signal well.

    If you use timeout-decorator in windows system you will get the following

    AttributeError: module 'signal' has no attribute 'SIGALRM'
    

    Some suggested to use use_signals=False but didn't worked for me.

    Author @bitranox created the following package:

    pip install https://github.com/bitranox/wrapt-timeout-decorator/archive/master.zip
    

    Code Sample:

    import time
    from wrapt_timeout_decorator import *
    
    @timeout(5)
    def mytest(message):
        print(message)
        for i in range(1,10):
            time.sleep(1)
            print('{} seconds have passed'.format(i))
    
    def main():
        mytest('starting')
    
    
    if __name__ == '__main__':
        main()
    

    Gives the following exception:

    TimeoutError: Function mytest timed out after 5 seconds
    
    0 讨论(0)
  • 2020-11-21 05:04

    Another solution with asyncio :

    import asyncio
    import functools
    import multiprocessing
    from concurrent.futures.thread import ThreadPoolExecutor
    
    
    class SingletonTimeOut:
        pool = None
    
        @classmethod
        def run(cls, to_run: functools.partial, timeout: float):
            pool = cls.get_pool()
            loop = cls.get_loop()
            try:
                task = loop.run_in_executor(pool, to_run)
                return loop.run_until_complete(asyncio.wait_for(task, timeout=timeout))
            except asyncio.TimeoutError as e:
                error_type = type(e).__name__ #TODO
                raise e
    
        @classmethod
        def get_pool(cls):
            if cls.pool is None:
                cls.pool = ThreadPoolExecutor(multiprocessing.cpu_count())
            return cls.pool
    
        @classmethod
        def get_loop(cls):
            try:
                return asyncio.get_event_loop()
            except RuntimeError:
                asyncio.set_event_loop(asyncio.new_event_loop())
                # print("NEW LOOP" + str(threading.current_thread().ident))
                return asyncio.get_event_loop()
    
    # ---------------
    
    TIME_OUT = float('0.2')  # seconds
    
    def toto(input_items,nb_predictions):
        return 1
    
    to_run = functools.partial(toto,
                               input_items=1,
                               nb_predictions="a")
    
    results = SingletonTimeOut.run(to_run, TIME_OUT)
    
    
    0 讨论(0)
  • 2020-11-21 05:05

    Building on and and enhancing the answer by @piro , you can build a contextmanager. This allows for very readable code which will disable the alaram signal after a successful run (sets signal.alarm(0))

    @contextmanager
    def timeout(duration):
        def timeout_handler(signum, frame):
            raise Exception(f'block timedout after {duration} seconds')
        signal.signal(signal.SIGALRM, timeout_handler)
        signal.alarm(duration)
        yield
        signal.alarm(0)
    
    def sleeper(duration):
        time.sleep(duration)
        print('finished')
    

    Example usage:

    In [19]: with timeout(2):
        ...:     sleeper(1)
        ...:     
    finished
    
    In [20]: with timeout(2):
        ...:     sleeper(3)
        ...:         
    ---------------------------------------------------------------------------
    Exception                                 Traceback (most recent call last)
    <ipython-input-20-66c78858116f> in <module>()
          1 with timeout(2):
    ----> 2     sleeper(3)
          3 
    
    <ipython-input-7-a75b966bf7ac> in sleeper(t)
          1 def sleeper(t):
    ----> 2     time.sleep(t)
          3     print('finished')
          4 
    
    <ipython-input-18-533b9e684466> in timeout_handler(signum, frame)
          2 def timeout(duration):
          3     def timeout_handler(signum, frame):
    ----> 4         raise Exception(f'block timedout after {duration} seconds')
          5     signal.signal(signal.SIGALRM, timeout_handler)
          6     signal.alarm(duration)
    
    Exception: block timedout after 2 seconds
    
    0 讨论(0)
  • 2020-11-21 05:07
    #!/usr/bin/python2
    import sys, subprocess, threading
    proc = subprocess.Popen(sys.argv[2:])
    timer = threading.Timer(float(sys.argv[1]), proc.terminate)
    timer.start()
    proc.wait()
    timer.cancel()
    exit(proc.returncode)
    
    0 讨论(0)
  • 2020-11-21 05:08

    You may use the signal package if you are running on UNIX:

    In [1]: import signal
    
    # Register an handler for the timeout
    In [2]: def handler(signum, frame):
       ...:     print("Forever is over!")
       ...:     raise Exception("end of time")
       ...: 
    
    # This function *may* run for an indetermined time...
    In [3]: def loop_forever():
       ...:     import time
       ...:     while 1:
       ...:         print("sec")
       ...:         time.sleep(1)
       ...:         
       ...:         
    
    # Register the signal function handler
    In [4]: signal.signal(signal.SIGALRM, handler)
    Out[4]: 0
    
    # Define a timeout for your function
    In [5]: signal.alarm(10)
    Out[5]: 0
    
    In [6]: try:
       ...:     loop_forever()
       ...: except Exception, exc: 
       ...:     print(exc)
       ....: 
    sec
    sec
    sec
    sec
    sec
    sec
    sec
    sec
    Forever is over!
    end of time
    
    # Cancel the timer if the function returned before timeout
    # (ok, mine won't but yours maybe will :)
    In [7]: signal.alarm(0)
    Out[7]: 0
    

    10 seconds after the call signal.alarm(10), the handler is called. This raises an exception that you can intercept from the regular Python code.

    This module doesn't play well with threads (but then, who does?)

    Note that since we raise an exception when timeout happens, it may end up caught and ignored inside the function, for example of one such function:

    def loop_forever():
        while 1:
            print('sec')
            try:
                time.sleep(10)
            except:
                continue
    
    0 讨论(0)
提交回复
热议问题