This commit is contained in:
Ryan Williams
2010-02-16 20:25:45 -08:00
25 changed files with 464 additions and 214 deletions

View File

@@ -99,4 +99,91 @@ Though Eventlet has many modules, much of the most-used stuff is accessible simp
Queues are a fundamental construct for communicating data between execution units. Eventlet's Queue class is used to communicate between greenthreads, and provides a bunch of useful features for doing that. See :class:`queue.Queue` for more details.
.. class:: eventlet.Timeout
Raises *exception* in the current greenthread after *timeout* seconds::
timeout = Timeout(seconds, exception)
try:
... # execution here is limited by timeout
finally:
timeout.cancel()
When *exception* is omitted or ``None``, the :class:`Timeout` instance
itself is raised:
>>> Timeout(0.1)
>>> eventlet.sleep(0.2)
Traceback (most recent call last):
...
Timeout: 0.1 seconds
In Python 2.5 and newer, you can use the ``with`` statement for additional
convenience::
with Timeout(seconds, exception) as timeout:
pass # ... code block ...
This is equivalent to the try/finally block in the first example.
There is an additional feature when using the ``with`` statement: if
*exception* is ``False``, the timeout is still raised, but the with
statement suppresses it, so the code outside the with-block won't see it::
data = None
with Timeout(5, False):
data = mysock.makefile().readline()
if data is None:
... # 5 seconds passed without reading a line
else:
... # a line was read within 5 seconds
As a very special case, if *seconds* is None, the timer is not scheduled,
and is only useful if you're planning to raise it directly.
There are two Timeout caveats to be aware of:
* If the code block in the try/finally or with-block never cooperatively yields, the timeout cannot be raised. In Eventlet, this should rarely be a problem, but be aware that you cannot time out CPU-only operations with this class.
* If the code block catches and doesn't re-raise :class:`BaseException` (for example, with ``except:``), then it will catch the Timeout exception, and might not abort as intended.
When catching timeouts, keep in mind that the one you catch may not be the
one you have set; if you going to silence a timeout, always check that it's
the same instance that you set::
timeout = Timeout(1)
try:
...
except Timeout, t:
if t is not timeout:
raise # not my timeout
.. function:: eventlet.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.
:param seconds: seconds before timeout occurs
:type seconds: int or float
:param func: the callable to execute with a timeout; it must cooperatively yield, or else the timeout will not be able to trigger
:param \*args: positional arguments to pass to *func*
:param \*\*kwds: keyword arguments to pass to *func*
:param timeout_value: value to return if timeout occurs (by default raises
:class:`Timeout`)
:rtype: Value returned by *func* if *func* returns before *seconds*, else
*timeout_value* if provided, else raises :class:`Timeout`.
:exception Timeout: if *func* times out and no ``timeout_value`` has
been provided.
:exception: Any exception raised by *func*
Example::
data = with_timeout(30, urllib2.open, 'http://www.google.com/', timeout_value="")
Here *data* is either the result of the ``get()`` call, or the empty string
if it took too long to return. Any exception raised by the ``get()`` call
is passed through to the caller.
These are the basic primitives of Eventlet; there are a lot more out there in the other Eventlet modules; check out the :doc:`modules`.

View File

@@ -5,21 +5,25 @@ try:
from eventlet import greenthread
from eventlet import greenpool
from eventlet import queue
from eventlet import timeout
sleep = greenthread.sleep
spawn = greenthread.spawn
spawn_n = greenthread.spawn_n
spawn_after = greenthread.spawn_after
call_after_global = greenthread.call_after_global
TimeoutError = greenthread.TimeoutError
exc_after = greenthread.exc_after
with_timeout = greenthread.with_timeout
Timeout = timeout.Timeout
with_timeout = timeout.with_timeout
GreenPool = greenpool.GreenPool
GreenPile = greenpool.GreenPile
Queue = queue.Queue
# deprecated
TimeoutError = timeout.Timeout
exc_after = greenthread.exc_after
call_after_global = greenthread.call_after_global
except ImportError:
# this is to make Debian packaging easier
import traceback

View File

@@ -4,7 +4,8 @@ import time
from eventlet.pools import Pool
from eventlet.processes import DeadProcess
from eventlet import api
from eventlet import timeout
from eventlet import greenthread
class ConnectTimeout(Exception):
@@ -89,7 +90,7 @@ class BaseConnectionPool(Pool):
if next_delay > 0:
# set up a continuous self-calling loop
self._expiration_timer = api.call_after(next_delay,
self._expiration_timer = greenthread.spawn_after(next_delay,
self._schedule_expiration)
def _expire_old_connections(self, now):
@@ -248,7 +249,7 @@ class TpooledConnectionPool(BaseConnectionPool):
@classmethod
def connect(cls, db_module, connect_timeout, *args, **kw):
timeout = api.exc_after(connect_timeout, ConnectTimeout())
timeout = timeout.Timeout(connect_timeout, ConnectTimeout())
try:
from eventlet import tpool
conn = tpool.execute(db_module.connect, *args, **kw)
@@ -268,7 +269,7 @@ class RawConnectionPool(BaseConnectionPool):
@classmethod
def connect(cls, db_module, connect_timeout, *args, **kw):
timeout = api.exc_after(connect_timeout, ConnectTimeout())
timeout = timeout.Timeout(connect_timeout, ConnectTimeout())
try:
return db_module.connect(*args, **kw)
finally:

View File

@@ -2,7 +2,7 @@ from eventlet import patcher
from eventlet.green import thread
from eventlet.green import time
__patched__ = ['_start_new_thread', '_allocate_lock', '_get_ident']
__patched__ = ['_start_new_thread', '_allocate_lock', '_get_ident', '_sleep']
patcher.inject('threading',
globals(),

View File

@@ -2,6 +2,7 @@ import sys
from eventlet import event
from eventlet import hubs
from eventlet import timeout
from eventlet.hubs import timer
from eventlet.support import greenlets as greenlet
import warnings
@@ -9,7 +10,6 @@ import warnings
__all__ = ['getcurrent', 'sleep', 'spawn', 'spawn_n', 'call_after_global', 'call_after_local', 'GreenThread']
getcurrent = greenlet.getcurrent
TimeoutError = hubs.TimeoutError
def sleep(seconds=0):
"""Yield control to another eligible coroutine until at least *seconds* have
@@ -170,57 +170,17 @@ def exc_after(seconds, *throw_args):
else:
timer.cancel()
"""
warnings.warn("Instead of exc_after, which is deprecated, use "
"Timeout(seconds, exception)",
DeprecationWarning, stacklevel=2)
if seconds is None: # dummy argument, do nothing
return timer.Timer(seconds, lambda: None)
hub = hubs.get_hub()
return hub.schedule_call_local(seconds, getcurrent().throw, *throw_args)
def with_timeout(seconds, func, *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.
:param seconds: seconds before timeout occurs
:type seconds: int or float
:param func: the callable to execute with a timeout; must be one of the
functions that implicitly or explicitly yields
:param \*args: positional arguments to pass to *func*
:param \*\*kwds: keyword arguments to pass to *func*
:param timeout_value: value to return if timeout occurs (default raise
:class:`~eventlet.api.TimeoutError`)
:rtype: Value returned by *func* if *func* returns before *seconds*, else
*timeout_value* if provided, else raise ``TimeoutError``
:exception TimeoutError: if *func* times out and no ``timeout_value`` has
been provided.
:exception *any*: Any exception raised by *func*
**Example**::
data = with_timeout(30, httpc.get, 'http://www.google.com/', timeout_value="")
Here *data* is either the result of the ``get()`` call, or the empty string if
it took too long to return. Any exception raised by the ``get()`` call is
passed through to the caller.
"""
# Recognize a specific keyword argument, while also allowing pass-through
# of any other keyword arguments accepted by func. Use pop() so we don't
# pass timeout_value through to func().
has_timeout_value = "timeout_value" in kwds
timeout_value = kwds.pop("timeout_value", None)
error = TimeoutError()
timeout = exc_after(seconds, error)
try:
try:
return func(*args, **kwds)
except TimeoutError, ex:
if ex is error and has_timeout_value:
return timeout_value
raise
finally:
timeout.cancel()
# deprecate, remove
TimeoutError = timeout.Timeout
with_timeout = timeout.with_timeout
def _spawn_n(seconds, func, args, kwargs):
hub = hubs.get_hub()

View File

@@ -83,12 +83,9 @@ def get_hub():
hub = _threadlocal.hub = _threadlocal.Hub()
return hub
class TimeoutError(Exception):
"""Exception raised if an asynchronous operation times out"""
pass
from eventlet import timeout
def trampoline(fd, read=None, write=None, timeout=None,
timeout_exc=TimeoutError):
timeout_exc=timeout.Timeout):
"""Suspend the current coroutine until the given socket object or file
descriptor is ready to *read*, ready to *write*, or the specified
*timeout* elapses, depending on arguments specified.

View File

@@ -29,7 +29,10 @@ class event_wrapper(object):
if self.impl is not None:
self.impl.delete()
self.impl = None
@property
def pending(self):
return bool(self.impl and self.impl.pending())
class Hub(BaseHub):

View File

@@ -29,6 +29,10 @@ class Timer(object):
@property
def cancelled(self):
return self._cancelled
@property
def pending(self):
return not (self._cancelled or self.called)
def __repr__(self):
secs = getattr(self, 'seconds', None)

View File

@@ -28,8 +28,9 @@ from Queue import Full, Empty
_NONE = object()
from eventlet.hubs import get_hub
from eventlet.greenthread import getcurrent, exc_after
from eventlet.greenthread import getcurrent
from eventlet.event import Event
from eventlet.timeout import Timeout
__all__ = ['Queue', 'PriorityQueue', 'LifoQueue', 'LightQueue', 'Full', 'Empty']
@@ -211,7 +212,7 @@ class LightQueue(object):
elif block:
waiter = ItemWaiter(item)
self.putters.add(waiter)
timeout = exc_after(timeout, Full)
timeout = Timeout(timeout, Full)
try:
if self.getters:
self._schedule_unlock()
@@ -259,7 +260,7 @@ class LightQueue(object):
raise Empty
elif block:
waiter = Waiter()
timeout = exc_after(timeout, Empty)
timeout = Timeout(timeout, Empty)
try:
self.getters.add(waiter)
if self.putters:

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()

View File

@@ -99,9 +99,9 @@ class LimitedTestCase(unittest.TestCase):
TEST_TIMEOUT = 1
def setUp(self):
from eventlet import api
self.timer = api.exc_after(self.TEST_TIMEOUT,
TestIsTakingTooLong(self.TEST_TIMEOUT))
import eventlet
self.timer = eventlet.Timeout(self.TEST_TIMEOUT,
TestIsTakingTooLong(self.TEST_TIMEOUT))
def tearDown(self):
self.timer.cancel()

View File

@@ -2,9 +2,9 @@
from tests import skipped, skip_unless, skip_with_pyevent
from unittest import TestCase, main
from eventlet import api
from eventlet import event
from eventlet import db_pool
import eventlet
import os
class DBTester(object):
@@ -145,7 +145,7 @@ class DBConnectionPool(DBTester):
results.append(2)
evt.send()
evt2 = event.Event()
api.spawn(a_query)
eventlet.spawn(a_query)
results.append(1)
self.assertEqual([1], results)
evt.wait()
@@ -232,8 +232,8 @@ class DBConnectionPool(DBTester):
results.append(2)
evt2.send()
api.spawn(long_running_query)
api.spawn(short_running_query)
eventlet.spawn(long_running_query)
eventlet.spawn(short_running_query)
evt.wait()
evt2.wait()
results.sort()
@@ -304,17 +304,17 @@ class DBConnectionPool(DBTester):
self.connection = self.pool.get()
self.connection.close()
self.assertEquals(len(self.pool.free_items), 1)
api.sleep(0.01) # not long enough to trigger the idle timeout
eventlet.sleep(0.01) # not long enough to trigger the idle timeout
self.assertEquals(len(self.pool.free_items), 1)
self.connection = self.pool.get()
self.connection.close()
self.assertEquals(len(self.pool.free_items), 1)
api.sleep(0.01) # idle timeout should have fired but done nothing
eventlet.sleep(0.01) # idle timeout should have fired but done nothing
self.assertEquals(len(self.pool.free_items), 1)
self.connection = self.pool.get()
self.connection.close()
self.assertEquals(len(self.pool.free_items), 1)
api.sleep(0.03) # long enough to trigger idle timeout for real
eventlet.sleep(0.03) # long enough to trigger idle timeout for real
self.assertEquals(len(self.pool.free_items), 0)
@skipped
@@ -323,11 +323,11 @@ class DBConnectionPool(DBTester):
self.pool = self.create_pool(max_size=2, max_idle=0.02)
self.connection, conn2 = self.pool.get(), self.pool.get()
self.connection.close()
api.sleep(0.01)
eventlet.sleep(0.01)
self.assertEquals(len(self.pool.free_items), 1)
conn2.close()
self.assertEquals(len(self.pool.free_items), 2)
api.sleep(0.02) # trigger cleanup of conn1 but not conn2
eventlet.sleep(0.02) # trigger cleanup of conn1 but not conn2
self.assertEquals(len(self.pool.free_items), 1)
@skipped
@@ -337,12 +337,12 @@ class DBConnectionPool(DBTester):
self.connection = self.pool.get()
self.connection.close()
self.assertEquals(len(self.pool.free_items), 1)
api.sleep(0.01) # not long enough to trigger the age timeout
eventlet.sleep(0.01) # not long enough to trigger the age timeout
self.assertEquals(len(self.pool.free_items), 1)
self.connection = self.pool.get()
self.connection.close()
self.assertEquals(len(self.pool.free_items), 1)
api.sleep(0.05) # long enough to trigger age timeout
eventlet.sleep(0.05) # long enough to trigger age timeout
self.assertEquals(len(self.pool.free_items), 0)
@skipped
@@ -352,9 +352,9 @@ class DBConnectionPool(DBTester):
self.connection, conn2 = self.pool.get(), self.pool.get()
self.connection.close()
self.assertEquals(len(self.pool.free_items), 1)
api.sleep(0) # not long enough to trigger the age timeout
eventlet.sleep(0) # not long enough to trigger the age timeout
self.assertEquals(len(self.pool.free_items), 1)
api.sleep(0.2) # long enough to trigger age timeout
eventlet.sleep(0.2) # long enough to trigger age timeout
self.assertEquals(len(self.pool.free_items), 0)
conn2.close() # should not be added to the free items
self.assertEquals(len(self.pool.free_items), 0)
@@ -374,13 +374,13 @@ class DBConnectionPool(DBTester):
def retrieve(pool, ev):
c = pool.get()
ev.send(c)
api.spawn(retrieve, self.pool, e)
api.sleep(0) # these two sleeps should advance the retrieve
api.sleep(0) # coroutine until it's waiting in get()
eventlet.spawn(retrieve, self.pool, e)
eventlet.sleep(0) # these two sleeps should advance the retrieve
eventlet.sleep(0) # coroutine until it's waiting in get()
self.assertEquals(self.pool.free(), 0)
self.assertEquals(self.pool.waiting(), 1)
self.pool.put(self.connection)
timer = api.exc_after(1, api.TimeoutError)
timer = eventlet.Timeout(1)
conn = e.wait()
timer.cancel()
self.assertEquals(self.pool.free(), 0)

View File

@@ -64,6 +64,6 @@ class TestEvent(LimitedTestCase):
self.assertRaises(RuntimeError, evt.wait)
evt.reset()
# shouldn't see the RuntimeError again
eventlet.exc_after(0.001, eventlet.TimeoutError('from test_double_exception'))
self.assertRaises(eventlet.TimeoutError, evt.wait)
eventlet.Timeout(0.001)
self.assertRaises(eventlet.Timeout, evt.wait)

View File

@@ -246,7 +246,7 @@ class TestGreenIo(LimitedTestCase):
try:
# try and get some data off of this pipe
# but bail before any is sent
eventlet.exc_after(0.01, eventlet.TimeoutError)
eventlet.Timeout(0.01)
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('127.0.0.1', bound_port))
wrap_rfile = client.makefile()

View File

@@ -120,7 +120,7 @@ class GreenPool(tests.LimitedTestCase):
self.assertEquals(pool.free(), num_free)
def wait_long_time(e):
e.wait()
timer = eventlet.exc_after(1, eventlet.TimeoutError)
timer = eventlet.Timeout(1)
try:
evt = event.Event()
for x in xrange(num_free):
@@ -132,7 +132,7 @@ class GreenPool(tests.LimitedTestCase):
# if the runtime error is not raised it means the pool had
# some unexpected free items
timer = eventlet.exc_after(0, RuntimeError())
timer = eventlet.Timeout(0, RuntimeError)
try:
self.assertRaises(RuntimeError, pool.spawn, wait_long_time, evt)
finally:
@@ -182,7 +182,7 @@ class GreenPool(tests.LimitedTestCase):
tp = pools.TokenPool(max_size=1)
token = tp.get() # empty out the pool
def do_receive(tp):
timer = eventlet.exc_after(0, RuntimeError())
timer = eventlet.Timeout(0, RuntimeError())
try:
t = tp.get()
self.fail("Shouldn't have recieved anything from the pool")
@@ -316,8 +316,8 @@ class GreenPile(tests.LimitedTestCase):
for i in xrange(4):
p.spawn(passthru, i)
# now it should be full and this should time out
eventlet.exc_after(0, eventlet.TimeoutError)
self.assertRaises(eventlet.TimeoutError, p.spawn, passthru, "time out")
eventlet.Timeout(0)
self.assertRaises(eventlet.Timeout, p.spawn, passthru, "time out")
# verify that the spawn breakage didn't interrupt the sequence
# and terminates properly
for i in xrange(4,10):

View File

@@ -10,7 +10,7 @@ if parent_dir not in sys.path:
# hacky hacks: skip test__api_timeout when under 2.4 because otherwise it SyntaxErrors
if sys.version_info < (2,5):
argv = sys.argv + ["--exclude=.*test__api_timeout.*"]
argv = sys.argv + ["--exclude=.*timeout_test_with_statement.*"]
else:
argv = sys.argv

View File

@@ -1,5 +1,6 @@
from unittest import TestCase, main
import eventlet
from eventlet import api
from eventlet import coros
from eventlet import pools
@@ -11,7 +12,6 @@ class IntPool(pools.Pool):
class TestIntPool(TestCase):
mode = 'static'
def setUp(self):
self.pool = IntPool(min_size=0, max_size=4)
@@ -103,7 +103,7 @@ class TestIntPool(TestCase):
self.assertEquals(self.pool.get(), two)
def test_putting_to_queue(self):
timer = api.exc_after(0.1, api.TimeoutError)
timer = eventlet.Timeout(0.1)
try:
size = 2
self.pool = IntPool(min_size=0, max_size=size)

View File

@@ -3,7 +3,7 @@ import eventlet
from eventlet import event
def do_bail(q):
eventlet.exc_after(0, RuntimeError())
eventlet.Timeout(0, RuntimeError())
try:
result = q.get()
return result

View File

@@ -27,7 +27,7 @@ def assimilate_patched(name):
test_method()
restart_hub()
globals()[method_name] = test_main
modobj.test_main.__name__ = name + '.test_main'
test_main.__name__ = name + '.test_main'
except AttributeError:
print "No test_main for %s, assuming it tests on import" % name

View File

@@ -1,106 +0,0 @@
from __future__ import with_statement
import sys
import unittest
import weakref
import time
from eventlet.api import sleep, timeout, TimeoutError, _SilentException
DELAY = 0.01
class Error(Exception):
pass
class Test(unittest.TestCase):
def test_api(self):
# Nothing happens if with-block finishes before the timeout expires
with timeout(DELAY*2):
sleep(DELAY)
sleep(DELAY*2) # check if timer was actually cancelled
# An exception will be raised if it's not
try:
with timeout(DELAY):
sleep(DELAY*2)
except TimeoutError:
pass
else:
raise AssertionError('must raise TimeoutError')
# You can customize the exception raised:
try:
with timeout(DELAY, IOError("Operation takes way too long")):
sleep(DELAY*2)
except IOError, ex:
assert str(ex)=="Operation takes way too long", repr(ex)
# Providing classes instead of values should be possible too:
try:
with timeout(DELAY, ValueError):
sleep(DELAY*2)
except ValueError:
pass
# basically, anything that greenlet.throw accepts work:
try:
1/0
except:
try:
with timeout(DELAY, *sys.exc_info()):
sleep(DELAY*2)
raise AssertionError('should not get there')
raise AssertionError('should not get there')
except ZeroDivisionError:
pass
else:
raise AssertionError('should not get there')
# It's possible to cancel the timer inside the block:
with timeout(DELAY) as timer:
timer.cancel()
sleep(DELAY*2)
# To silent the exception, pass None as second parameter. The with-block
# will be interrupted with _SilentException, but it won't be propagated
# outside.
XDELAY=0.1
start = time.time()
with timeout(XDELAY, None):
sleep(XDELAY*10)
delta = (time.time()-start)
assert delta<XDELAY*10, delta
# passing None as seconds disables the timer
with timeout(None):
sleep(DELAY)
sleep(DELAY)
def test_ref(self):
err = Error()
err_ref = weakref.ref(err)
with timeout(DELAY*2, err):
sleep(DELAY)
del err
assert not err_ref(), repr(err_ref())
def test_nested_timeout(self):
with timeout(DELAY, None):
with timeout(DELAY*2, None):
sleep(DELAY*3)
raise AssertionError('should not get there')
with timeout(DELAY, _SilentException()):
with timeout(DELAY*2, _SilentException()):
sleep(DELAY*3)
raise AssertionError('should not get there')
# this case fails and there's no intent to fix it.
# just don't do it like that
#with timeout(DELAY, _SilentException):
# with timeout(DELAY*2, _SilentException):
# sleep(DELAY*3)
# assert False, 'should not get there'
if __name__=='__main__':
unittest.main()

View File

@@ -19,7 +19,7 @@ class TestQueue(LimitedTestCase):
def test_send_last(self):
q = coros.queue()
def waiter(q):
timer = api.exc_after(0.1, api.TimeoutError)
timer = eventlet.Timeout(0.1)
self.assertEquals(q.wait(), 'hi2')
timer.cancel()
@@ -92,7 +92,7 @@ class TestQueue(LimitedTestCase):
q = coros.queue()
def do_receive(q, evt):
api.exc_after(0, RuntimeError())
eventlet.Timeout(0, RuntimeError())
try:
result = q.wait()
evt.send(result)
@@ -120,7 +120,7 @@ class TestQueue(LimitedTestCase):
def waiter(q, evt):
evt.send(q.wait())
def do_receive(q, evt):
api.exc_after(0, RuntimeError())
eventlet.Timeout(0, RuntimeError())
try:
result = q.wait()
evt.send(result)
@@ -139,7 +139,7 @@ class TestQueue(LimitedTestCase):
def test_two_bogus_waiters(self):
def do_receive(q, evt):
api.exc_after(0, RuntimeError())
eventlet.Timeout(0, RuntimeError())
try:
result = q.wait()
evt.send(result)

View File

@@ -1,6 +1,7 @@
import unittest
from eventlet.coros import Event
from eventlet.api import spawn, sleep, exc_after, with_timeout
from eventlet.event import Event
from eventlet.api import spawn, sleep, with_timeout
import eventlet
from tests import LimitedTestCase
DELAY= 0.01
@@ -30,7 +31,7 @@ class TestEvent(LimitedTestCase):
event2 = Event()
spawn(event1.send, 'hello event1')
exc_after(0, ValueError('interrupted'))
eventlet.Timeout(0, ValueError('interrupted'))
try:
result = event1.wait()
except ValueError:

View File

@@ -1,4 +1,4 @@
from eventlet import pool, coros, api, hubs
from eventlet import pool, coros, api, hubs, timeout
from tests import LimitedTestCase
from unittest import main
@@ -171,7 +171,7 @@ class TestCoroutinePool(LimitedTestCase):
api.sleep(0)
self.assertEqual(pool.free(), 1)
# shouldn't block when trying to get
t = api.exc_after(0.1, api.TimeoutError)
t = timeout.Timeout(0.1)
try:
pool.execute(api.sleep, 1)
finally:

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)

View File

@@ -0,0 +1,116 @@
""" Tests with-statement behavior of Timeout class. Don't import when
using Python 2.4. """
from __future__ import with_statement
import sys
import unittest
import weakref
import time
from eventlet import sleep
from eventlet.timeout import Timeout
from tests import LimitedTestCase
DELAY = 0.01
class Error(Exception):
pass
class Test(LimitedTestCase):
def test_api(self):
# Nothing happens if with-block finishes before the timeout expires
t = Timeout(DELAY*2)
sleep(0) # make it pending
assert t.pending, repr(t)
with t:
assert t.pending, repr(t)
sleep(DELAY)
# check if timer was actually cancelled
assert not t.pending, repr(t)
sleep(DELAY*2)
# An exception will be raised if it's not
try:
with Timeout(DELAY) as t:
sleep(DELAY*2)
except Timeout, ex:
assert ex is t, (ex, t)
else:
raise AssertionError('must raise Timeout')
# You can customize the exception raised:
try:
with Timeout(DELAY, IOError("Operation takes way too long")):
sleep(DELAY*2)
except IOError, ex:
assert str(ex)=="Operation takes way too long", repr(ex)
# Providing classes instead of values should be possible too:
try:
with Timeout(DELAY, ValueError):
sleep(DELAY*2)
except ValueError:
pass
try:
1/0
except:
try:
with Timeout(DELAY, sys.exc_info()[0]):
sleep(DELAY*2)
raise AssertionError('should not get there')
raise AssertionError('should not get there')
except ZeroDivisionError:
pass
else:
raise AssertionError('should not get there')
# It's possible to cancel the timer inside the block:
with Timeout(DELAY) as timer:
timer.cancel()
sleep(DELAY*2)
# To silent the exception before exiting the block, pass False as second parameter.
XDELAY=0.1
start = time.time()
with Timeout(XDELAY, False):
sleep(XDELAY*2)
delta = (time.time()-start)
assert delta<XDELAY*2, delta
# passing None as seconds disables the timer
with Timeout(None):
sleep(DELAY)
sleep(DELAY)
def test_ref(self):
err = Error()
err_ref = weakref.ref(err)
with Timeout(DELAY*2, err):
sleep(DELAY)
del err
assert not err_ref(), repr(err_ref())
def test_nested_timeout(self):
with Timeout(DELAY, False):
with Timeout(DELAY*2, False):
sleep(DELAY*3)
raise AssertionError('should not get there')
with Timeout(DELAY) as t1:
with Timeout(DELAY*2) as t2:
try:
sleep(DELAY*3)
except Timeout, ex:
assert ex is t1, (ex, t1)
assert not t1.pending, t1
assert t2.pending, t2
assert not t2.pending, t2
with Timeout(DELAY*2) as t1:
with Timeout(DELAY) as t2:
try:
sleep(DELAY*3)
except Timeout, ex:
assert ex is t2, (ex, t2)
assert t1.pending, t1
assert not t2.pending, t2
assert not t1.pending, t1