diff --git a/func_timeout/StoppableThread.py b/func_timeout/StoppableThread.py index a124f42..35fdab2 100644 --- a/func_timeout/StoppableThread.py +++ b/func_timeout/StoppableThread.py @@ -8,43 +8,83 @@ import os import ctypes import threading -import time -import sys __all__ = ('StoppableThread', 'JoinThread') class StoppableThread(threading.Thread): ''' StoppableThread - A thread that can be stopped by forcing an exception in the execution context. + + This works both to interrupt code that is in C or in python code, at either the next call to a python function, + or the next line in python code. + + It is recommended that if you call stop ( @see StoppableThread.stop ) that you use an exception that inherits BaseException, to ensure it likely isn't caught. + + Also, beware unmarked exception handlers in your code. Code like this: + + while True: + try: + doSomething() + except: + continue + + will never be able to abort, because the exception you raise is immediately caught. + + The exception is raised over and over, with a specifed delay (default 2.0 seconds) ''' - def _stopThread(self, exception): + def _stopThread(self, exception, raiseEvery=2.0): + ''' + _stopThread - @see StoppableThread.stop + ''' if self.isAlive() is False: return True self._stderr = open(os.devnull, 'w') - joinThread = JoinThread(self, exception) + + # Create "joining" thread which will raise the provided exception + # on a repeat, until the thread stops. + joinThread = JoinThread(self, exception, repeatEvery=raiseEvery) + + # Try to prevent spurrious prints joinThread._stderr = self._stderr joinThread.start() joinThread._stderr = self._stderr - def stop(self, exception): + def stop(self, exception, raiseEvery=2.0): ''' Stops the thread by raising a given exception. @param exception - Exception to throw. Likely, you want to use something that inherits from BaseException (so except Exception as e: continue; isn't a problem) ''' - return self._stopThread(exception) + return self._stopThread(exception, raiseEvery) class JoinThread(threading.Thread): ''' - JoinThread - The workhouse that stops the StoppableThread + JoinThread - The workhouse that stops the StoppableThread. + + Takes an exception, and upon being started immediately raises that exception in the current context + of the thread's execution (so next line of python gets it, or next call to a python api function in C code ). + + @see StoppableThread for more details ''' def __init__(self, otherThread, exception, repeatEvery=2.0): + ''' + __init__ - Create a JoinThread (don't forget to call .start() ! ) + + @param otherThread - A thread + + @param exception - An exception. Should be a BaseException, to prevent "catch Exception as e: continue" type code + from never being terminated. If such code is unavoidable, you can try setting #repeatEvery to a very low number, like .00001, + and it will hopefully raise within the context of the catch, and be able to break free. + + @param repeatEvery Default 2.0 - After starting, the given exception is immediately raised. Then, every #repeatEvery seconds, + it is raised again, until the thread terminates. + ''' threading.Thread.__init__(self) self.otherThread = otherThread self.exception = exception @@ -52,6 +92,9 @@ class JoinThread(threading.Thread): self.daemon = True def run(self): + ''' + run - The thread main. Will attempt to stop and join the attached thread. + ''' # Try to silence default exception printing. self.otherThread._Thread__stderr = self._stderr @@ -69,4 +112,4 @@ class JoinThread(threading.Thread): except: pass - +# vim: set ts=4 sw=4 expandtab :