How to limit execution time of a function call in Python

后端 未结 10 1675
遥遥无期
遥遥无期 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 11:11

    Doing this from within a signal handler is dangerous: you might be inside an exception handler at the time the exception is raised, and leave things in a broken state. For example,

    def function_with_enforced_timeout():
      f = open_temporary_file()
      try:
       ...
      finally:
       here()
       unlink(f.filename)
    

    If your exception is raised here(), the temporary file will never be deleted.

    The solution here is for asynchronous exceptions to be postponed until the code is not inside exception-handling code (an except or finally block), but Python doesn't do that.

    Note that this won't interrupt anything while executing native code; it'll only interrupt it when the function returns, so this may not help this particular case. (SIGALRM itself might interrupt the call that's blocking--but socket code typically simply retries after an EINTR.)

    Doing this with threads is a better idea, since it's more portable than signals. Since you're starting a worker thread and blocking until it finishes, there are none of the usual concurrency worries. Unfortunately, there's no way to deliver an exception asynchronously to another thread in Python (other thread APIs can do this). It'll also have the same issue with sending an exception during an exception handler, and require the same fix.

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

    You don't have to use threads. You can use another process to do the blocking work, for instance, maybe using the subprocess module. If you want to share data structures between different parts of your program then Twisted is a great library for giving yourself control of this, and I'd recommend it if you care about blocking and expect to have this trouble a lot. The bad news with Twisted is you have to rewrite your code to avoid any blocking, and there is a fair learning curve.

    You can use threads to avoid blocking, but I'd regard this as a last resort, since it exposes you to a whole world of pain. Read a good book on concurrency before even thinking about using threads in production, e.g. Jean Bacon's "Concurrent Systems". I work with a bunch of people who do really cool high performance stuff with threads, and we don't introduce threads into projects unless we really need them.

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

    The method from @user2283347 is tested working, but we want to get rid of the traceback messages. Use pass trick from Remove traceback in Python on Ctrl-C, the modified code is:

    from contextlib import contextmanager
    import threading
    import _thread
    
    class TimeoutException(Exception): pass
    
    @contextmanager
    def time_limit(seconds):
        timer = threading.Timer(seconds, lambda: _thread.interrupt_main())
        timer.start()
        try:
            yield
        except KeyboardInterrupt:
            pass     
        finally:
            # if the action ends in specified time, timer is canceled
            timer.cancel()
    
    def timeout_svm_score(i):
         #from sklearn import svm
         #import numpy as np
         #from IPython.core.display import display
         #%store -r names X Y
         clf = svm.SVC(kernel='linear', C=1).fit(np.nan_to_num(X[[names[i]]]), Y)
         score = clf.score(np.nan_to_num(X[[names[i]]]),Y)
         #scoressvm.append((score, names[i]))
         display((score, names[i])) 
         
    %%time
    with time_limit(5):
        i=0
        timeout_svm_score(i)
    #Wall time: 14.2 s
    
    %%time
    with time_limit(20):
        i=0
        timeout_svm_score(i)
    #(0.04541284403669725, '计划飞行时间')
    #Wall time: 16.1 s
    
    %%time
    with time_limit(5):
        i=14
        timeout_svm_score(i)
    #Wall time: 5h 43min 41s
    

    We can see that this method may need far long time to interrupt the calculation, we asked for 5 seconds, but it work out in 5 hours.

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

    I would usually prefer using a contextmanager as suggested by @josh-lee

    But in case someone is interested in having this implemented as a decorator, here's an alternative.

    Here's how it would look like:

    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')
    

    And this is the 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
    

    The output:

    python
    rocks
    A Done
    timing
    Traceback (most recent call last):
      ...
    timeout.TimeoutError: execution expired
    out
    

    Notice that even if the TimeoutError is thrown, the decorated method will continue to run in a different thread. If you would also want this thread to be "stopped" see: Is there any way to kill a Thread in Python?

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