Last active
May 3, 2019 20:45
-
-
Save joe-sullivan/c43a12e80e7f4e5a04752981ebc7007c to your computer and use it in GitHub Desktop.
Simple timeout decorator for python
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import signal | |
from functools import wraps | |
__all__ = ['timeout', 'TimeoutError'] | |
class TimeoutError(Exception): pass | |
# IMPORTANT: this is not thread-safe | |
def timeout(seconds, error_message='Function call timed out'): | |
def _handle_timeout(signum, frame): | |
raise TimeoutError(error_message) | |
def decorated(func): | |
@wraps(func) | |
def wrapper(*args, **kwargs): | |
signal.signal(signal.SIGALRM, _handle_timeout) | |
signal.alarm(seconds) | |
try: | |
result = func(*args, **kwargs) | |
finally: | |
signal.alarm(0) | |
return result | |
return wrapper | |
return decorated | |
if __name__ == '__main__': | |
# sample usage | |
import time | |
@timeout(1) | |
def wait(delay): | |
time.sleep(delay) | |
print('done: ' + str(delay)) | |
wait(0) # pass | |
try: | |
wait(5) # fail | |
except TimeoutError as e: | |
print(e) | |
Caveats using Signals
as ABADGER1999 points out in his blog https://anonbadger.wordpress.com/2018/12/15/python-signal-handlers-and-exceptions/
using signals and the TimeoutException is probably not the best idea - because it can be catched in the decorated function.
Of course You can use Your own Exception, derived from the Base Exception Class, but the code might still not work as expected -
see the next example - You may try it out in jupyter <https://mybinder.org/v2/gh/bitranox/wrapt_timeout_decorator/master?filepath=jupyter_test_wrapt_timeout_decorator.ipynb>
_:
You might try that decorator : https://github.com/bitranox/wrapt_timeout_decorator
.. code-block:: py
import time
from wrapt_timeout_decorator import *
# caveats when using signals - the TimeoutError raised by the signal may be catched
# inside the decorated function.
# So You might use Your own Exception, derived from the base Exception Class.
# In Python-3.7.1 stdlib there are over 300 pieces of code that will catch your timeout
# if you were to base an exception on Exception. If you base your exception on BaseException,
# there are still 231 places that can potentially catch your exception.
# You should use use_signals=False if You want to make sure that the timeout is handled correctly !
# therefore the default value for use_signals = False on this decorator !
@timeout(5, use_signals=True)
def mytest(message):
try:
print(message)
for i in range(1,10):
time.sleep(1)
print('{} seconds have passed - lets assume we read a big file here'.format(i))
# TimeoutError is a Subclass of OSError - therefore it is catched here !
except OSError:
for i in range(1,10):
time.sleep(1)
print('Whats going on here ? - Ooops the Timeout Exception is catched by the OSError ! {}'.format(i))
except Exception:
# even worse !
pass
except:
# the worst - and exists more then 300x in actual Python 3.7 stdlib Code !
# so You never really can rely that You catch the TimeoutError when using Signals !
pass
if __name__ == '__main__':
try:
mytest('starting')
print('no Timeout Occured')
except TimeoutError():
# this will never be printed because the decorated function catches implicitly the TimeoutError !
print('Timeout Occured')
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Good work, this isn't a crossplatform solutions, as signal is does not work in Windows.