How do I write a timeout decorator that can receive a function or a decorated function?

I have the following timeout creation decorator function:

class TimeoutError(Exception): pass


def timeout(seconds, error_message = 'Function call timed out'):
    def decorated(func):
        print "timeout: \t" + func.__name__
        def _handle_timeout(signum, frame):
            raise TimeoutError(error_message)


        def wrapper(*args, **kwargs):
            signal.signal(signal.SIGALRM, _handle_timeout)
            signal.alarm(seconds)
            try:
                print "timeout wrapper: \t" + func.__name__
                result = func(*args, **kwargs)
            finally:
                signal.alarm(0)
            return result

        return functools.wraps(func)(wrapper)

    return decorated

      

And one more decorator:

import inspect

class withHostAndToken(object):

    __name__ = "withHostAndToken"
    __doc__ = "Get the Host and Token for the API call"

    def __init__(self, func):
        print "withHostAndToken: \t" + func.__name__
        self.func = func
        self.HOST = ''
        self.TOKEN = ''

    def __call__(self,*args, **kwds):

        if self.HOST == '':
            self.HOST = "HOST"
        if self.TOKEN == '':
            self.TOKEN = "TOKEN"

        argsspec = inspect.getargspec(self.func)
        function_args = argsspec[0]
        if 'HOST' in function_args:
            if 'TOKEN' in function_args:
                return self.func(self.HOST , self.TOKEN , *args, **kwds)
            else:
                return self.func(self.HOST  , *args, **kwds)
        elif 'TOKEN' in function_args:
            return self.func(self.TOKEN, *args, **kwds)

      

When I try to apply both to a function, I don't get the function code:

@timeout(2)
@withHostAndToken
def testDecorators():
    print __name__
    while True:
        print '.'

testDecorators()

      

output of this:

withHostAndToken: testDecorators
timeout: withHostAndToken
timeout: withHostAndToken

Process ended with exit code 0

+3


source to share


1 answer


Your problem is not there and the decorator chaining works fine.

Here's some sample code to demonstrate it with your decorators:

>>> @timeout(2)
@withHostAndToken
def bar(*args):
    print(*args)
    i = 0;
    while True:
        dummy = sys.stderr.write('.')


>>> bar('foo')
host token foo
....................................................................................................................................................................................................................................................................................................................................................................................................................Traceback (most recent call last):
  File "<pyshell#48>", line 1, in <module>
    bar('foo')
  File "<pyshell#2>", line 10, in wrapper
    result = func(*args, **kwargs)
  File "<pyshell#5>", line 19, in __call__
    return self.func(self.HOST , self.TOKEN , *args, **kwds)
  File "<pyshell#47>", line 7, in bar
    dummy = sys.stderr.write('.')
... message list truncate for brievety ...
  File "<pyshell#2>", line 4, in _handle_timeout
    raise TimeoutError(error_message)
TimeoutError: Function call timed out
>>> 

      

Thus, the function is correctly intercepted after about 2 seconds as expected.




But in your use case, you used time.sleep

inside the innermost function. And in Linux and other Unixes, it sleep

is implemented via ... SIGALRM

!

So here's what's going on:

  • outside decorators ask the alarm to go up after 10 seconds. Interior decorator
  • passes an additional parameter to the function
  • the function is called and calls sleep(20)

    • calling the function sleep

      resets the alarm timeout to 20 seconds!

This is the reason why the function actually lasts 20 seconds instead of 10 ...

0


source







All Articles