1""" 2Timeout decorator. 3 4 :copyright: (c) 2012-2013 by PN. 5 :license: MIT, see LICENSE for more details. 6""" 7 8from __future__ import print_function 9from __future__ import unicode_literals 10from __future__ import division 11 12import sys 13import time 14import multiprocessing 15import signal 16from functools import wraps 17 18############################################################ 19# Timeout 20############################################################ 21 22# http://www.saltycrane.com/blog/2010/04/using-python-timeout-decorator-uploading-s3/ 23# Used work of Stephen "Zero" Chappell <Noctis.Skytower@gmail.com> 24# in https://code.google.com/p/verse-quiz/source/browse/trunk/timeout.py 25 26 27class TimeoutError(AssertionError): 28 29 """Thrown when a timeout occurs in the `timeout` context manager.""" 30 31 def __init__(self, value="Timed Out"): 32 self.value = value 33 34 def __str__(self): 35 return repr(self.value) 36 37 38def _raise_exception(exception, exception_message): 39 """ This function checks if a exception message is given. 40 41 If there is no exception message, the default behaviour is maintained. 42 If there is an exception message, the message is passed to the exception with the 'value' keyword. 43 """ 44 if exception_message is None: 45 raise exception() 46 else: 47 raise exception(exception_message) 48 49 50def timeout(seconds=None, use_signals=True, timeout_exception=TimeoutError, exception_message=None): 51 """Add a timeout parameter to a function and return it. 52 53 :param seconds: optional time limit in seconds or fractions of a second. If None is passed, no timeout is applied. 54 This adds some flexibility to the usage: you can disable timing out depending on the settings. 55 :type seconds: float 56 :param use_signals: flag indicating whether signals should be used for timing function out or the multiprocessing 57 When using multiprocessing, timeout granularity is limited to 10ths of a second. 58 :type use_signals: bool 59 60 :raises: TimeoutError if time limit is reached 61 62 It is illegal to pass anything other than a function as the first 63 parameter. The function is wrapped and returned to the caller. 64 """ 65 def decorate(function): 66 67 if use_signals: 68 def handler(signum, frame): 69 _raise_exception(timeout_exception, exception_message) 70 71 @wraps(function) 72 def new_function(*args, **kwargs): 73 new_seconds = kwargs.pop('timeout', seconds) 74 if new_seconds: 75 old = signal.signal(signal.SIGALRM, handler) 76 signal.setitimer(signal.ITIMER_REAL, new_seconds) 77 78 if not seconds: 79 return function(*args, **kwargs) 80 81 try: 82 return function(*args, **kwargs) 83 finally: 84 if new_seconds: 85 signal.setitimer(signal.ITIMER_REAL, 0) 86 signal.signal(signal.SIGALRM, old) 87 return new_function 88 else: 89 @wraps(function) 90 def new_function(*args, **kwargs): 91 timeout_wrapper = _Timeout(function, timeout_exception, exception_message, seconds) 92 return timeout_wrapper(*args, **kwargs) 93 return new_function 94 95 return decorate 96 97 98def _target(queue, function, *args, **kwargs): 99 """Run a function with arguments and return output via a queue. 100 101 This is a helper function for the Process created in _Timeout. It runs 102 the function with positional arguments and keyword arguments and then 103 returns the function's output by way of a queue. If an exception gets 104 raised, it is returned to _Timeout to be raised by the value property. 105 """ 106 try: 107 queue.put((True, function(*args, **kwargs))) 108 except: 109 queue.put((False, sys.exc_info()[1])) 110 111 112class _Timeout(object): 113 114 """Wrap a function and add a timeout (limit) attribute to it. 115 116 Instances of this class are automatically generated by the add_timeout 117 function defined above. Wrapping a function allows asynchronous calls 118 to be made and termination of execution after a timeout has passed. 119 """ 120 121 def __init__(self, function, timeout_exception, exception_message, limit): 122 """Initialize instance in preparation for being called.""" 123 self.__limit = limit 124 self.__function = function 125 self.__timeout_exception = timeout_exception 126 self.__exception_message = exception_message 127 self.__name__ = function.__name__ 128 self.__doc__ = function.__doc__ 129 self.__timeout = time.time() 130 self.__process = multiprocessing.Process() 131 self.__queue = multiprocessing.Queue() 132 133 def __call__(self, *args, **kwargs): 134 """Execute the embedded function object asynchronously. 135 136 The function given to the constructor is transparently called and 137 requires that "ready" be intermittently polled. If and when it is 138 True, the "value" property may then be checked for returned data. 139 """ 140 self.__limit = kwargs.pop('timeout', self.__limit) 141 self.__queue = multiprocessing.Queue(1) 142 args = (self.__queue, self.__function) + args 143 self.__process = multiprocessing.Process(target=_target, 144 args=args, 145 kwargs=kwargs) 146 self.__process.daemon = True 147 self.__process.start() 148 if self.__limit is not None: 149 self.__timeout = self.__limit + time.time() 150 while not self.ready: 151 time.sleep(0.01) 152 return self.value 153 154 def cancel(self): 155 """Terminate any possible execution of the embedded function.""" 156 if self.__process.is_alive(): 157 self.__process.terminate() 158 159 _raise_exception(self.__timeout_exception, self.__exception_message) 160 161 @property 162 def ready(self): 163 """Read-only property indicating status of "value" property.""" 164 if self.__limit and self.__timeout < time.time(): 165 self.cancel() 166 return self.__queue.full() and not self.__queue.empty() 167 168 @property 169 def value(self): 170 """Read-only property containing data returned from function.""" 171 if self.ready is True: 172 flag, load = self.__queue.get() 173 if flag: 174 return load 175 raise load 176