5

I am trying to limit the time a function is allowed to run in python (flask). It would look something like this:

def my_function:
    try:
        long_function_time()
    catch TimeOutException:
        return "Function took too long to execute"

def long_function_time:
    #stuff here

I have initially tried using signals but been told thats not a good approach since flask runs in a threaded environment. I want the max execution time to be flexible, so that I easily can change it.

The code I currently use (which sometimes does not work, don't know why):

class TimedOutExc(Exception):
  pass

def deadline(timeout, *args):
  def decorate(f):
    def handler(signum, frame):
      signal.alarm(0)
      raise TimedOutExc()

    def new_f(*args):

      signal.signal(signal.SIGALRM, handler)
      signal.alarm(timeout)
      return f(*args)

    new_f.__name__ = f.__name__
    return new_f
  return decorate

Thanks in advance!

Firas Dib
  • 2,743
  • 19
  • 38
  • There are a number of different solutions for interrupting a long Python function in [this question](http://stackoverflow.com/questions/366682/how-to-limit-execution-time-of-a-function-call-in-python). – Miguel Grinberg Sep 02 '13 at 22:00
  • @Miguel: Most of them seem to use Signals though? – Firas Dib Sep 02 '13 at 22:06
  • 2
    You may find [this blog post](http://eli.thegreenplace.net/2011/08/22/how-not-to-set-a-timeout-on-a-computation-in-python/) and the various comments interesting. Basically, what you're trying to do is not an easy problem to solve. You may be better off taking your long-running code and breaking it down into smaller chunks (maybe make the long-running function a generator function). Then, test timeout after each chunk of processing. Alternatively, reevaluate what you're actually trying to do; perhaps this long-running process should be done using an asynchronous task queue library like Celery. – Mark Hildreth Sep 05 '13 at 17:57
  • Thats not possible in my case @Mark. Can you not fork the thread and then kill it after x seconds? If I knew enough python I could try it myself. – Firas Dib Sep 10 '13 at 10:45
  • The typical rule of thumb is "never kill a thread from outside of itself". It sounds good in theory, but in practice, it ends up being more trouble than it's worth (resources are not properly allocated). – Mark Hildreth Sep 10 '13 at 14:57
  • Sorry, that should read "resources are not properly _deallocated_". – Mark Hildreth Sep 10 '13 at 15:06
  • Take a look at this question http://stackoverflow.com/questions/492519/timeout-on-a-python-function-call – dmitry Sep 11 '13 at 15:06

2 Answers2

7

This works with CPython 2 (tested with 2.7), and obviously doesn't use signals, but the CPython interpreter loop. Thus, you still have to handle non-Python blocks in native I/O calls and similar (for example, you must set socket.timeout). Also, it has a possibly hefty runtime penalty (you could make it check the timeout every *n*th call to alleviate that a little).

Still, helps for a certain class of problems (e.g. computations). If you want it to co-exist with a profiler, you'll have to invest some work.

import sys
import time

class WatchdogTimeoutError(RuntimeError):
    """Raised in case of runtime limit violations."""

def sleeper(tick):
    """Endless loop."""
    while True:
        time.sleep(tick)

def watchdog(timeout, code, *args, **kwargs):
    "Time-limited execution."
    def tracer(frame, event, arg, start=time.time()):
        "Helper."
        now = time.time()
        if now > start + timeout:
            raise WatchdogTimeoutError(start, now)
        return tracer if event == "call" else None

    old_tracer = sys.gettrace()
    try:
        sys.settrace(tracer)
        code(*args, **kwargs)
    finally:
        sys.settrace(old_tracer)

def demo():
    """Show timeout executor."""
    try:
        watchdog(5, sleeper, 0.1)
    except WatchdogTimeoutError, exc:
        start, abort = exc.args
        print "Aborted after %.3f secs" % (abort - start,)
    else:
        print "Ended"

if __name__ == "__main__":
    demo()
ThiefMaster
  • 310,957
  • 84
  • 592
  • 636
jhermann
  • 2,071
  • 13
  • 17
5

Try to use threads:

from multiprocessing import Pool, TimeoutError
from time import sleep

class TimedOutExc(Exception):
  pass

def f(x):
    sleep(2)
    return x*x

pool = Pool(processes=1)
result = pool.apply_async(f, (1,))
try:
    print(result.get(timeout=1))
except TimeoutError:
    print 'timeout'
    raise TimedOutExc()
zie1ony
  • 1,190
  • 2
  • 14
  • 33