Adding timeout files, derr.

This commit is contained in:
Ryan Williams
2010-02-16 13:13:21 -08:00
parent df20576a69
commit 7664905e55
2 changed files with 182 additions and 0 deletions

128
eventlet/timeout.py Normal file
View File

@@ -0,0 +1,128 @@
# Copyright (c) 2009-2010 Denis Bilenko and Eventlet contributors. See LICENSE for details.
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):
"""Return 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. Otherwise, do nothing."""
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()

54
tests/timeout_test.py Normal file
View File

@@ -0,0 +1,54 @@
from tests import LimitedTestCase
from eventlet import timeout
from eventlet import greenthread
DELAY = 0.01
class TestDirectRaise(LimitedTestCase):
def test_direct_raise_class(self):
try:
raise timeout.Timeout
except timeout.Timeout, t:
assert not t.pending, repr(t)
def test_direct_raise_instance(self):
tm = timeout.Timeout()
try:
raise tm
except timeout.Timeout, t:
assert tm is t, (tm, t)
assert not t.pending, repr(t)
def test_repr(self):
# just verify these don't crash
tm = timeout.Timeout(1)
greenthread.sleep(0)
repr(tm)
str(tm)
tm.cancel()
tm = timeout.Timeout(None, RuntimeError)
repr(tm)
str(tm)
tm = timeout.Timeout(None, False)
repr(tm)
str(tm)
class TestWithTimeout(LimitedTestCase):
def test_with_timeout(self):
self.assertRaises(timeout.Timeout, timeout.with_timeout, DELAY, greenthread.sleep, DELAY*10)
X = object()
r = timeout.with_timeout(DELAY, greenthread.sleep, DELAY*10, timeout_value=X)
self.assert_(r is X, (r, X))
r = timeout.with_timeout(DELAY*10, greenthread.sleep,
DELAY, timeout_value=X)
self.assert_(r is None, r)
def test_with_outer_timer(self):
def longer_timeout():
# this should not catch the outer timeout's exception
return timeout.with_timeout(DELAY * 10,
greenthread.sleep, DELAY * 20,
timeout_value='b')
self.assertRaises(timeout.Timeout,
timeout.with_timeout, DELAY, longer_timeout)