[Checkins] SVN: zc.ngi/trunk/ Ignore stuff.

Jim Fulton jim at zope.com
Tue Jan 1 12:37:54 EST 2008


Log message for revision 82627:
  Ignore stuff.
  

Changed:
  _U  zc.ngi/trunk/
  U   zc.ngi/trunk/src/zc/ngi/README.txt
  U   zc.ngi/trunk/src/zc/ngi/async.py
  U   zc.ngi/trunk/src/zc/ngi/async.txt
  U   zc.ngi/trunk/src/zc/ngi/blocking.py
  U   zc.ngi/trunk/src/zc/ngi/interfaces.py
  U   zc.ngi/trunk/src/zc/ngi/testing.py
  U   zc.ngi/trunk/src/zc/ngi/tests.py
  U   zc.ngi/trunk/src/zc/ngi/wordcount.py

-=-

Property changes on: zc.ngi/trunk
___________________________________________________________________
Name: svn:ignore
   + develop-eggs
documentation.txt
bin
parts


Modified: zc.ngi/trunk/src/zc/ngi/README.txt
===================================================================
--- zc.ngi/trunk/src/zc/ngi/README.txt	2008-01-01 14:46:34 UTC (rev 82626)
+++ zc.ngi/trunk/src/zc/ngi/README.txt	2008-01-01 17:37:53 UTC (rev 82627)
@@ -403,14 +403,41 @@
     ...
     TypeError: Connection closed
 
-Peer connectors
-===============
+Connecting servers and clients
+==============================
 
 It is sometimes useful to connect a client handler and a server
-handler.  The zc.ngi.testing.peer function can be used to create a 
-connection to a peer handler. To illustrate, we'll set up an echo
-client that connects to our echo server:
+handler.  Listeners created with the zc.ngi.testing.listener class have a
+connector method that can be used to create connections to a server.
 
+Let's connect out echo server and client. First, we'll create out
+server using the listener constructor:
+
+    >>> listener = zc.ngi.testing.listener(EchoServer)
+
+Then we'll use the connector method on the listener:
+
+    >>> client = EchoClient(listener.connector)
+    >>> client.check(('localhost', 42), ['hello', 'world', 'how are you?'])
+    server connected
+    server got input: 'hello\n'
+    server got input: 'world\n'
+    server got input: 'how are you?\n'
+    got input: 'hello\nworld\nhow are you?\n'
+    matched: hello
+    matched: world
+    matched: how are you?
+    server closed: closed
+
+.. Peer connectors
+
+  Below is an older API for connecting servers and clients in a
+  testing environment.  The mechanisms defined above are prefered.
+
+  The zc.ngi.testing.peer function can be used to create a 
+  connection to a peer handler. To illustrate, we'll set up an echo
+  client that connects to our echo server:
+
     >>> client = EchoClient(zc.ngi.testing.peer(('localhost', 42), EchoServer))
     >>> client.check(('localhost', 42), ['hello', 'world', 'how are you?'])
     server connected

Modified: zc.ngi/trunk/src/zc/ngi/async.py
===================================================================
--- zc.ngi/trunk/src/zc/ngi/async.py	2008-01-01 14:46:34 UTC (rev 82626)
+++ zc.ngi/trunk/src/zc/ngi/async.py	2008-01-01 17:37:53 UTC (rev 82627)
@@ -58,9 +58,10 @@
         try:
             self.handle_close(reason)
         except:
-            self.logger.exception("Exception raised by handle_close(%r)",
-                                  reason)
-        self.close()
+            self.logger.exception(
+                "Exception raised by dispatcher handle_close(%r)",
+                reason)
+            self.close()
 
     def close(self):
         self.del_channel(_map)
@@ -93,9 +94,19 @@
         if self.__exception:
             exception = self.__exception
             self.__exception = None
-            handler.handle_exception(self, exception)
+            try:
+                handler.handle_exception(self, exception)
+            except:
+                self.logger.exception("handle_exception failed")
+                return self.handle_close("handle_exception failed")
+                
         if self.__closed:
-            handler.handle_close(self, self.__closed)
+            try:
+                handler.handle_close(self, self.__closed)
+            except:
+                self.logger.exception("Exception raised by handle_close(%r)",
+                                      self.__closed)
+                raise
         
     def write(self, data):
         if __debug__:
@@ -141,7 +152,12 @@
 
             if __debug__:
                 self.logger.debug('input %r', d)
-            self.__handler.handle_input(self, d)
+            try:
+                self.__handler.handle_input(self, d)
+            except:
+                self.logger.exception("handle_input failed")
+                self.handle_close("handle_input failed")
+                
             if len(d) < 8192:
                 break
 
@@ -194,7 +210,11 @@
 
     def __report_exception(self, exception):
         if self.__handler is not None:
-            self.__handler.handle_exception(self, exception)
+            try:
+                self.__handler.handle_exception(self, exception)
+            except:
+                self.logger.exception("handle_exception failed")
+                self.handle_close("handle_exception failed")
         else:
             self.__exception = exception
             
@@ -202,7 +222,11 @@
         if __debug__:
             self.logger.debug('close %r', reason)
         if self.__handler is not None:
-            self.__handler.handle_close(self, reason)
+            try:
+                self.__handler.handle_close(self, reason)
+            except:
+                self.logger.exception("Exception raised by handle_close(%r)",
+                                      reason)
         else:
             self.__closed = reason
         self.close()
@@ -261,7 +285,10 @@
     def handle_close(self, reason=None):
         if __debug__:
             self.logger.debug('connector close %r', reason)
-        self.__handler.failed_connect(reason)
+        try:
+            self.__handler.failed_connect(reason)
+        except:
+            self.logger.exception("failed_connect(%r) failed", reason)
         self.close()
  
     def handle_write_event(self):
@@ -280,7 +307,11 @@
             self.logger.debug('outgoing connected %r', self.addr)
 
         connection = _Connection(self.socket, self.addr, self.logger)
-        self.__handler.connected(connection)
+        try:
+            self.__handler.connected(connection)
+        except:
+            self.logger.exception("connection handler failed")
+            connection.handle_close("connection handler failed")
         return
 
     def handle_error(self):
@@ -337,7 +368,11 @@
         connection = _Connection(sock, addr, self.logger)
         self.__connections[connection] = 1
         connection.control = self
-        self.__handler(connection)
+        try:
+            self.__handler(connection)
+        except:
+            self.logger.exception("server handler failed")
+            self.close()
 
     def connections(self):
         return iter(self.__connections)

Modified: zc.ngi/trunk/src/zc/ngi/async.txt
===================================================================
--- zc.ngi/trunk/src/zc/ngi/async.txt	2008-01-01 14:46:34 UTC (rev 82626)
+++ zc.ngi/trunk/src/zc/ngi/async.txt	2008-01-01 17:37:53 UTC (rev 82627)
@@ -68,7 +68,7 @@
 .. Error handling:
 
    If we pass a non-iterable to writelines, we'll get an immediate
-   error.  To demonstrate this we'll violate out output file and
+   error.  To demonstrate this we'll violate our output file and
    access it's _connection attribute so that we can bypass the check
    in the blocking writelines method:
 
@@ -83,7 +83,7 @@
     AssertionError: writelines does not accept strings
 
    If we pass an iterable that returns a non-string, we'll get a type
-   error when we try to read because handle_exception is caused ion
+   error when we try to read because handle_exception is called in
    the input handler.
 
     >>> output.writelines([2], timeout=0.1)
@@ -96,7 +96,86 @@
     ...
     TypeError: ('iterable output returned a non-string', 2)
 
+  If there is an error, then the connection is closed:
 
+    >>> input.read()
+    ''
+
+    >>> output.write('hello')
+    Traceback (most recent call last):
+    ...
+    IOError: I/O operation on closed file
+  
+  Handler errors cause connections to be closed.  To see this, we'll
+  send the server an error message, which foreces an error:
+
+    >>> output, input = zc.ngi.blocking.open(addr, zc.ngi.async.connector,
+    ...                                      timeout=1.0)
+    >>> output.write('E\0')
+    >>> input.read()
+    ''
+
+  Let's create some lame clients:
+
+    >>> import zope.testing.loggingsupport, logging
+    >>> loghandler = zope.testing.loggingsupport.InstalledHandler(
+    ...                  None, level=logging.ERROR)
+
+    >>> event = threading.Event()
+    >>> class LameClientConnectionHandler:
+    ...     def connected(self, connection):
+    ...         connection.setHandler(self)
+    ...         raise ValueError('Broken connector')
+    ...     def handle_close(self, conection, reason):
+    ...         self.closed = reason
+    ...         event.set()
+    
+    >>> handler = LameClientConnectionHandler()
+    >>> _ = zc.ngi.async.connector(addr, handler)
+    >>> event.wait(1)
+
+    >>> print loghandler
+    zc.ngi.async.client ERROR
+      connection handler failed
+
+    >>> handler.closed
+    'connection handler failed'
+
+
+    >>> loghandler.clear()
+    >>> class LameClientConnectionHandler:
+    ...     def connected(self, connection):
+    ...         connection.setHandler(self)
+    ...         connection.write('foo\0')
+    ...
+    ...     def handle_input(self, data):
+    ...         raise ValueError()         
+    ...
+    ...     def handle_close(self, conection, reason):
+    ...         self.closed = reason
+    ...         event.set()
+    >>> handler = LameClientConnectionHandler()
+    >>> _ = zc.ngi.async.connector(addr, handler)
+    >>> event.wait(1)
+
+    >>> print loghandler
+    zc.ngi.async.client ERROR
+      handle_input failed
+
+    >>> handler.closed
+    'handle_input failed'
+
+    >>> loghandler.uninstall()
+
+
 .. stop the server
 
     >>> zc.ngi.wordcount.stop_server_process(zc.ngi.async.connector, addr)
+    ... # doctest: +ELLIPSIS
+    handle_input failed
+    Traceback (most recent call last):
+    ...
+    ValueError: E
+
+   The server log was printed. Note that we see the Error that we
+   requested above.

Modified: zc.ngi/trunk/src/zc/ngi/blocking.py
===================================================================
--- zc.ngi/trunk/src/zc/ngi/blocking.py	2008-01-01 14:46:34 UTC (rev 82626)
+++ zc.ngi/trunk/src/zc/ngi/blocking.py	2008-01-01 17:37:53 UTC (rev 82627)
@@ -61,15 +61,16 @@
     if connector is None:
         connection = connection_or_address
     else:
-        connection = connect(connection_or_address, connector, timeout) 
-    return OutputFile(connection), InputFile(connection)
+        connection = connect(connection_or_address, connector, timeout)
 
+    outputfile = OutputFile(connection)
+    return outputfile, InputFile(connection, outputfile)
+
 class _BaseFile:
 
     def __init__(self, connection):
         self._connection = connection
         self._position = 0
-        self._closed = False
 
     def seek(self, offset, whence=0):
         position = self._position
@@ -88,9 +89,10 @@
     def tell(self):
         return self._position
 
+    _closed = False
     def _check_open(self):
         if self._closed:
-            raise ValueError("I/O operation on closed file")
+            raise IOError("I/O operation on closed file")
 
 class OutputFile(_BaseFile):
 
@@ -100,7 +102,7 @@
     read = readline = readlines = invalid_method
 
     def flush(self):
-        pass
+        self._check_exception()
 
     def close(self):
         if not self._closed:
@@ -108,12 +110,14 @@
         self._closed = True
             
     def write(self, data):
+        self._check_exception()
         self._check_open()
         assert isinstance(data, str)
         self._position += len(data)
         self._connection.write(data)
             
     def writelines(self, data, timeout=None, nonblocking=False):
+        self._check_exception()
         self._check_open()
         if nonblocking:
             self._connection.writelines(iter(data))
@@ -126,9 +130,14 @@
         event.wait(timeout)
         if not event.isSet():
             raise Timeout()
-        
-    
 
+    _exception = None
+    def _check_exception(self):
+        if self._exception is not None:
+            exception = self._exception
+            self._exception = None
+            raise exception
+
 class _writelines_iterator:
 
     def __init__(self, base, file, notify):
@@ -150,11 +159,12 @@
 
 class InputFile(_BaseFile):
 
-    def __init__(self, connection):
+    def __init__(self, connection, outputfile):
         _BaseFile.__init__(self, connection)
         self._condition = threading.Condition()
         self._data = ''
-        self._exception = None
+        self._outputfile = outputfile
+        self._outputfile._exception = None
         connection.setHandler(self)
 
     def invalid_method(*args, **kw):
@@ -173,7 +183,7 @@
         condition = self._condition
         condition.acquire()
         try:
-            self._closed = True
+            self._closed = self._outputfile._closed = True
             condition.notifyAll()
         finally:
             condition.release()
@@ -182,7 +192,7 @@
         condition = self._condition
         condition.acquire()
         try:
-            self._exception = exception
+            self._outputfile._exception = exception
             condition.notifyAll()
         finally:
             condition.release()
@@ -191,7 +201,7 @@
         condition = self._condition
         condition.acquire()
         try:
-            self._closed = True
+            self._closed = self._outputfile._closed = True
             self._connection.close()
             condition.notifyAll()
         finally:
@@ -211,7 +221,7 @@
         condition = self._condition
         condition.acquire()
         try:
-            self._check_exception()
+            self._outputfile._check_exception()
             while 1:
                 data = self._data
                 if size is not None and size <= len(data):
@@ -234,7 +244,7 @@
         condition = self._condition
         condition.acquire()
         try:
-            self._check_exception()
+            self._outputfile._check_exception()
             while 1:
                 data = self._data
                 l = data.find('\n')
@@ -264,7 +274,7 @@
         condition = self._condition
         condition.acquire()
         try:
-            self._check_exception()
+            self._outputfile._check_exception()
             while 1:
                 data = self._data
                 if sizehint is not None and sizehint <= len(data):
@@ -281,12 +291,6 @@
         finally:
             condition.release()
 
-    def _check_exception(self):
-        if self._exception is not None:
-            exception = self._exception
-            self._exception = None
-            raise exception
-
     def _wait(self, timeout, deadline):
         if timeout is not None:
             if deadline is None:
@@ -301,7 +305,7 @@
         else:
             self._condition.wait()
 
-        self._check_exception()
+        self._outputfile._check_exception()
         
         return timeout, deadline
         

Modified: zc.ngi/trunk/src/zc/ngi/interfaces.py
===================================================================
--- zc.ngi/trunk/src/zc/ngi/interfaces.py	2008-01-01 14:46:34 UTC (rev 82626)
+++ zc.ngi/trunk/src/zc/ngi/interfaces.py	2008-01-01 17:37:53 UTC (rev 82627)
@@ -22,7 +22,8 @@
 register handlers that respond to input events.  There are 3 kinds of
 handlers:
 
-- Input handlers receive network input
+- Input handlers receive network input and notification of connection
+  closes and exceptions,
 
 - Client-connect handlers respond to outbound connection events, and
 
@@ -36,6 +37,10 @@
   Theoretically, different implementations could call handlers at the
   same time.)
 
+  Note that when an application calls setHandler on a connection, the
+  connection handler may have it's methods called immediately with
+  pending input or notifications.
+
 - All handler calls that are associated with a connection include the
   connection as a parameter,  This allows a single handler object to
   respond to events from multiple connections.
@@ -71,7 +76,11 @@
 
         This method can only be called in direct response to an
         implementation call to a IConnectionHandler,
-        IClientConnectHandler, or IServer
+        IClientConnectHandler, or IServer.
+
+        Any failure of a handler call must be caught and logged.  If
+        an exception is raised by a call to hande_input or
+        handle_exception, the connection must be closed.
         """
 
     def write(data):

Modified: zc.ngi/trunk/src/zc/ngi/testing.py
===================================================================
--- zc.ngi/trunk/src/zc/ngi/testing.py	2008-01-01 14:46:34 UTC (rev 82626)
+++ zc.ngi/trunk/src/zc/ngi/testing.py	2008-01-01 17:37:53 UTC (rev 82627)
@@ -16,6 +16,8 @@
 $Id$
 """
 
+import sys
+import traceback
 import zc.ngi
 
 class PrintingHandler:
@@ -57,6 +59,27 @@
     def __nonzero__(self):
         return not self.closed
 
+    queue = None
+    def _callHandler(self, method, *args):
+        if self.queue is None:
+            self.queue = [(method, args)]
+            while self.queue:
+                method, args = self.queue.pop(0)
+                if self.closed and method != 'handle_close':
+                    break
+                try:
+                    getattr(self.handler, method)(self, *args)
+                except:
+                    print "Error test connection calling connection handler:"
+                    traceback.print_exc(file=sys.stdout)
+                    if method != 'handle_close':
+                        self.close()
+                        self.handler.handle_close(self, method+' error')
+                    
+            self.queue = None
+        else:
+            self.queue.append((method, args))
+
     def close(self):
         self.peer.test_close('closed')
         if self.control is not None:
@@ -71,19 +94,19 @@
         if self.exception:
             exception = self.exception
             self.exception = None
-            handler.handle_exception(self, exception)
+            self._callHandler('handle_exception', exception)
         if self.input:
-            handler.handle_input(self, self.input)
+            self._callHandler('handle_input', self.input)
             self.input = ''
 
         # Note is self.closed is True, we self closed and we
         # don't want to call handle_close.
         if self.closed and isinstance(self.closed, str):
-            handler.handle_close(self, self.closed)
+            self._callHandler('handle_close', self.closed)
 
     def test_input(self, data):
         if self.handler is not None:
-            self.handler.handle_input(self, data)
+            self._callHandler('handle_input', data)
         else:
             self.input += data
 
@@ -92,7 +115,7 @@
             self.control.closed(self)
         self.closed = reason
         if self.handler is not None:
-            self.handler.handle_close(self, reason)
+            self._callHandler('handle_close', reason)
 
     def write(self, data):
         if data is zc.ngi.END_OF_DATA:
@@ -118,7 +141,7 @@
         if self.handler is None:
             self.exception = exception
         else:
-            self.handler.handle_exception(self, exception)
+            self._callHandler('handle_exception', exception)
 
 class TextPrintingHandler(PrintingHandler):
 
@@ -176,6 +199,10 @@
         if not self._connections and self._close_handler:
             self._close_handler(self)
 
+    def connector(self, addr, handler):
+        handler.connected(Connection(None, self._handler))
+        
+
 class peer:
 
     def __init__(self, addr, handler):

Modified: zc.ngi/trunk/src/zc/ngi/tests.py
===================================================================
--- zc.ngi/trunk/src/zc/ngi/tests.py	2008-01-01 14:46:34 UTC (rev 82626)
+++ zc.ngi/trunk/src/zc/ngi/tests.py	2008-01-01 17:37:53 UTC (rev 82627)
@@ -106,6 +106,7 @@
     return unittest.TestSuite([
         doctest.DocFileSuite(
             'README.txt',
+            'testing.test',
             'message.txt',
             'adapters.txt',
             'blocking.txt',

Modified: zc.ngi/trunk/src/zc/ngi/wordcount.py
===================================================================
--- zc.ngi/trunk/src/zc/ngi/wordcount.py	2008-01-01 14:46:34 UTC (rev 82626)
+++ zc.ngi/trunk/src/zc/ngi/wordcount.py	2008-01-01 17:37:53 UTC (rev 82627)
@@ -59,6 +59,8 @@
             elif data == 'C':
                 connection.write(zc.ngi.END_OF_DATA)
                 return
+            elif data == 'E':
+                raise ValueError(data)
             else:
                 cc = len(data)
                 lc = len(data.split('\n'))-1
@@ -72,6 +74,8 @@
 def serve():
     mod, name, port, level = sys.argv[1:]
     __import__(mod)
+    logging.getLogger().addHandler(
+        logging.StreamHandler(open('server.log', 'w')))
     logger.setLevel(int(level))
     logger.addHandler(logging.StreamHandler())
     logger.info('serving')
@@ -120,7 +124,7 @@
         else:
             print "Server still accepting connections"
 
-def start_server_process(listener):
+def start_server_process(listener, loglevel=None):
     """Start a server in a subprocess and return the port used
     """
     module = listener.__module__
@@ -130,8 +134,10 @@
         os.environ,
         PYTHONPATH=os.pathsep.join(sys.path),
         )
+    if loglevel is None:
+        loglevel = logger.getEffectiveLevel()
     os.spawnle(os.P_NOWAIT, sys.executable, sys.executable, __file__,
-               module, name, str(port), str(logger.getEffectiveLevel()),
+               module, name, str(port), str(loglevel),
                env)
     addr = 'localhost', port
     wait(addr)
@@ -140,6 +146,9 @@
 def stop_server_process(connector, addr):
     zc.ngi.message.message(connector, addr, 'Q\0', lambda s: s == 'Q\n')
     wait(addr, up=False)
+    log = open('server.log').read()
+    os.remove('server.log')
+    print log,
 
 sample_docs = [
 """Hello world



More information about the Checkins mailing list