[Zope-Checkins] CVS: ZODB3/ZEO/tests - forker.py:1.23

Barry Warsaw barry@wooz.org
Thu, 12 Dec 2002 13:58:40 -0500


Update of /cvs-repository/ZODB3/ZEO/tests
In directory cvs.zope.org:/tmp/cvs-serv21476

Modified Files:
	forker.py 
Log Message:
Get rid of the differences between forking off the zeo server for NT
and Unix.  We actually use the Windows code as the basis for the new
approach so there's some hope that this will actually work on Windows
<wink>.  Tim will verify once the checkins are finished.

start_zeo_server(): Is now the blessed way of starting a ZEO server,
and it primarily takes a ZConfig storage section string as its
argument.  This it writes to a temp file and passes -C to the
zeoserver.py script, which parses that temp file and creates the
underlying storage.  zeoserver.py is responsible for deleting the temp
file.

zeoserver opens an `admin' port which is used to quit the server, but
the protocol has been expanded slightly.  The first time the admin
port is connected to, an ack character is sent from zeoserver.  This
is necessary to coordinate the tests so that they are assured the
server is running and responding before it tries to do more.
Otherwise, some tests were vulnerable to timing bugs where the
shutdown connect happened before the server was ready to accept them.

The second connect exits the server process just like before.

start_zeo_server() returns a 3-tuple.  The first element is the
(host,port) tuple of the ZEO server, the second is the (host,port)
tuple of the admin socket, and the third is the pid of the spawned
child process.

shutdown_zeo_server(): The blessed way to shut down a zeoserver.py
process.  Takes an admin (host,port) tuple, connects to that and
writes a log entry.


=== ZODB3/ZEO/tests/forker.py 1.22 => 1.23 ===
--- ZODB3/ZEO/tests/forker.py:1.22	Tue Dec 10 16:45:02 2002
+++ ZODB3/ZEO/tests/forker.py	Thu Dec 12 13:58:39 2002
@@ -15,16 +15,17 @@
 
 import os
 import sys
+import time
+import errno
 import types
 import random
 import socket
 import asyncore
+import tempfile
 import traceback
 from cStringIO import StringIO
 
-import ZEO.ClientStorage
-import ZConfig
-from ZODB import StorageConfig
+import zLOG
 
 # Change value of PROFILE to enable server-side profiling
 PROFILE = 0
@@ -52,130 +53,56 @@
             s.close()
     raise RuntimeError, "Can't find port"
 
-if os.name == "nt":
 
-    # XXX This is probably broken now
-    def start_zeo_server(conf, addr=None, ro_svr=0):
-        """Start a ZEO server in a separate process.
-
-        Returns the ZEO port, the test server port, and the pid.
-        """
-        import ZEO.tests.winserver
-        if addr is None:
-            port = get_port()
-        else:
-            port = addr[1]
-        script = ZEO.tests.winserver.__file__
-        if script.endswith('.pyc'):
-            script = script[:-1]
-        if ro_svr:
-            prefix = (sys.executable, script, "-r")
-        else:
-            prefix = (sys.executable, script)
-        args = prefix + (str(port), storage_name) + args
-        d = os.environ.copy()
-        d['PYTHONPATH'] = os.pathsep.join(sys.path)
-        pid = os.spawnve(os.P_NOWAIT, sys.executable, args, d)
-        return ('localhost', port), ('localhost', port + 1), pid
-
-else:
-
-    class ZEOServerExit(asyncore.file_dispatcher):
-        """Used to exit ZEO.StorageServer when run is done"""
-
-        def writable(self):
-            return 0
-
-        def readable(self):
-            return 1
-
-        def handle_read(self):
-            buf = self.recv(4)
-            if buf:
-                assert buf == "done"
-                server.close_server()
-                asyncore.socket_map.clear()
-
-        def handle_close(self):
-            server.close_server()
-            asyncore.socket_map.clear()
-
-    class ZEOClientExit:
-        """Used by client to cause server to exit"""
-        def __init__(self, pipe):
-            self.pipe = pipe
+def start_zeo_server(conf, addr=None, ro_svr=0):
+    """Start a ZEO server in a separate process.
 
-        def close(self):
-            try:
-                os.write(self.pipe, "done")
-                os.close(self.pipe)
-            except os.error:
-                pass
-
-    def start_zeo_server(conf, addr, ro_svr=0):
-        rd, wr = os.pipe()
-        pid = os.fork()
-        if pid == 0:
-            asyncore.socket_map.clear() # Don't service the parent's sockets
-            import ZEO.zrpc.log
-            reload(ZEO.zrpc.log) # Don't share the logging file object
-            try:
-                if PROFILE:
-                    p = hotshot.Profile("stats.s.%d" % os.getpid())
-                    p.runctx(
-                        "run_server(addr, rd, wr, conf, ro_svr)",
-                        globals(), locals())
-                    p.close()
-                else:
-                    run_server(addr, rd, wr, conf, ro_svr)
-            except:
-                print "Exception in ZEO server process"
-                traceback.print_exc()
-            os._exit(0)
-        else:
-            os.close(rd)
-            return pid, ZEOClientExit(wr)
-
-    def load_storage(conf):
-        fp = StringIO(conf)
-        rootconf = ZConfig.loadfile(fp)
-        storageconf = rootconf.getSection('Storage')
-        return StorageConfig.createStorage(storageconf)
-
-    def run_server(addr, rd, wr, conf, ro_svr):
-        # in the child, run the storage server
-        global server
-        os.close(wr)
-        ZEOServerExit(rd)
-        import ZEO.StorageServer, ZEO.zrpc.server
-        storage = load_storage(conf)
-        server = ZEO.StorageServer.StorageServer(addr, {'1':storage}, ro_svr)
-        ZEO.zrpc.server.loop()
-        storage.close()
-        if isinstance(addr, types.StringType):
-            os.unlink(addr)
-
-    def start_zeo(conf, cache=None, cleanup=None,
-                  domain="AF_INET", storage_id="1", cache_size=20000000):
-        """Setup ZEO client-server for storage.
-
-        Returns a ClientStorage instance and a ZEOClientExit instance.
-
-        XXX Don't know if os.pipe() will work on Windows.
-        """
-
-        if domain == "AF_INET":
-            addr = '', get_port()
-        elif domain == "AF_UNIX":
-            import tempfile
-            addr = tempfile.mktemp()
-        else:
-            raise ValueError, "bad domain: %s" % domain
-
-        pid, exit = start_zeo_server(conf, addr)
-        s = ZEO.ClientStorage.ClientStorage(addr, storage_id,
-                                            client=cache,
-                                            cache_size=cache_size,
-                                            min_disconnect_poll=0.5,
-                                            wait=1)
-        return s, exit, pid
+    Returns the ZEO port, the test server port, and the pid.
+    """
+    # Store the config info in a temp file.
+    tmpfile = tempfile.mktemp()
+    fp = open(tmpfile, 'w')
+    fp.write(conf)
+    fp.close()
+    # Create the server
+    import ZEO.tests.zeoserver
+    if addr is None:
+        port = get_port()
+    else:
+        port = addr[1]
+    script = ZEO.tests.zeoserver.__file__
+    if script.endswith('.pyc'):
+        script = script[:-1]
+    # Create a list of arguments, which we'll tuplify below
+    args = [sys.executable, script, '-C', tmpfile]
+    if ro_svr:
+        args.append('-r')
+    args.append(str(port))
+    d = os.environ.copy()
+    d['PYTHONPATH'] = os.pathsep.join(sys.path)
+    pid = os.spawnve(os.P_NOWAIT, sys.executable, tuple(args), d)
+    adminaddr = ('localhost', port+1)
+    # We need to wait until the server starts, but not forever
+    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+    for i in range(5):
+        try:
+            zLOG.LOG('forker', zLOG.DEBUG, 'connect %s' % i)
+            s.connect(adminaddr)
+            ack = s.recv(1)
+            zLOG.LOG('forker', zLOG.DEBUG, 'acked: %s' % ack)
+            break
+        except socket.error, e:
+            if e[0] <> errno.ECONNREFUSED: raise
+            time.sleep(1)
+    else:
+        zLOG.LOG('forker', zLOG.DEBUG, 'boo hoo')
+        raise
+    return ('localhost', port), adminaddr, pid
+
+
+def shutdown_zeo_server(adminaddr):
+    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+    s.connect(adminaddr)
+    ack = s.recv(1)
+    zLOG.LOG('shutdownServer', zLOG.DEBUG, 'acked: %s' % ack)
+    s.close()