[svn r96] A batch of exception-related cleanup and fixes.
- insulated Actor against exceptions raised in the received() method and another very-infrequently-occuring bug in run_forever. - More verbose when GreenletExit is raised in CoroutinePool, since we're not even sure it happens. - Minor optimization in httpd -- exceptions are somewhat expensive to raise -- when keep-alive header isn't present. - More verbose exception logging in httpd -- previously these exceptions would have gone unnoticed. - Trimmed the logic in greenlib.switch to just call exc_clear() since there's no reason to even bother with the if statement.
This commit is contained in:
@@ -23,6 +23,7 @@ THE SOFTWARE.
|
||||
"""
|
||||
|
||||
import collections
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
|
||||
@@ -253,8 +254,12 @@ class CoroutinePool(pools.Pool):
|
||||
result = func(*args, **kw)
|
||||
if evt is not None:
|
||||
evt.send(result)
|
||||
except api.GreenletExit:
|
||||
pass
|
||||
except api.GreenletExit, e:
|
||||
# we're printing this out to see if it ever happens
|
||||
# in practice
|
||||
print "GreenletExit raised in coroutine pool", e
|
||||
if evt is not None:
|
||||
evt.send(e) # sent as a return value, not an exception
|
||||
except Exception, e:
|
||||
traceback.print_exc()
|
||||
if evt is not None:
|
||||
@@ -352,12 +357,22 @@ class Actor(object):
|
||||
while True:
|
||||
if not self._mailbox:
|
||||
self._event.wait()
|
||||
self._event.reset()
|
||||
self._event = event()
|
||||
else:
|
||||
# leave the message in the mailbox until after it's
|
||||
# been processed so the event doesn't get triggered
|
||||
# while in the received method
|
||||
self.received(self._mailbox[0])
|
||||
try:
|
||||
self.received(self._mailbox[0])
|
||||
except KeyboardInterrupt:
|
||||
raise # allow the program to quit
|
||||
except:
|
||||
# we don't want to let the exception escape this
|
||||
# loop because that would kill the coroutine
|
||||
e = sys.exc_info()[0]
|
||||
self.excepted(e)
|
||||
sys.exc_clear()
|
||||
|
||||
self._mailbox.popleft()
|
||||
|
||||
def cast(self, message):
|
||||
@@ -394,9 +409,45 @@ class Actor(object):
|
||||
>>> api.sleep(0)
|
||||
received message 2
|
||||
received message 3
|
||||
|
||||
>>> api.kill(a._killer) # test cleanup
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def excepted(self, exc):
|
||||
""" Called when the received method raises an exception.
|
||||
|
||||
The default implementation simply prints out the raised exception.
|
||||
Redefine it for customization.
|
||||
|
||||
>>> class Exceptor(Actor):
|
||||
... def received(self, message):
|
||||
... if message == 'fail':
|
||||
... message + 1
|
||||
... else:
|
||||
... print "received", message
|
||||
... def excepted(self, exc):
|
||||
... print "excepted:", exc
|
||||
>>> a = Exceptor()
|
||||
>>> a.cast('fail')
|
||||
>>> api.sleep(0)
|
||||
excepted: <type 'exceptions.TypeError'>
|
||||
|
||||
The main purpose of excepted is to prevent the actor's coroutine
|
||||
from dying.
|
||||
|
||||
>>> a.cast('message 2')
|
||||
>>> api.sleep(0)
|
||||
received message 2
|
||||
|
||||
If excepted() itself raises an exception, that will kill the coroutine.
|
||||
|
||||
>>> api.kill(a._killer) # test cleanup
|
||||
"""
|
||||
print "Exception in %s.received(): %s" % (
|
||||
type(self).__name__, exc)
|
||||
traceback.print_exc()
|
||||
|
||||
def _test():
|
||||
print "Running doctests. There will be no further output if they succeed."
|
||||
import doctest
|
||||
|
||||
@@ -98,6 +98,16 @@ class TestEvent(tests.TestCase):
|
||||
api.spawn(send_to_event2)
|
||||
self.assertEqual(evt.wait(), value2)
|
||||
|
||||
def test_double_exception(self):
|
||||
evt = coros.event()
|
||||
# send an exception through the event
|
||||
evt.send(exc=RuntimeError())
|
||||
self.assertRaises(RuntimeError, evt.wait)
|
||||
evt.reset()
|
||||
# shouldn't see the RuntimeError again
|
||||
api.exc_after(0.001, api.TimeoutError)
|
||||
self.assertRaises(api.TimeoutError, evt.wait)
|
||||
|
||||
class TestCoroutinePool(tests.TestCase):
|
||||
mode = 'static'
|
||||
def setUp(self):
|
||||
@@ -160,7 +170,7 @@ class TestActor(tests.TestCase):
|
||||
mode = 'static'
|
||||
def setUp(self):
|
||||
# raise an exception if we're waiting forever
|
||||
self._cancel_timeout = api.exc_after(1, RuntimeError())
|
||||
self._cancel_timeout = api.exc_after(1, api.TimeoutError())
|
||||
self.actor = IncrActor()
|
||||
|
||||
def tearDown(self):
|
||||
@@ -213,7 +223,22 @@ class TestActor(tests.TestCase):
|
||||
self.assertEqual(msgs, [1,2,3,4,5])
|
||||
|
||||
|
||||
def test_raising_received(self):
|
||||
msgs = []
|
||||
def received(message):
|
||||
if message == 'fail':
|
||||
raise RuntimeError()
|
||||
else:
|
||||
print "appending"
|
||||
msgs.append(message)
|
||||
|
||||
self.actor.received = received
|
||||
|
||||
self.actor.cast('fail')
|
||||
api.sleep(0)
|
||||
self.actor.cast('should_appear')
|
||||
api.sleep(0)
|
||||
self.assertEqual(['should_appear'], msgs)
|
||||
|
||||
if __name__ == '__main__':
|
||||
tests.main()
|
||||
|
||||
@@ -306,10 +306,7 @@ def switch(other=None, value=None, exc=None):
|
||||
if not (other or hasattr(other, 'run')):
|
||||
raise SwitchingToDeadGreenlet("Switching to dead greenlet %r %r %r" % (other, value, exc))
|
||||
_greenlet_context_call('swap_out')
|
||||
running_exc = sys.exc_info()
|
||||
if running_exc[0] != None: # see if we're in the middle of an exception handler
|
||||
sys.exc_clear() # don't pass along exceptions to the other coroutine
|
||||
del running_exc # tracebacks can create cyclic object references
|
||||
sys.exc_clear() # don't pass along exceptions to the other coroutine
|
||||
try:
|
||||
rval = other.switch(value, exc)
|
||||
if not rval or not other:
|
||||
|
||||
@@ -487,8 +487,8 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler):
|
||||
request.set_header('Server', self.version_string())
|
||||
request.set_header('Date', self.date_time_string())
|
||||
try:
|
||||
timeout = int(request.get_header('keep-alive'))
|
||||
except (TypeError, ValueError), e:
|
||||
timeout = int(request.get_header('keep-alive', timeout))
|
||||
except TypeError, ValueError:
|
||||
pass
|
||||
|
||||
try:
|
||||
@@ -522,11 +522,13 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler):
|
||||
else:
|
||||
raise
|
||||
except Exception, e:
|
||||
self.server.log_message("Exception caught in HttpRequest.handle():\n")
|
||||
self.server.log_exception(*sys.exc_info())
|
||||
if not request.response_written():
|
||||
request.response(500)
|
||||
request.write('Internal Server Error')
|
||||
self.socket.close()
|
||||
raise
|
||||
raise e # can't do a plain raise since exc_info might have been cleared
|
||||
self.socket.close()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user