[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