[Repoze-checkins] r861 - in repoze.zope2/trunk: . repoze/zope2 repoze/zope2/tests

Chris McDonough chrism at agendaless.com
Mon Mar 24 15:28:56 EDT 2008


Author: Chris McDonough <chrism at agendaless.com>
Date: Mon Mar 24 15:28:55 2008
New Revision: 861

Log:
  - When Zope 2 starts, it potentially writes data to the database
    during product initialization.  When multiple clients talk to the
    same ZEO storage at startup, they often simultaneously try to
    write (the same) information to the database concurrently.  This
    causes startup failure due to conflict errors.  We now retry
    product initialization up to five times to work around this issue.



Added:
   repoze.zope2/trunk/repoze/zope2/tests/test_db.py   (contents, props changed)
Modified:
   repoze.zope2/trunk/CHANGES.txt
   repoze.zope2/trunk/repoze/zope2/db.py
   repoze.zope2/trunk/setup.py

Modified: repoze.zope2/trunk/CHANGES.txt
==============================================================================
--- repoze.zope2/trunk/CHANGES.txt	(original)
+++ repoze.zope2/trunk/CHANGES.txt	Mon Mar 24 15:28:55 2008
@@ -1,7 +1,14 @@
-Unreleased
+0.3.4 (2008-03-24)
 
   - Bump ez_setup.py version.
 
+  - When Zope 2 starts, it potentially writes data to the database
+    during product initialization.  When multiple clients talk to the
+    same ZEO storage at startup, they often simultaneously try to
+    write (the same) information to the database concurrently.  This
+    causes startup failure due to conflict errors.  We now retry
+    product initialization up to five times to work around this issue.
+
 0.3.3 (2008-03-10)
 
   - repoze.zope2 now properly respects virtual host directives

Modified: repoze.zope2/trunk/repoze/zope2/db.py
==============================================================================
--- repoze.zope2/trunk/repoze/zope2/db.py	(original)
+++ repoze.zope2/trunk/repoze/zope2/db.py	Mon Mar 24 15:28:55 2008
@@ -14,6 +14,16 @@
 
 """ Utilies for constructing a ZODB database object """
 
+def retry(f, numtries):
+    from ZODB.POSException import ConflictError
+    for x in range(0, numtries):
+        try:
+            return f()
+            break
+        except ConflictError, why:
+            if x == (numtries-1):
+                raise
+
 def getDB(config):
     config_file = config['zope.conf']
     from Zope2.Startup import options, handlers, get_starter
@@ -30,8 +40,12 @@
     starter.setupSecurityOptions()
     starter.setupPublisher()
     from Zope2.App.startup import startup as _startup
-    _startup()
-    #starter.startZope() # this calls Zope2.App.startup.startup
+    # _startup is what causes Products to be installed; Product
+    # initialization causes database writes, which cause conflict
+    # errors when used with multiple clients vs. ZEO.  We need to retry
+    # these conflict errors.  I wish Zope didn't write to the database
+    # at startup time.
+    retry(_startup, 5)
     starter.setupFinalLogging()
     Zope2.zpublisher_transactions_manager = None # middleware does this
     # Zope2.bobo_application is used by: App.ZApplication
@@ -44,9 +58,11 @@
     root = conn.root()
     import transaction
     appname = config.get('appname', 'Application')
-    if not root.has_key(appname):
-        root[appname] = Application()
-        transaction.commit()
+    def addroot():
+        if not root.has_key(appname):
+            root[appname] = Application()
+            transaction.commit()
+    retry(addroot, 5)
     conn.close()
     return db
 

Added: repoze.zope2/trunk/repoze/zope2/tests/test_db.py
==============================================================================
--- (empty file)
+++ repoze.zope2/trunk/repoze/zope2/tests/test_db.py	Mon Mar 24 15:28:55 2008
@@ -0,0 +1,32 @@
+import unittest
+
+class TestRetry(unittest.TestCase):
+    def _getFUT(self):
+        from repoze.zope2.db import retry
+        return retry
+
+    def test_retry_success(self):
+        retry = self._getFUT()
+        raiser = DummyConflictErrorRaiser(5)
+        val = retry(raiser, 10)
+        self.assertEqual(val, 5)
+
+    def test_retry_fail(self):
+        retry = self._getFUT()
+        raiser = DummyConflictErrorRaiser(11)
+        from ZODB.POSException import ConflictError
+        self.assertRaises(ConflictError, retry, raiser, 10)
+
+class DummyConflictErrorRaiser:
+    def __init__(self, retries):
+        self.retries = retries
+        self.tried = 0
+
+    def __call__(self):
+        if self.tried >= self.retries:
+            return self.tried
+        self.tried += 1
+        from ZODB.POSException import ConflictError
+        raise ConflictError
+    
+    

Modified: repoze.zope2/trunk/setup.py
==============================================================================
--- repoze.zope2/trunk/setup.py	(original)
+++ repoze.zope2/trunk/setup.py	Mon Mar 24 15:28:55 2008
@@ -12,7 +12,7 @@
 #
 ##############################################################################
 
-__version__ = '0.3.3'
+__version__ = '0.3.4'
 
 from ez_setup import use_setuptools
 use_setuptools()


More information about the Repoze-checkins mailing list