How to limit execution time of a function call in Python

后端 未结 10 1674
遥遥无期
遥遥无期 2020-11-22 10:25

There is a socket related function call in my code, that function is from another module thus out of my control, the problem is that it blocks for hours occasionally, which

相关标签:
10条回答
  • 2020-11-22 10:56

    Here's a timeout function I think I found via google and it works for me.

    From: 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   
    
    0 讨论(0)
  • 2020-11-22 10:57

    I'm not sure how cross-platform this might be, but using signals and alarm might be a good way of looking at this. With a little work you could make this completely generic as well and usable in any situation.

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

    So your code is going to look something like this.

    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 讨论(0)
  • 2020-11-22 11:02

    I prefer a context manager approach because it allows the execution of multiple python statements within a with time_limit statement. Because windows system does not have SIGALARM, a more portable and perhaps more straightforward method could be using a 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)
    

    The key technique here is the use of _thread.interrupt_main to interrupt the main thread from the timer thread. One caveat is that the main thread does not always respond to the KeyboardInterrupt raised by the Timer quickly. For example, time.sleep() calls a system function so a KeyboardInterrupt will be handled after the sleep call.

    0 讨论(0)
  • 2020-11-22 11:04

    An improvement on @rik.the.vik's answer would be to use the with statement to give the timeout function some syntactic sugar:

    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!")
    
    0 讨论(0)
  • 2020-11-22 11:08

    Here's a Linux/OSX way to limit a function's running time. This is in case you don't want to use threads, and want your program to wait until the function ends, or the time limit expires.

    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 __name__ == '__main__':
        print run_with_limited_time(f, (1.5, ), {}, 2.5) # True
        print run_with_limited_time(f, (3.5, ), {}, 2.5) # False
    
    0 讨论(0)
  • 2020-11-22 11:08

    The only "safe" way to do this, in any language, is to use a secondary process to do that timeout-thing, otherwise you need to build your code in such a way that it will time out safely by itself, for instance by checking the time elapsed in a loop or similar. If changing the method isn't an option, a thread will not suffice.

    Why? Because you're risking leaving things in a bad state when you do. If the thread is simply killed mid-method, locks being held, etc. will just be held, and cannot be released.

    So look at the process way, do not look at the thread way.

    0 讨论(0)
提交回复
热议问题