154 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			154 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
# Copyright (c) 2009-2010 Denis Bilenko, denis.bilenko at gmail com
 | 
						|
# Copyright (c) 2010 Eventlet Contributors (see AUTHORS)
 | 
						|
# and licensed under the MIT license:
 | 
						|
# 
 | 
						|
# Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
						|
# of this software and associated documentation files (the "Software"), to deal
 | 
						|
# in the Software without restriction, including without limitation the rights
 | 
						|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
						|
# copies of the Software, and to permit persons to whom the Software is
 | 
						|
# furnished to do so, subject to the following conditions:
 | 
						|
# 
 | 
						|
# The above copyright notice and this permission notice shall be included in
 | 
						|
# all copies or substantial portions of the Software.
 | 
						|
# 
 | 
						|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
						|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
						|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
						|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
						|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
						|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | 
						|
# THE SOFTWARE.from eventlet.support import greenlets as greenlet
 | 
						|
 | 
						|
from eventlet.support import greenlets as greenlet
 | 
						|
from eventlet.hubs import get_hub
 | 
						|
 | 
						|
__all__ = ['Timeout',
 | 
						|
           'with_timeout']
 | 
						|
 | 
						|
_NONE = object()
 | 
						|
 | 
						|
try:
 | 
						|
    BaseException
 | 
						|
except NameError: # Python < 2.5
 | 
						|
    class BaseException:
 | 
						|
        # not subclassing from object() intentionally, because in
 | 
						|
        # that case "raise Timeout" fails with TypeError.
 | 
						|
        pass
 | 
						|
 | 
						|
# deriving from BaseException so that "except Exception, e" doesn't catch
 | 
						|
# Timeout exceptions.
 | 
						|
class Timeout(BaseException):
 | 
						|
    """Raises *exception* in the current greenthread after *timeout* seconds.
 | 
						|
 | 
						|
    When *exception* is omitted or ``None``, the :class:`Timeout` instance 
 | 
						|
    itself is raised. If *seconds* is None, the timer is not scheduled, and is
 | 
						|
    only useful if you're planning to raise it directly.
 | 
						|
    
 | 
						|
    Timeout objects are context managers, and so can be used in with statements.
 | 
						|
    When used in a with statement, if *exception* is ``False``, the timeout is
 | 
						|
    still raised, but the context manager suppresses it, so the code outside the 
 | 
						|
    with-block won't see it.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, seconds=None, exception=None):
 | 
						|
        self.seconds = seconds
 | 
						|
        self.exception = exception
 | 
						|
        self.timer = None
 | 
						|
        self.start()
 | 
						|
 | 
						|
    def start(self):
 | 
						|
        """Schedule the timeout.  This is called on construction, so
 | 
						|
        it should not be called explicitly, unless the timer has been 
 | 
						|
        cancelled."""
 | 
						|
        assert not self.pending, '%r is already started; to restart it, cancel it first' % self
 | 
						|
        if self.seconds is None: # "fake" timeout (never expires)
 | 
						|
            self.timer = None
 | 
						|
        elif self.exception is None or self.exception is False: # timeout that raises self
 | 
						|
            self.timer = get_hub().schedule_call_global(self.seconds, greenlet.getcurrent().throw, self)
 | 
						|
        else: # regular timeout with user-provided exception
 | 
						|
            self.timer = get_hub().schedule_call_global(self.seconds, greenlet.getcurrent().throw, self.exception)
 | 
						|
        return self
 | 
						|
 | 
						|
    @property
 | 
						|
    def pending(self):
 | 
						|
        """True if the timeout is scheduled to be raised."""
 | 
						|
        if self.timer is not None:
 | 
						|
            return self.timer.pending
 | 
						|
        else:
 | 
						|
            return False
 | 
						|
 | 
						|
    def cancel(self):
 | 
						|
        """If the timeout is pending, cancel it.  If not using Timeouts in 
 | 
						|
        ``with`` statements, always call cancel() in a ``finally`` after the 
 | 
						|
        block of code that is getting timed out.  If not cancelled, the timeout 
 | 
						|
        will be raised later on, in some unexpected section of the 
 | 
						|
        application."""
 | 
						|
        if self.timer is not None:
 | 
						|
            self.timer.cancel()
 | 
						|
            self.timer = None
 | 
						|
 | 
						|
    def __repr__(self):
 | 
						|
        try:
 | 
						|
            classname = self.__class__.__name__
 | 
						|
        except AttributeError: # Python < 2.5
 | 
						|
            classname = 'Timeout'
 | 
						|
        if self.pending:
 | 
						|
            pending = ' pending'
 | 
						|
        else:
 | 
						|
            pending = ''
 | 
						|
        if self.exception is None:
 | 
						|
            exception = ''
 | 
						|
        else:
 | 
						|
            exception = ' exception=%r' % self.exception
 | 
						|
        return '<%s at %s seconds=%s%s%s>' % (classname, hex(id(self)), self.seconds, exception, pending)
 | 
						|
 | 
						|
    def __str__(self):
 | 
						|
        """
 | 
						|
        >>> raise Timeout
 | 
						|
        Traceback (most recent call last):
 | 
						|
            ...
 | 
						|
        Timeout
 | 
						|
        """
 | 
						|
        if self.seconds is None:
 | 
						|
            return ''
 | 
						|
        if self.seconds == 1:
 | 
						|
            suffix = ''
 | 
						|
        else:
 | 
						|
            suffix = 's'
 | 
						|
        if self.exception is None:
 | 
						|
            return '%s second%s' % (self.seconds, suffix)
 | 
						|
        elif self.exception is False:
 | 
						|
            return '%s second%s (silent)' % (self.seconds, suffix)
 | 
						|
        else:
 | 
						|
            return '%s second%s (%s)' % (self.seconds, suffix, self.exception)
 | 
						|
 | 
						|
    def __enter__(self):
 | 
						|
        if self.timer is None:
 | 
						|
            self.start()
 | 
						|
        return self
 | 
						|
 | 
						|
    def __exit__(self, typ, value, tb):
 | 
						|
        self.cancel()
 | 
						|
        if value is self and self.exception is False:
 | 
						|
            return True
 | 
						|
 | 
						|
 | 
						|
def with_timeout(seconds, function, *args, **kwds):
 | 
						|
    """Wrap a call to some (yielding) function with a timeout; if the called
 | 
						|
    function fails to return before the timeout, cancel it and return a flag
 | 
						|
    value.
 | 
						|
    """
 | 
						|
    timeout_value = kwds.pop("timeout_value", _NONE)
 | 
						|
    timeout = Timeout(seconds)
 | 
						|
    try:
 | 
						|
        try:
 | 
						|
            return function(*args, **kwds)
 | 
						|
        except Timeout, ex:
 | 
						|
            if ex is timeout and timeout_value is not _NONE:
 | 
						|
                return timeout_value
 | 
						|
            raise
 | 
						|
    finally:
 | 
						|
        timeout.cancel()
 | 
						|
 |