[CalendarServer-changes] [2007] CalendarServer/trunk/twistedcaldav

source_changes at macosforge.org source_changes at macosforge.org
Wed Nov 14 12:57:26 PST 2007


Revision: 2007
          http://trac.macosforge.org/projects/calendarserver/changeset/2007
Author:   cdaboo at apple.com
Date:     2007-11-14 12:57:25 -0800 (Wed, 14 Nov 2007)

Log Message:
-----------
Prevent a race condition whereby multiple processes can attempt to create a database table more than once,
resulting in exceptions for all but the first process to do so.

Modified Paths:
--------------
    CalendarServer/trunk/twistedcaldav/sql.py
    CalendarServer/trunk/twistedcaldav/test/test_sql.py

Modified: CalendarServer/trunk/twistedcaldav/sql.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/sql.py	2007-11-12 20:24:19 UTC (rev 2006)
+++ CalendarServer/trunk/twistedcaldav/sql.py	2007-11-14 20:57:25 UTC (rev 2007)
@@ -81,14 +81,8 @@
             q = self._db_connection.cursor()
             try:
                 # Create CALDAV table if needed
-                q.execute(
-                    """
-                    select (1) from SQLITE_MASTER
-                     where TYPE = 'table' and NAME = 'CALDAV'
-                    """)
-                caldav = q.fetchone()
 
-                if caldav:
+                if self._test_schema_table(q):
                     q.execute(
                         """
                         select VALUE from CALDAV
@@ -131,6 +125,13 @@
                 if q is not None: q.close()
         return self._db_connection
 
+    def _test_schema_table(self, q):
+        q.execute("""
+        select (1) from SQLITE_MASTER
+         where TYPE = 'table' and NAME = 'CALDAV'
+        """)
+        return q.fetchone()
+
     def _db_init(self, db_filename, q):
         """
         Initialise the underlying database tables.
@@ -139,10 +140,23 @@
         """
         log.msg("Initializing database %s" % (db_filename,))
 
-        self._db_init_schema_table(q)
-        self._db_init_data_tables(q)
-        self._db_recreate()
+        # We need an exclusive lock here as we are making a big change to the database and we don't
+        # want other processes to get stomped on or stomp on us.
+        old_isolation = self._db_connection.isolation_level
+        self._db_connection.isolation_level = None
+        q.execute("begin exclusive transaction")
+        
+        # We re-check whether the schema table is present again AFTER we've got an exclusive
+        # lock as some other server process may have snuck in and already created it
+        # before we got the lock, or whilst we were waiting for it.
+        if not self._test_schema_table(q):
+            self._db_init_schema_table(q)
+            self._db_init_data_tables(q)
+            self._db_recreate()
 
+        q.execute("commit")
+        self._db_connection.isolation_level = old_isolation
+
     def _db_init_schema_table(self, q):
         """
         Initialise the underlying database tables.

Modified: CalendarServer/trunk/twistedcaldav/test/test_sql.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_sql.py	2007-11-12 20:24:19 UTC (rev 2006)
+++ CalendarServer/trunk/twistedcaldav/test/test_sql.py	2007-11-14 20:57:25 UTC (rev 2007)
@@ -20,6 +20,8 @@
 
 import twistedcaldav.test.util
 from twistedcaldav.sql import db_prefix
+from threading import Thread
+import time
 import os
 
 class SQL (twistedcaldav.test.util.TestCase):
@@ -62,6 +64,13 @@
                 """
             )
 
+    class TestDBPauseInInit(TestDB):
+        
+        def _db_init(self, db_filename, q):
+            
+            time.sleep(1)
+            super(SQL.TestDBPauseInInit, self)._db_init(db_filename, q)
+
     def test_connect(self):
         """
         Connect to database and create table
@@ -152,3 +161,26 @@
         children = self.site.resource.listChildren()
         self.assertTrue("test" in children)
         self.assertFalse(db_prefix + "sqlite" in children)
+
+    def test_duplicate_create(self):
+        dbname = self.mktemp()
+        
+        class DBThread(Thread):
+            
+            def run(self):
+                try:
+                    db = SQL.TestDBPauseInInit(dbname)
+                    db._db()
+                    self.result = True
+                except:
+                    self.result = False
+
+        t1 = DBThread()
+        t2 = DBThread()
+        t1.start()
+        t2.start()
+        t1.join()
+        t2.join()
+        self.assertTrue(t1.result)
+        self.assertTrue(t2.result)
+

-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20071114/b44cbc07/attachment-0001.html


More information about the calendarserver-changes mailing list