codekingpro/portable-devtools
114k
1# Copyright (c) 2009-2010 Denis Bilenko, denis.bilenko at gmail com2# Copyright (c) 2010 Eventlet Contributors (see AUTHORS)3# and licensed under the MIT license:4#5# Permission is hereby granted, free of charge, to any person obtaining a copy6# of this software and associated documentation files (the "Software"), to deal7# in the Software without restriction, including without limitation the rights8# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell9# copies of the Software, and to permit persons to whom the Software is10# furnished to do so, subject to the following conditions:11#12# The above copyright notice and this permission notice shall be included in13# all copies or substantial portions of the Software.14#15# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR16# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,17# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE18# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER19# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,20# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN21# THE SOFTWARE.22 23import functools24import inspect25 26import eventlet27from eventlet.support import greenlets as greenlet28from eventlet.hubs import get_hub29 30__all__ = ['Timeout', 'with_timeout', 'wrap_is_timeout', 'is_timeout']31 32_MISSING = object()33 34# deriving from BaseException so that "except Exception as e" doesn't catch35# Timeout exceptions.36 37 38class Timeout(BaseException):39 """Raises *exception* in the current greenthread after *timeout* seconds.40 41 When *exception* is omitted or ``None``, the :class:`Timeout` instance42 itself is raised. If *seconds* is None, the timer is not scheduled, and is43 only useful if you're planning to raise it directly.44 45 Timeout objects are context managers, and so can be used in with statements.46 When used in a with statement, if *exception* is ``False``, the timeout is47 still raised, but the context manager suppresses it, so the code outside the48 with-block won't see it.49 """50 51 def __init__(self, seconds=None, exception=None):52 self.seconds = seconds53 self.exception = exception54 self.timer = None55 self.start()56 57 def start(self):58 """Schedule the timeout. This is called on construction, so59 it should not be called explicitly, unless the timer has been60 canceled."""61 assert not self.pending, \62 '%r is already started; to restart it, cancel it first' % self63 if self.seconds is None: # "fake" timeout (never expires)64 self.timer = None65 elif self.exception is None or isinstance(self.exception, bool): # timeout that raises self66 self.timer = get_hub().schedule_call_global(67 self.seconds, greenlet.getcurrent().throw, self)68 else: # regular timeout with user-provided exception69 self.timer = get_hub().schedule_call_global(70 self.seconds, greenlet.getcurrent().throw, self.exception)71 return self72 73 @property74 def pending(self):75 """True if the timeout is scheduled to be raised."""76 if self.timer is not None:77 return self.timer.pending78 else:79 return False80 81 def cancel(self):82 """If the timeout is pending, cancel it. If not using83 Timeouts in ``with`` statements, always call cancel() in a84 ``finally`` after the block of code that is getting timed out.85 If not canceled, the timeout will be raised later on, in some86 unexpected section of the application."""87 if self.timer is not None:88 self.timer.cancel()89 self.timer = None90 91 def __repr__(self):92 classname = self.__class__.__name__93 if self.pending:94 pending = ' pending'95 else:96 pending = ''97 if self.exception is None:98 exception = ''99 else:100 exception = ' exception=%r' % self.exception101 return '<%s at %s seconds=%s%s%s>' % (102 classname, hex(id(self)), self.seconds, exception, pending)103 104 def __str__(self):105 """106 >>> raise Timeout # doctest: +IGNORE_EXCEPTION_DETAIL107 Traceback (most recent call last):108 ...109 Timeout110 """111 if self.seconds is None:112 return ''113 if self.seconds == 1:114 suffix = ''115 else:116 suffix = 's'117 if self.exception is None or self.exception is True:118 return '%s second%s' % (self.seconds, suffix)119 elif self.exception is False:120 return '%s second%s (silent)' % (self.seconds, suffix)121 else:122 return '%s second%s (%s)' % (self.seconds, suffix, self.exception)123 124 def __enter__(self):125 if self.timer is None:126 self.start()127 return self128 129 def __exit__(self, typ, value, tb):130 self.cancel()131 if value is self and self.exception is False:132 return True133 134 @property135 def is_timeout(self):136 return True137 138 139def with_timeout(seconds, function, *args, **kwds):140 """Wrap a call to some (yielding) function with a timeout; if the called141 function fails to return before the timeout, cancel it and return a flag142 value.143 """144 timeout_value = kwds.pop("timeout_value", _MISSING)145 timeout = Timeout(seconds)146 try:147 try:148 return function(*args, **kwds)149 except Timeout as ex:150 if ex is timeout and timeout_value is not _MISSING:151 return timeout_value152 raise153 finally:154 timeout.cancel()155 156 157def wrap_is_timeout(base):158 '''Adds `.is_timeout=True` attribute to objects returned by `base()`.159 160 When `base` is class, attribute is added as read-only property. Returns `base`.161 Otherwise, it returns a function that sets attribute on result of `base()` call.162 163 Wrappers make best effort to be transparent.164 '''165 if inspect.isclass(base):166 base.is_timeout = property(lambda _: True)167 return base168 169 @functools.wraps(base)170 def fun(*args, **kwargs):171 ex = base(*args, **kwargs)172 ex.is_timeout = True173 return ex174 return fun175 176 177if isinstance(__builtins__, dict): # seen when running tests on py310, but HOW??178 _timeout_err = __builtins__.get('TimeoutError', Timeout)179else:180 _timeout_err = getattr(__builtins__, 'TimeoutError', Timeout)181 182 183def is_timeout(obj):184 return bool(getattr(obj, 'is_timeout', False)) or isinstance(obj, _timeout_err)185 