[Zope3-checkins] CVS: ZODB4/src/zodb/zeo/tests - invalid.py:1.1.2.1 zeoserver.py:1.8.30.1 test_zeo.py:1.12.6.1 test_conn.py:1.5.6.1 forker.py:1.5.30.1

Jeremy Hylton jeremy@zope.com
Tue, 17 Jun 2003 17:59:55 -0400


Update of /cvs-repository/ZODB4/src/zodb/zeo/tests
In directory cvs.zope.org:/tmp/cvs-serv10995/src/zodb/zeo/tests

Modified Files:
      Tag: ZODB3-2-merge
	zeoserver.py test_zeo.py test_conn.py forker.py 
Added Files:
      Tag: ZODB3-2-merge
	invalid.py 
Log Message:
Checkpoint progress merging ZODB 3.2 features and fixes into ZODB4.


=== Added File ZODB4/src/zodb/zeo/tests/invalid.py ===
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################

from thread import get_ident
import threading
import time

from zodb.btrees.check import check, display
from zodb.btrees.OOBTree import OOBTree
from zodb.db import DB
from zodb.interfaces import ReadConflictError, ConflictError, VersionLockError

from zodb.zeo.tests.thread import TestThread
from zodb.zeo.tests.connection import CommonSetupTearDown

import logging

# XXX stopped porting here

# The tests here let several threads have a go at one or more database
# instances simultaneously.  Each thread appends a disjoint (from the
# other threads) sequence of increasing integers to an OOBTree, one at
# at time (per thread).  This provokes lots of conflicts, and BTrees
# work hard at conflict resolution too.  An OOBTree is used because
# that flavor has the smallest maximum bucket size, and so splits buckets
# more often than other BTree flavors.
#
# When these tests were first written, they provoked an amazing number
# of obscure timing-related bugs in cache consistency logic, revealed
# by failure of the BTree to pass internal consistency checks at the end,
# and/or by failure of the BTree to contain all the keys the threads
# thought they added (i.e., the keys for which get_transaction().commit()
# did not raise any exception).

class StressThread(TestThread):

    # Append integers startnum, startnum + step, startnum + 2*step, ...
    # to 'tree' until Event stop is set.  If sleep is given, sleep
    # that long after each append.  At the end, instance var .added_keys
    # is a list of the ints the thread believes it added successfully.
    def __init__(self, testcase, db, stop, threadnum, startnum,
                 step=2, sleep=None):
        TestThread.__init__(self, testcase)
        self.db = db
        self.stop = stop
        self.threadnum = threadnum
        self.startnum = startnum
        self.step = step
        self.sleep = sleep
        self.added_keys = []

    def testrun(self):
        cn = self.db.open()
        while not self.stop.isSet():
            try:
                tree = cn.root()["tree"]
                break
            except (ConflictError, KeyError):
                get_transaction().abort()
                cn.sync()
        key = self.startnum
        while not self.stop.isSet():
            try:
                tree[key] = self.threadnum
                get_transaction().note("add key %s" % key)
                get_transaction().commit()
                if self.sleep:
                    time.sleep(self.sleep)
            except (ReadConflictError, ConflictError), msg:
                get_transaction().abort()
                # sync() is necessary here to process invalidations
                # if we get a read conflict.  In the read conflict case,
                # no objects were modified so cn never got registered
                # with the transaction.
                cn.sync()
            else:
                self.added_keys.append(key)
            key += self.step
        cn.close()

class VersionStressThread(TestThread):

    def __init__(self, testcase, db, stop, threadnum, startnum,
                 step=2, sleep=None):
        TestThread.__init__(self, testcase)
        self.db = db
        self.stop = stop
        self.threadnum = threadnum
        self.startnum = startnum
        self.step = step
        self.sleep = sleep
        self.added_keys = []

    def log(self, msg):
        zLOG.LOG("thread %d" % get_ident(), 0, msg)

    def testrun(self):
        self.log("thread begin")
        commit = 0
        key = self.startnum
        while not self.stop.isSet():
            version = "%s:%s" % (self.threadnum, key)
            commit = not commit
            self.log("attempt to add key=%s version=%s commit=%d" %
                     (key, version, commit))
            if self.oneupdate(version, key, commit):
                self.added_keys.append(key)
            key += self.step

    def oneupdate(self, version, key, commit=1):
        # The mess of sleeps below were added to reduce the number
        # of VersionLockErrors, based on empirical observation.
        # It looks like the threads don't switch enough without
        # the sleeps.

        cn = self.db.open(version)
        while not self.stop.isSet():
            try:
                tree = cn.root()["tree"]
                break
            except (ConflictError, KeyError):
                get_transaction().abort()
                cn.sync()
        while not self.stop.isSet():
            try:
                tree[key] = self.threadnum
                get_transaction().note("add key %d" % key)
                get_transaction().commit()
                if self.sleep:
                    time.sleep(self.sleep)
                break
            except (VersionLockError, ReadConflictError, ConflictError), msg:
                self.log(msg)
                get_transaction().abort()
                # sync() is necessary here to process invalidations
                # if we get a read conflict.  In the read conflict case,
                # no objects were modified so cn never got registered
                # with the transaction.
                cn.sync()
                if self.sleep:
                    time.sleep(self.sleep)
        try:
            while not self.stop.isSet():
                try:
                    if commit:
                        self.db.commitVersion(version)
                        get_transaction().note("commit version %s" % version)
                    else:
                        self.db.abortVersion(version)
                        get_transaction().note("abort version %s" % version)
                    get_transaction().commit()
                    if self.sleep:
                        time.sleep(self.sleep)
                    return commit
                except ConflictError, msg:
                    self.log(msg)
                    get_transaction().abort()
                    cn.sync()
        finally:
            cn.close()
        return 0

class InvalidationTests(CommonSetupTearDown):

    level = 2
    DELAY = 15  # number of seconds the main thread lets the workers run

    def _check_tree(self, cn, tree):
        # Make sure the BTree is sane and that all the updates persisted.
        retries = 3
        while retries:
            retries -= 1
            try:
                check(tree)
                tree._check()
            except ReadConflictError:
                if retries:
                    get_transaction().abort()
                    cn.sync()
                else:
                    raise
            except:
                display(tree)
                raise

    def _check_threads(self, tree, *threads):
        # Make sure the thread's view of the world is consistent with
        # the actual database state.
        all_keys = []
        for t in threads:
            # If the test didn't add any keys, it didn't do what we expected.
            self.assert_(t.added_keys)
            for key in t.added_keys:
                self.assert_(tree.has_key(key), key)
            all_keys.extend(t.added_keys)
        all_keys.sort()
        self.assertEqual(all_keys, list(tree.keys()))

    def go(self, stop, *threads):
        # Run the threads
        for t in threads:
            t.start()
        time.sleep(self.DELAY)
        stop.set()
        for t in threads:
            t.cleanup()

    def testConcurrentUpdates2Storages(self):
        self._storage = storage1 = self.openClientStorage()
        storage2 = self.openClientStorage(cache="2")
        db1 = DB(storage1)
        db2 = DB(storage2)
        stop = threading.Event()

        cn = db1.open()
        tree = cn.root()["tree"] = OOBTree()
        get_transaction().commit()

        # Run two threads that update the BTree
        t1 = StressThread(self, db1, stop, 1, 1)
        t2 = StressThread(self, db2, stop, 2, 2)
        self.go(stop, t1, t2)

        cn.sync()
        self._check_tree(cn, tree)
        self._check_threads(tree, t1, t2)

        cn.close()
        db1.close()
        db2.close()

    def testConcurrentUpdates1Storage(self):
        self._storage = storage1 = self.openClientStorage()
        db1 = DB(storage1)
        stop = threading.Event()

        cn = db1.open()
        tree = cn.root()["tree"] = OOBTree()
        get_transaction().commit()

        # Run two threads that update the BTree
        t1 = StressThread(self, db1, stop, 1, 1, sleep=0.001)
        t2 = StressThread(self, db1, stop, 2, 2, sleep=0.001)
        self.go(stop, t1, t2)

        cn.sync()
        self._check_tree(cn, tree)
        self._check_threads(tree, t1, t2)

        cn.close()
        db1.close()

    def testConcurrentUpdates2StoragesMT(self):
        self._storage = storage1 = self.openClientStorage()
        db1 = DB(storage1)
        stop = threading.Event()

        cn = db1.open()
        tree = cn.root()["tree"] = OOBTree()
        get_transaction().commit()

        db2 = DB(self.openClientStorage(cache="2"))
        # Run three threads that update the BTree.
        # Two of the threads share a single storage so that it
        # is possible for both threads to read the same object
        # at the same time.

        t1 = StressThread(self, db1, stop, 1, 1, 3)
        t2 = StressThread(self, db2, stop, 2, 2, 3, 0.001)
        t3 = StressThread(self, db2, stop, 3, 3, 3, 0.001)
        self.go(stop, t1, t2, t3)

        cn.sync()
        self._check_tree(cn, tree)
        self._check_threads(tree, t1, t2, t3)

        cn.close()
        db1.close()
        db2.close()

    def testConcurrentUpdatesInVersions(self):
        self._storage = storage1 = self.openClientStorage()
        db1 = DB(storage1)
        db2 = DB(self.openClientStorage(cache="2"))
        stop = threading.Event()

        cn = db1.open()
        tree = cn.root()["tree"] = OOBTree()
        get_transaction().commit()

        # Run three threads that update the BTree.
        # Two of the threads share a single storage so that it
        # is possible for both threads to read the same object
        # at the same time.

        t1 = VersionStressThread(self, db1, stop, 1, 1, 3)
        t2 = VersionStressThread(self, db2, stop, 2, 2, 3, 0.001)
        t3 = VersionStressThread(self, db2, stop, 3, 3, 3, 0.001)
        self.go(stop, t1, t2, t3)

        cn.sync()
        self._check_tree(cn, tree)
        self._check_threads(tree, t1, t2, t3)

        cn.close()
        db1.close()
        db2.close()



=== ZODB4/src/zodb/zeo/tests/zeoserver.py 1.8 => 1.8.30.1 ===
--- ZODB4/src/zodb/zeo/tests/zeoserver.py:1.8	Tue Feb 25 13:55:05 2003
+++ ZODB4/src/zodb/zeo/tests/zeoserver.py	Tue Jun 17 17:59:24 2003
@@ -28,12 +28,8 @@
 from zodb import config
 import zodb.zeo.server
 from zodb.zeo import threadedasync
-
-def load_storage(fp):
-    context = ZConfig.Context.Context()
-    rootconf = context.loadFile(fp)
-    storageconf = rootconf.getSection('Storage')
-    return config.createStorage(storageconf)
+from zodb.zeo.runzeo import ZEOOptions
+from zodb.zeo.server import StorageServer
 
 class ZEOTestServer(asyncore.dispatcher):
     """A server for killing the whole process at the end of a test.
@@ -123,56 +119,58 @@
 
     # We don't do much sanity checking of the arguments, since if we get it
     # wrong, it's a bug in the test suite.
-    ro_svr = False
-    keep = False
+    keep = 0
     configfile = None
-    invalidation_queue_size = 100
-    transaction_timeout = None
-    monitor_address = None
     # Parse the arguments and let getopt.error percolate
-    opts, args = getopt.getopt(sys.argv[1:], 'rkC:Q:T:m:')
+    opts, args = getopt.getopt(sys.argv[1:], 'kC:')
     for opt, arg in opts:
-        if opt == '-r':
-            ro_svr = True
-        elif opt == '-k':
-            keep = True
+        if opt == '-k':
+            keep = 1
         elif opt == '-C':
             configfile = arg
-        elif opt == '-Q':
-            invalidation_queue_size = int(arg)
-        elif opt == '-T':
-            transaction_timeout = int(arg)
-        elif opt == '-m':
-            monitor_address = '', int(arg)
+
+    zo = ZEOOptions()
+    zo.realize(["-C", configfile])
+    zeo_port = int(zo.address[1])
+            
+    # XXX a hack
+    if zo.auth_protocol == "plaintext":
+        import ZEO.tests.auth_plaintext
+        
     # Open the config file and let ZConfig parse the data there.  Then remove
     # the config file, otherwise we'll leave turds.
-    fp = open(configfile, 'r')
-    storage = load_storage(fp)
-    fp.close()
-    os.remove(configfile)
     # The rest of the args are hostname, portnum
-    zeo_port = int(args[0])
     test_port = zeo_port + 1
-    test_addr = ('', test_port)
-    addr = ('', zeo_port)
-    serv = zodb.zeo.server.StorageServer(
-        addr, {'1': storage}, ro_svr,
-        invalidation_queue_size=invalidation_queue_size,
-        transaction_timeout=transaction_timeout,
-        monitor_address=monitor_address)
+    test_addr = ('localhost', test_port)
+    addr = ('localhost', zeo_port)
+    logger.info('creating the storage server')
+    storage = zo.storages[0].open()
+    mon_addr = None
+    if zo.monitor_address:
+        mon_addr = zo.monitor_address.address
+    server = StorageServer(
+        zo.address,
+        {"1": storage},
+        read_only=zo.read_only,
+        invalidation_queue_size=zo.invalidation_queue_size,
+        transaction_timeout=zo.transaction_timeout,
+        monitor_address=mon_addr,
+        auth_protocol=zo.auth_protocol,
+        auth_filename=zo.auth_database,
+        auth_realm=zo.auth_realm)
+
     try:
-        logger.info('creating the test server, ro: %s, keep: %s',
-                    ro_svr, keep)
-        t = ZEOTestServer(test_addr, serv, keep)
+        logger.info('creating the test server, keep: %s', keep)
+        t = ZEOTestServer(test_addr, server, keep)
     except socket.error, e:
         if e[0] <> errno.EADDRINUSE: raise
         logger.info('addr in use, closing and exiting')
         storage.close()
-        storage.cleanup()
+        cleanup(storage)
         sys.exit(2)
-    addr = ('', zeo_port)
+        
     logger.info('creating the storage server')
-    t.register_socket(serv.dispatcher)
+    t.register_socket(server.dispatcher)
     # Loop for socket events
     logger.info('entering threadedasync loop')
     threadedasync.loop()


=== ZODB4/src/zodb/zeo/tests/test_zeo.py 1.12 => 1.12.6.1 ===
--- ZODB4/src/zodb/zeo/tests/test_zeo.py:1.12	Fri May 16 18:53:47 2003
+++ ZODB4/src/zodb/zeo/tests/test_zeo.py	Tue Jun 17 17:59:24 2003
@@ -19,6 +19,7 @@
 import time
 import errno
 import socket
+import random
 import logging
 import asyncore
 import tempfile
@@ -40,6 +41,27 @@
 from zodb.zeo.tests import commitlock, threadtests
 from zodb.zeo.tests.common import TestClientStorage, DummyDB
 
+def get_port():
+    """Return a port that is not in use.
+
+    Checks if a port is in use by trying to connect to it.  Assumes it
+    is not in use if connect raises an exception.
+
+    Raises RuntimeError after 10 tries.
+    """
+    for i in range(10):
+        port = random.randrange(20000, 30000)
+        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        try:
+            try:
+                s.connect(('localhost', port))
+            except socket.error:
+                # XXX check value of error?
+                return port
+        finally:
+            s.close()
+    raise RuntimeError, "Can't find port"
+
 class DummyDB:
     def invalidate(self, *args):
         pass
@@ -102,8 +124,11 @@
         logging.info("testZEO: setUp() %s", self.id())
         config = self.getConfig()
         for i in range(10):
+            port = get_port()
+            zconf = forker.ZEOConfig(('', port))
             try:
-                zeoport, adminaddr, pid = forker.start_zeo_server(config)
+                zport, adminaddr, pid, path = \
+                       forker.start_zeo_server(config, zconf, port)
             except socket.error, e:
                 if e[0] not in (errno.ECONNREFUSED, errno.ECONNRESET):
                     raise
@@ -113,14 +138,16 @@
             raise
         self._pids = [pid]
         self._servers = [adminaddr]
-        self._storage = TestClientStorage(zeoport, '1', cache_size=20000000,
-                                          min_disconnect_poll=0.5, wait=1)
+        self._conf_path = path
+        self._storage = ClientStorage(zport, '1', cache_size=20000000,
+                                      min_disconnect_poll=0.5, wait=1)
         self._storage.registerDB(DummyDB())
 
     def tearDown(self):
         # Clean up any transaction that might be left hanging around
         get_transaction().abort()
         self._storage.close()
+        os.remove(self._conf_path)
         for server in self._servers:
             forker.shutdown_zeo_server(server)
         if hasattr(os, 'waitpid'):
@@ -174,11 +201,9 @@
     def getConfig(self):
         filename = self.__fs_base = tempfile.mktemp()
         return """\
-        <Storage>
-            type FileStorage
-            file_name %s
-            create yes
-        </Storage>
+        <filestorage 1>
+        path %s
+        </filestorage>
         """ % filename
 
 class BDBTests(UndoVersionStorageTests):
@@ -189,10 +214,9 @@
     def getConfig(self):
         self._envdir = tempfile.mktemp()
         return """\
-        <Storage>
-            type BDBFullStorage
-            name %s
-        </Storage>
+        <fullstorage 1>
+        name %s
+        </fullstorage>
         """ % self._envdir
 
     # XXX These test seems to have massive failures when I run them.
@@ -207,19 +231,14 @@
 class MappingStorageTests(StorageTests):
 
     def getConfig(self):
-        self._envdir = tempfile.mktemp()
-        return """\
-        <Storage>
-            type MappingStorage
-            name %s
-        </Storage>
-        """ % self._envdir
+        return """<mappingstorage 1/>"""
 
-test_classes = [FileStorageTests, MappingStorageTests]
+##test_classes = [FileStorageTests, MappingStorageTests]
 
-from zodb.storage.base import berkeley_is_available
-if berkeley_is_available:
-    test_classes.append(BDBTests)
+##from zodb.storage.base import berkeley_is_available
+##if berkeley_is_available:
+##    test_classes.append(BDBTests)
+test_classes = [BDBTests]
 
 def test_suite():
     suite = unittest.TestSuite()


=== ZODB4/src/zodb/zeo/tests/test_conn.py 1.5 => 1.5.6.1 ===
--- ZODB4/src/zodb/zeo/tests/test_conn.py:1.5	Fri May 16 17:49:36 2003
+++ ZODB4/src/zodb/zeo/tests/test_conn.py	Tue Jun 17 17:59:24 2003
@@ -25,33 +25,25 @@
 class FileStorageConfig:
     def getConfig(self, path, create, read_only):
         return """\
-        <Storage>
-            type FileStorage
-            file_name %s
-            create %s
-            read_only %s
-        </Storage>""" % (path,
-                         create and 'yes' or 'no',
-                         read_only and 'yes' or 'no')
+        <filestorage 1>
+        path %s
+        create %s
+        read-only %s
+        </filestorage>""" % (path,
+                             create and 'yes' or 'no',
+                             read_only and 'yes' or 'no')
 
 class BerkeleyStorageConfig:
     def getConfig(self, path, create, read_only):
-        # Full always creates and doesn't have a read_only flag
         return """\
-        <Storage>
-            type BDBFullStorage
-            name %s
-            read_only %s
-        </Storage>""" % (path,
-                         read_only and 'yes' or 'no')
+        <fullstorage 1>
+        name %s
+        read-only %s
+        </fullstorage>""" % (path, read_only and "yes" or "no")
 
 class MappingStorageConfig:
     def getConfig(self, path, create, read_only):
-        return """\
-        <Storage>
-            type MappingStorage
-            name %s
-        </Storage>""" % path
+        return """<mappingstorage 1/>"""
 
 tests = [
     (MappingStorageConfig, ConnectionTests, 1),


=== ZODB4/src/zodb/zeo/tests/forker.py 1.5 => 1.5.30.1 ===
--- ZODB4/src/zodb/zeo/tests/forker.py:1.5	Tue Feb 25 13:55:05 2003
+++ ZODB4/src/zodb/zeo/tests/forker.py	Tue Jun 17 17:59:24 2003
@@ -16,104 +16,103 @@
 import os
 import sys
 import time
+import logging
 import errno
 import random
 import socket
+import StringIO
 import tempfile
-import traceback
-import logging
-
-# Change value of PROFILE to enable server-side profiling
-PROFILE = False
-if PROFILE:
-    import hotshot
-
-
-def get_port():
-    """Return a port that is not in use.
 
-    Checks if a port is in use by trying to connect to it.  Assumes it
-    is not in use if connect raises an exception.
-
-    Raises RuntimeError after 10 tries.
-    """
-    for i in range(10):
-        port = random.randrange(20000, 30000)
-        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-        try:
-            try:
-                s.connect(('localhost', port))
-            except socket.error:
-                # XXX check value of error?
-                return port
-        finally:
-            s.close()
-    raise RuntimeError, "Can't find port"
+class ZEOConfig:
+    """Class to generate ZEO configuration file. """
 
+    def __init__(self, addr):
+        self.address = addr
+        self.read_only = None
+        self.invalidation_queue_size = None
+        self.monitor_address = None
+        self.transaction_timeout = None
+        self.authentication_protocol = None
+        self.authentication_database = None
+        self.authentication_realm = None
+
+    def dump(self, f):
+        print >> f, "<zeo>"
+        print >> f, "address %s:%s" % self.address
+        if self.read_only is not None:
+            print >> f, "read-only", self.read_only and "true" or "false"
+        if self.invalidation_queue_size is not None:
+            print >> f, "invalidation-queue-size", self.invalidation_queue_size
+        if self.monitor_address is not None:
+            print >> f, "monitor-address %s:%s" % self.monitor_address
+        if self.transaction_timeout is not None:
+            print >> f, "transaction-timeout", self.transaction_timeout
+        if self.authentication_protocol is not None:
+            print >> f, "authentication-protocol", self.authentication_protocol
+        if self.authentication_database is not None:
+            print >> f, "authentication-database", self.authentication_database
+        if self.authentication_realm is not None:
+            print >> f, "authentication-realm", self.authentication_realm
+        print >> f, "</zeo>"
+
+    def __str__(self):
+        f = StringIO.StringIO()
+        self.dump(f)
+        return f.getvalue()
 
-def start_zeo_server(conf, addr=None, ro_svr=False, monitor=False, keep=False,
-                     invq=None, timeout=None):
+def start_zeo_server(storage_conf, zeo_conf, port, keep=0):
     """Start a ZEO server in a separate process.
 
-    Returns the ZEO port, the test server port, and the pid.
+    Takes two positional arguments a string containing the storage conf
+    and a ZEOConfig object.
+
+    Returns the ZEO port, the test server port, the pid, and the path
+    to the config file.
     """
+    
     # Store the config info in a temp file.
-    tmpfile = tempfile.mktemp()
+    tmpfile = tempfile.mktemp(".conf")
     fp = open(tmpfile, 'w')
-    fp.write(conf)
+    zeo_conf.dump(fp)
+    fp.write(storage_conf)
     fp.close()
-    # Create the server
+    
+    # Find the zeoserver script
     import zodb.zeo.tests.zeoserver
-    if addr is None:
-        port = get_port()
-    else:
-        port = addr[1]
     script = zodb.zeo.tests.zeoserver.__file__
     if script.endswith('.pyc'):
         script = script[:-1]
+        
     # Create a list of arguments, which we'll tuplify below
     qa = _quote_arg
     args = [qa(sys.executable), qa(script), '-C', qa(tmpfile)]
-    if ro_svr:
-        args.append('-r')
     if keep:
-        args.append('-k')
-    if invq:
-        args += ['-Q', str(invq)]
-    if timeout:
-        args += ['-T', str(timeout)]
-    if monitor:
-        # XXX Is it safe to reuse the port?
-        args += ['-m', '42000']
-    args.append(str(port))
+        args.append("-k")
     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.
-    # Always do a sleep as the first thing, since we don't expect
-    # the spawned process to get started right away.
-    delay = 0.25
-    for i in range(10):
-        time.sleep(delay)
-        logging.debug('forker: connect %s', i)
-        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+    adminaddr = ('localhost', port + 1)
+    logger = logging.getLogger("forker")
+    # We need to wait until the server starts, but not forever
+    for i in range(20):
+        time.sleep(0.25)
         try:
+            logger.debug("connect %s", i)
+            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
             s.connect(adminaddr)
             ack = s.recv(1024)
+            s.close()
+            logger.debug("acked: %s", ack)
+            break
         except socket.error, e:
             if e[0] not in (errno.ECONNREFUSED, errno.ECONNRESET):
                 raise
-            logging.debug('forker: failed %s' % i)
             s.close()
-        else:
-            logging.debug('forker: acked: %s', ack)
-            s.close()
-            break
     else:
-        logging.debug('forker: boo hoo')
+        logger.debug("boo foo")
         raise
-    return ('localhost', port), adminaddr, pid
+    return ('localhost', port), adminaddr, pid, tmpfile
+
 
 if sys.platform[:3].lower() == "win":
     def _quote_arg(s):
@@ -122,6 +121,7 @@
     def _quote_arg(s):
         return s
 
+
 def shutdown_zeo_server(adminaddr):
     s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
     s.connect(adminaddr)
@@ -130,5 +130,5 @@
     except socket.error, e:
         if e[0] <> errno.ECONNRESET: raise
         ack = 'no ack received'
-    logging.debug('shutdownServer: acked: %s', ack)
+    logging.getLogger("forker").debug("shutdown server acked: %s", ack)
     s.close()