Skip to content

Instantly share code, notes, and snippets.

@joe-sullivan
Last active May 3, 2019 20:45
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save joe-sullivan/c43a12e80e7f4e5a04752981ebc7007c to your computer and use it in GitHub Desktop.
Save joe-sullivan/c43a12e80e7f4e5a04752981ebc7007c to your computer and use it in GitHub Desktop.
Simple timeout decorator for python
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)
@rhlobo
Copy link

rhlobo commented Jun 13, 2018

Good work, this isn't a crossplatform solutions, as signal is does not work in Windows.

@bitranox
Copy link

bitranox commented May 3, 2019

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