[Checkins] SVN: zc.ngi/branches/jim-dev/src/zc/ngi/ blocking.request now propigate handler errors to caller.
Jim Fulton
jim at zope.com
Sat Jun 19 15:27:28 EDT 2010
Log message for revision 113701:
blocking.request now propigate handler errors to caller.
async didn't properly handle iterator errors. (It didn't call
handle_error.)
Various other error handling fixes.
Changed:
U zc.ngi/branches/jim-dev/src/zc/ngi/async.py
U zc.ngi/branches/jim-dev/src/zc/ngi/async.txt
U zc.ngi/branches/jim-dev/src/zc/ngi/blocking.py
U zc.ngi/branches/jim-dev/src/zc/ngi/tests.py
-=-
Modified: zc.ngi/branches/jim-dev/src/zc/ngi/async.py
===================================================================
--- zc.ngi/branches/jim-dev/src/zc/ngi/async.py 2010-06-19 19:04:46 UTC (rev 113700)
+++ zc.ngi/branches/jim-dev/src/zc/ngi/async.py 2010-06-19 19:27:28 UTC (rev 113701)
@@ -78,7 +78,7 @@
self.__connected = True
self.__closed = None
self.__handler = None
- self.__exception = None
+ self.__iterator_exception = None
self.__output = []
dispatcher.__init__(self, sock, addr)
self.logger = logger
@@ -91,14 +91,14 @@
raise TypeError("Handler already set")
self.__handler = handler
- if self.__exception:
- exception = self.__exception
- self.__exception = None
+ if self.__iterator_exception:
+ v = self.__iterator_exception
+ self.__iterator_exception = None
try:
- handler.handle_exception(self, exception)
+ handler.handle_exception(self, v)
except:
self.logger.exception("handle_exception failed")
- return self.handle_close("handle_exception failed")
+ raise
if self.__closed:
try:
@@ -156,7 +156,7 @@
self.__handler.handle_input(self, d)
except:
self.logger.exception("handle_input failed")
- self.handle_close("handle_input failed")
+ raise
if len(d) < BUFFER_SIZE:
break
@@ -176,16 +176,22 @@
# Must be an iterator
try:
v = v.next()
+ if not isinstance(v, str):
+ raise TypeError(
+ "writelines iterator must return strings",
+ v)
except StopIteration:
# all done
output.pop(0)
continue
+ except Exception, v:
+ self.logger.exception("writelines iterator failed")
+ if self.__handler is None:
+ self.__iterator_exception = v
+ raise
+ else:
+ self.__handler.handle_exception(self, v)
- if __debug__ and not isinstance(v, str):
- exc = TypeError("iterable output returned a non-string", v)
- self.__report_exception(exc)
- raise exc
-
output.insert(0, v)
if not v:
@@ -199,7 +205,7 @@
return # we couldn't write anything
raise
except Exception, v:
- self.__report_exception(v)
+ self.logger.exception("send failed")
raise
if n == len(v):
@@ -208,16 +214,6 @@
output[0] = v[n:]
return # can't send any more
- def __report_exception(self, exception):
- if self.__handler is not None:
- try:
- self.__handler.handle_exception(self, exception)
- except:
- self.logger.exception("handle_exception failed")
- self.handle_close("handle_exception failed")
- else:
- self.__exception = exception
-
def handle_close(self, reason='end of input'):
if __debug__:
self.logger.debug('close %r', reason)
Modified: zc.ngi/branches/jim-dev/src/zc/ngi/async.txt
===================================================================
--- zc.ngi/branches/jim-dev/src/zc/ngi/async.txt 2010-06-19 19:04:46 UTC (rev 113700)
+++ zc.ngi/branches/jim-dev/src/zc/ngi/async.txt 2010-06-19 19:27:28 UTC (rev 113701)
@@ -94,7 +94,7 @@
>>> input.readline()
Traceback (most recent call last):
...
- TypeError: ('iterable output returned a non-string', 2)
+ TypeError: ('writelines iterator must return strings', 2)
If there is an error, then the connection is closed:
@@ -164,9 +164,11 @@
>>> print loghandler
zc.ngi.async.client ERROR
handle_input failed
+ zc.ngi.async.client ERROR
+ handle_error
>>> handler.closed
- 'handle_input failed'
+ TypeError('handle_input() takes exactly 2 arguments (3 given)',)
>>> loghandler.uninstall()
Modified: zc.ngi/branches/jim-dev/src/zc/ngi/blocking.py
===================================================================
--- zc.ngi/branches/jim-dev/src/zc/ngi/blocking.py 2010-06-19 19:04:46 UTC (rev 113700)
+++ zc.ngi/branches/jim-dev/src/zc/ngi/blocking.py 2010-06-19 19:27:28 UTC (rev 113701)
@@ -11,9 +11,9 @@
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
-
-import threading, time
-
+import sys
+import threading
+import time
import zc.ngi
class ConnectionFailed(Exception):
@@ -52,12 +52,23 @@
self.connection.setHandler(self)
def handle_input(self, connection, data):
- self.handler.handle_input(self, data)
+ try:
+ self.handler.handle_input(self, data)
+ except:
+ self.connector.exception = sys.exc_info()
+ self.connector.event.set()
+ raise
def handle_close(self, connection, reason):
handle_close = getattr(self.handler, 'handle_close', None)
if handle_close is not None:
- handle_close(self, reason)
+ try:
+ handle_close(self, reason)
+ except:
+ self.connector.exception = sys.exc_info()
+ self.connector.event.set()
+ raise
+
self.connector.closed = True
self.connector.result = reason
self.connector.event.set()
@@ -65,8 +76,16 @@
@property
def handle_exception(self):
handle = self.handler.handle_exception
- return lambda c, exception: handle(self, exception)
+ def handle_exception(connection, exception):
+ try:
+ handle(self, exception)
+ except:
+ self.connector.exception = sys.exc_info()
+ self.connector.event.set()
+ raise
+ return handle_exception
+
class RequestConnector:
exception = closed = connection = result = None
@@ -87,7 +106,12 @@
def connected(self, connection):
self.connection = connection
- self._connected(RequestConnection(connection, self))
+ try:
+ self._connected(RequestConnection(connection, self))
+ except:
+ self.exception = sys.exc_info()
+ self.event.set()
+ raise
def failed_connect(self, reason):
self.exception = ConnectionFailed(reason)
@@ -98,10 +122,18 @@
connector = RequestConnector(connection_handler, event)
connect(address, connector)
event.wait(timeout)
+
+ if connector.exception:
+ exception = connector.exception
+ del connector.exception
+ if isinstance(exception, tuple):
+ raise exception[0], exception[1], exception[2]
+ else:
+ raise exception
+
if connector.closed:
return connector.result
- if connector.exception:
- raise connector.exception
+
if connector.connection is None:
raise ConnectionTimeout
raise Timeout
Modified: zc.ngi/branches/jim-dev/src/zc/ngi/tests.py
===================================================================
--- zc.ngi/branches/jim-dev/src/zc/ngi/tests.py 2010-06-19 19:04:46 UTC (rev 113700)
+++ zc.ngi/branches/jim-dev/src/zc/ngi/tests.py 2010-06-19 19:27:28 UTC (rev 113701)
@@ -206,6 +206,110 @@
>>> listener.close()
"""
+def errors_raised_by_handler_should_be_propigated_by_blocking_request():
+ """
+ Errors raised by handlers should propigate to the request caller,
+ rather than just getting logged as usual.
+
+ Note that this test also exercises error handling in zc.ngi.async.
+
+ >>> from zc.ngi import async
+ >>> from zc.ngi.adapters import Sized
+ >>> from zc.ngi.blocking import request
+
+ >>> @Sized.handler
+ ... def echo(c):
+ ... while 1:
+ ... data = (yield)
+ ... if data == 'stop': break
+ ... c.write(data)
+
+ >>> listener = async.listener(None, echo)
+
+ Handle error in setup
+
+ >>> @Sized.handler
+ ... def bad(c):
+ ... raise ValueError
+
+ >>> try: request(async.connect, listener.address, bad, 1)
+ ... except ValueError: pass
+ ... else: print 'oops'
+
+ Handle error in input
+
+ >>> @Sized.handler
+ ... def bad(c):
+ ... c.write('test')
+ ... data = (yield)
+ ... raise ValueError
+
+ >>> try: request(async.connect, listener.address, bad, 1)
+ ... except ValueError: pass
+ ... else: print 'oops'
+
+ Handle error in close
+
+ >>> @Sized.handler
+ ... def bad(c):
+ ... c.write('stop')
+ ... try:
+ ... while 1:
+ ... data = (yield)
+ ... except GeneratorExit:
+ ... raise ValueError
+
+ >>> try: request(async.connect, listener.address, bad, 1)
+ ... except ValueError: pass
+ ... else: print 'oops'
+
+ Handle error in handle_exception arising from error during iteration:
+
+ >>> @Sized.handler
+ ... def bad(c):
+ ... c.writelines(XXX for i in range(2))
+ ... data = (yield)
+
+ >>> try: request(async.connect, listener.address, bad, 1)
+ ... except NameError: pass
+ ... else: print 'oops'
+
+ >>> listener.close()
+ """
+
+def async_handling_iteration_errors():
+ """
+
+ >>> from zc.ngi import async
+ >>> from zc.ngi.adapters import Sized
+ >>> from zc.ngi.blocking import request
+
+ >>> @Sized.handler
+ ... def echo(c):
+ ... while 1:
+ ... data = (yield)
+ ... if data == 'stop': break
+ ... c.write(data)
+
+ >>> listener = async.listener(None, echo)
+
+ Handler with no handle_exception but with a handle close.
+
+ >>> event = threading.Event()
+ >>> class Bad:
+ ... def connected(self, connection):
+ ... connection.setHandler(self)
+ ... connection.writelines(XXX for i in range(2))
+ ... def handle_close(self, connection, reason):
+ ... print 'closed', reason
+ ... event.set()
+
+ >>> zc.ngi.async.connect(listener.address, Bad()); event.wait(1)
+ closed Bad instance has no attribute 'handle_exception'
+
+ >>> listener.close()
+ """
+
class BrokenConnect:
connected = failed_connect = __call__ = lambda: xxxxx
More information about the checkins
mailing list