Tweaked the implementation of Timeout so that passing True as the exception class is the same as passing None, because it seemed inconsistent that one boolean value would have special nice behavior and the other would cause weird exceptions.

This commit is contained in:
Ryan Williams
2010-06-19 18:42:30 -07:00
parent 392682b8fa
commit bf3944b0e1
3 changed files with 27 additions and 7 deletions

View File

@@ -57,7 +57,7 @@ class Timeout(BaseException):
'%r is already started; to restart it, cancel it first' % self '%r is already started; to restart it, cancel it first' % self
if self.seconds is None: # "fake" timeout (never expires) if self.seconds is None: # "fake" timeout (never expires)
self.timer = None self.timer = None
elif self.exception is None or self.exception is False: # timeout that raises self elif self.exception is None or isinstance(self.exception, bool): # timeout that raises self
self.timer = get_hub().schedule_call_global( self.timer = get_hub().schedule_call_global(
self.seconds, greenlet.getcurrent().throw, self) self.seconds, greenlet.getcurrent().throw, self)
else: # regular timeout with user-provided exception else: # regular timeout with user-provided exception
@@ -112,7 +112,7 @@ class Timeout(BaseException):
suffix = '' suffix = ''
else: else:
suffix = 's' suffix = 's'
if self.exception is None: if self.exception is None or self.exception is True:
return '%s second%s' % (self.seconds, suffix) return '%s second%s' % (self.seconds, suffix)
elif self.exception is False: elif self.exception is False:
return '%s second%s (silent)' % (self.seconds, suffix) return '%s second%s (silent)' % (self.seconds, suffix)

View File

@@ -2,7 +2,7 @@
it doesn't respect robots.txt and it is pretty brutal about how quickly it it doesn't respect robots.txt and it is pretty brutal about how quickly it
fetches pages. fetches pages.
This is a kind of "producer/consumer" example; the producer function produces This is a kind of "producer/consumer" example; the fetch function produces
jobs, and the GreenPool itself is the consumer, farming out work concurrently. jobs, and the GreenPool itself is the consumer, farming out work concurrently.
It's easier to write it this way rather than writing a standard consumer loop; It's easier to write it this way rather than writing a standard consumer loop;
GreenPool handles any exceptions raised and arranges so that there's a set GreenPool handles any exceptions raised and arranges so that there's a set
@@ -43,7 +43,7 @@ def producer(start_url):
# limit requests to eventlet.net so we don't crash all over the internet # limit requests to eventlet.net so we don't crash all over the internet
if url not in seen and 'eventlet.net' in url: if url not in seen and 'eventlet.net' in url:
seen.add(url) seen.add(url)
pool.spawn(fetch, url, q) pool.spawn_n(fetch, url, q)
return seen return seen

View File

@@ -15,7 +15,7 @@ class Error(Exception):
pass pass
class Test(LimitedTestCase): class Test(LimitedTestCase):
def test_api(self): def test_cancellation(self):
# Nothing happens if with-block finishes before the timeout expires # Nothing happens if with-block finishes before the timeout expires
t = Timeout(DELAY*2) t = Timeout(DELAY*2)
sleep(0) # make it pending sleep(0) # make it pending
@@ -27,6 +27,7 @@ class Test(LimitedTestCase):
assert not t.pending, repr(t) assert not t.pending, repr(t)
sleep(DELAY*2) sleep(DELAY*2)
def test_raising_self(self):
# An exception will be raised if it's not # An exception will be raised if it's not
try: try:
with Timeout(DELAY) as t: with Timeout(DELAY) as t:
@@ -36,6 +37,17 @@ class Test(LimitedTestCase):
else: else:
raise AssertionError('must raise Timeout') raise AssertionError('must raise Timeout')
def test_raising_self_true(self):
# specifying True as the exception raises self as well
try:
with Timeout(DELAY, True) as t:
sleep(DELAY*2)
except Timeout, ex:
assert ex is t, (ex, t)
else:
raise AssertionError('must raise Timeout')
def test_raising_custom_exception(self):
# You can customize the exception raised: # You can customize the exception raised:
try: try:
with Timeout(DELAY, IOError("Operation takes way too long")): with Timeout(DELAY, IOError("Operation takes way too long")):
@@ -43,6 +55,7 @@ class Test(LimitedTestCase):
except IOError, ex: except IOError, ex:
assert str(ex)=="Operation takes way too long", repr(ex) assert str(ex)=="Operation takes way too long", repr(ex)
def test_raising_exception_class(self):
# Providing classes instead of values should be possible too: # Providing classes instead of values should be possible too:
try: try:
with Timeout(DELAY, ValueError): with Timeout(DELAY, ValueError):
@@ -50,6 +63,7 @@ class Test(LimitedTestCase):
except ValueError: except ValueError:
pass pass
def test_raising_exc_tuple(self):
try: try:
1//0 1//0
except: except:
@@ -63,12 +77,16 @@ class Test(LimitedTestCase):
else: else:
raise AssertionError('should not get there') raise AssertionError('should not get there')
def test_cancel_timer_inside_block(self):
# It's possible to cancel the timer inside the block: # It's possible to cancel the timer inside the block:
with Timeout(DELAY) as timer: with Timeout(DELAY) as timer:
timer.cancel() timer.cancel()
sleep(DELAY*2) sleep(DELAY*2)
# To silent the exception before exiting the block, pass False as second parameter.
def test_silent_block(self):
# To silence the exception before exiting the block, pass
# False as second parameter.
XDELAY=0.1 XDELAY=0.1
start = time.time() start = time.time()
with Timeout(XDELAY, False): with Timeout(XDELAY, False):
@@ -76,6 +94,8 @@ class Test(LimitedTestCase):
delta = (time.time()-start) delta = (time.time()-start)
assert delta<XDELAY*2, delta assert delta<XDELAY*2, delta
def test_dummy_timer(self):
# passing None as seconds disables the timer # passing None as seconds disables the timer
with Timeout(None): with Timeout(None):
sleep(DELAY) sleep(DELAY)