From ecef73454785bc7060a8dfa9abd2cf6d6771cb8b Mon Sep 17 00:00:00 2001 From: Tim Savannah Date: Fri, 18 Mar 2016 14:03:48 -0400 Subject: [PATCH] Add missing files --- func_timeout/StoppableThread.py | 35 +++++++++++++++++++ func_timeout/dafunc.py | 60 +++++++++++++++++++++++++++++++++ func_timeout/exceptions.py | 4 +++ 3 files changed, 99 insertions(+) create mode 100644 func_timeout/StoppableThread.py create mode 100644 func_timeout/dafunc.py create mode 100644 func_timeout/exceptions.py diff --git a/func_timeout/StoppableThread.py b/func_timeout/StoppableThread.py new file mode 100644 index 0000000..860ce7d --- /dev/null +++ b/func_timeout/StoppableThread.py @@ -0,0 +1,35 @@ + +import ctypes +import threading +import time + +class StoppableThread(threading.Thread): + ''' + StoppableThread - A thread that can be stopped by forcing an exception in the execution context. + ''' + + + def _stopThread(self, exception): + if self.isAlive() is False: + return True + + joinThread = JoinThread(self, exception) + joinThread.start() + +class JoinThread(threading.Thread): + ''' + JoinThread - The workhouse that stops the StoppableThread + ''' + + def __init__(self, otherThread, exception): + threading.Thread.__init__(self) + self.otherThread = otherThread + self.exception = exception + self.daemon = True + + def run(self): + while self.otherThread.isAlive(): + # We loop raising exception incase it's caught hopefully this breaks us far out. + ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(self.otherThread.ident), ctypes.py_object(self.exception)) + self.otherThread.join(2) + diff --git a/func_timeout/dafunc.py b/func_timeout/dafunc.py new file mode 100644 index 0000000..f603caa --- /dev/null +++ b/func_timeout/dafunc.py @@ -0,0 +1,60 @@ +import threading +import time + +from .exceptions import FunctionTimedOut +from .StoppableThread import StoppableThread + +def func_timeout(timeout, func, args=(), kwargs=None): + ''' + func_timeout - Runs the given function for up to #timeout# seconds. + + Raises any exceptions #func# would raise, returns what #func# would return (unless timeout is exceeded), in which case it raises FunctionTimedOut + + @param timeout - Maximum number of seconds to run #func# before terminating + @param func - The function to call + @param args - Any ordered arguments to pass to the function + @param kwargs - Keyword arguments to pass to the function. + + @raises - FunctionTimedOut if #timeout# is exceeded, otherwise anything #func# could raise will be raised + + @return - The return value that #func# gives + ''' + + if not kwargs: + kwargs = {} + if not args: + args = () + + ret = [] + exception = [] + isStopped = False + + def funcwrap(args2, kwargs2): + try: + ret.append( func(*args2, **kwargs2) ) + except Exception as e: + if isStopped is False: + # Don't capture stopping exception + exception.append(e) + + thread = StoppableThread(target=funcwrap, args=(args, kwargs)) + thread.daemon = True + + thread.start() + thread.join(timeout) + + stopException = None + if thread.isAlive(): + isStopped = True + stopException = FunctionTimedOut('Function %s (args=%s) (kwargs=%s) timed out after %f seconds.\n' %(func.__name__, str(args), str(kwargs), timeout)) + thread._stopThread(stopException) + thread.join(.1) + raise stopException + + if exception: + raise exception[0] + + if ret: + return ret[0] + + diff --git a/func_timeout/exceptions.py b/func_timeout/exceptions.py new file mode 100644 index 0000000..680ffdb --- /dev/null +++ b/func_timeout/exceptions.py @@ -0,0 +1,4 @@ + + +class FunctionTimedOut(Exception): + pass