[Zope3-checkins] CVS: Zope3/lib/python/Zope/Server/Thread - SelectTrigger.py:1.4

Jeremy Hylton jeremy@zope.com
Fri, 20 Dec 2002 17:28:43 -0500


Update of /cvs-repository/Zope3/lib/python/Zope/Server/Thread
In directory cvs.zope.org:/tmp/cvs-serv2154/Server/Thread

Modified Files:
	SelectTrigger.py 
Log Message:
Copy trigger improvements from duplicate version in ZEO/zrpc.
Convert to use traceback module instead of asyncore compact tback.


=== Zope3/lib/python/Zope/Server/Thread/SelectTrigger.py 1.3 => 1.4 ===
--- Zope3/lib/python/Zope/Server/Thread/SelectTrigger.py:1.3	Wed Oct  2 15:29:46 2002
+++ Zope3/lib/python/Zope/Server/Thread/SelectTrigger.py	Fri Dec 20 17:28:41 2002
@@ -22,6 +22,7 @@
 import socket
 import string
 import thread
+import traceback
 
 if os.name == 'posix':
 
@@ -57,131 +58,151 @@
         # new data onto a channel's outgoing data queue at the same time that
         # the main thread is trying to remove some]
 
-        def __init__ (self):
-            r, w = os.pipe()
+        def __init__(self):
+            r, w = self._fds = os.pipe()
             self.trigger = w
-            asyncore.file_dispatcher.__init__ (self, r)
+            asyncore.file_dispatcher.__init__(self, r)
             self.lock = thread.allocate_lock()
             self.thunks = []
+            self._closed = 0
 
-        def __repr__ (self):
+        # Override the asyncore close() method, because it seems that
+        # it would only close the r file descriptor and not w.  The
+        # constructor calls file_dispatcher.__init__ and passes r,
+        # which would get stored in a file_wrapper and get closed by
+        # the default close.  But that would leave w open...
+
+        def close(self):
+            if not self._closed:
+                self._closed = 1
+                self.del_channel()
+                for fd in self._fds:
+                    os.close(fd)
+                self._fds = []
+
+        def __repr__(self):
             return '<select-trigger (pipe) at %x>' % id(self)
 
-        def readable (self):
+        def readable(self):
             return 1
 
-        def writable (self):
+        def writable(self):
             return 0
 
-        def handle_connect (self):
+        def handle_connect(self):
             pass
 
-        def pull_trigger (self, thunk=None):
-                # print 'PULL_TRIGGER: ', len(self.thunks)
+        def pull_trigger(self, thunk=None):
             if thunk:
+                self.lock.acquire()
                 try:
-                    self.lock.acquire()
-                    self.thunks.append (thunk)
+                    self.thunks.append(thunk)
                 finally:
                     self.lock.release()
-            os.write (self.trigger, 'x')
+            os.write(self.trigger, 'x')
 
-        def handle_read (self):
-            self.recv (8192)
+        def handle_read(self):
+            try:
+                self.recv(8192)
+            except socket.error:
+                return
+            self.lock.acquire()
             try:
-                self.lock.acquire()
                 for thunk in self.thunks:
                     try:
                         thunk()
                     except:
-                        (file, fun, line), t, v, tbinfo = \
-                               asyncore.compact_traceback()
-                        print 'exception in trigger thunk: (%s:%s %s)' % (
-                            t, v, tbinfo)
+                        L = traceback.format_exception(*sys.exc_info())
+                        print 'exception in trigger thunk:\n%s' % "".join(L)
                 self.thunks = []
             finally:
                 self.lock.release()
 
 else:
-
+    # XXX Should define a base class that has the common methods and
+    # then put the platform-specific in a subclass named trigger.
 
     # win32-safe version
 
-    # XXX The corresponding ZEO2 code (ZEO/zrpc/trigger.py) has a fix
-    # for Win98 hangs here.  Those changes should probably be applied
-    # here too.
+    HOST = '127.0.0.1'
+    MINPORT = 19950
+    NPORTS = 50
 
-    class Trigger (asyncore.dispatcher):
+    class trigger(asyncore.dispatcher):
 
-        address = ('127.9.9.9', 19999)
+        portoffset = 0
 
-        def __init__ (self):
-            a = socket.socket (socket.AF_INET, socket.SOCK_STREAM)
-            w = socket.socket (socket.AF_INET, socket.SOCK_STREAM)
+        def __init__(self):
+            a = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+            w = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 
             # set TCP_NODELAY to true to avoid buffering
             w.setsockopt(socket.IPPROTO_TCP, 1, 1)
 
             # tricky: get a pair of connected sockets
-            host='127.0.0.1'
-            port=19999
-            while 1:
+            for i in range(NPORTS):
+                trigger.portoffset = (trigger.portoffset + 1) % NPORTS
+                port = MINPORT + trigger.portoffset
+                address = (HOST, port)
                 try:
-                    self.address=(host, port)
-                    a.bind(self.address)
+                    a.bind(address)
+                except socket.error:
+                    continue
+                else:
                     break
-                except:
-                    if port <= 19950:
-                        raise 'Bind Error', 'Cannot bind trigger!'
-                    port=port - 1
+            else:
+                raise RuntimeError, 'Cannot bind trigger!'
 
-            a.listen (1)
-            w.setblocking (0)
+            a.listen(1)
+            w.setblocking(0)
             try:
-                w.connect (self.address)
+                w.connect(address)
             except:
                 pass
             r, addr = a.accept()
             a.close()
-            w.setblocking (1)
+            w.setblocking(1)
             self.trigger = w
 
-            asyncore.dispatcher.__init__ (self, r)
+            asyncore.dispatcher.__init__(self, r)
             self.lock = thread.allocate_lock()
             self.thunks = []
             self._trigger_connected = 0
 
-        def __repr__ (self):
+        def __repr__(self):
             return '<select-trigger (loopback) at %x>' % id(self)
 
-        def readable (self):
+        def readable(self):
             return 1
 
-        def writable (self):
+        def writable(self):
             return 0
 
-        def handle_connect (self):
+        def handle_connect(self):
             pass
 
-        def pull_trigger (self, thunk=None):
+        def pull_trigger(self, thunk=None):
             if thunk:
+                self.lock.acquire()
                 try:
-                    self.lock.acquire()
-                    self.thunks.append (thunk)
+                    self.thunks.append(thunk)
                 finally:
                     self.lock.release()
-            self.trigger.send ('x')
+            self.trigger.send('x')
 
-        def handle_read (self):
-            self.recv (8192)
+        def handle_read(self):
+            try:
+                self.recv(8192)
+            except socket.error:
+                return
+            self.lock.acquire()
             try:
-                self.lock.acquire()
                 for thunk in self.thunks:
                     try:
                         thunk()
                     except:
-                        (file, fun, line), t, v, tbinfo = asyncore.compact_traceback()
-                        print 'exception in trigger thunk: (%s:%s %s)' % (t, v, tbinfo)
+                        L = traceback.format_exception(*sys.exc_info())
+                        print 'exception in trigger thunk:\n%s' % "".join(L)
                 self.thunks = []
             finally:
                 self.lock.release()
@@ -210,37 +231,28 @@
                     )
 
     def writeline (self, line):
-        self.write (line+'\r\n')
+        self.write(line + '\r\n')
 
     def writelines (self, lines):
-        self.write (
-                string.joinfields (
-                        lines,
-                        '\r\n'
-                        ) + '\r\n'
-                )
+        self.write("\r\n".join(lines) + "\r\n")
 
     def flush (self):
         if self.buffer:
             d, self.buffer = self.buffer, ''
-            the_trigger.pull_trigger (
-                    lambda p=self.parent,d=d: p.push (d)
-                    )
+            the_trigger.pull_trigger(lambda: self.parent.push(d))
 
     def softspace (self, *args):
         pass
 
     def close (self):
-            # in a derived class, you may want to call trigger_close() instead.
+        # in a derived class, you may want to call trigger_close() instead.
         self.flush()
         self.parent = None
 
     def trigger_close (self):
         d, self.buffer = self.buffer, ''
         p, self.parent = self.parent, None
-        the_trigger.pull_trigger (
-                lambda p=p,d=d: (p.push(d), p.close_when_done())
-                )
+        the_trigger.pull_trigger(lambda: (p.push(d), p.close_when_done()))
 
 if __name__ == '__main__':