[CalendarServer-changes] [6200] CalendarServer/trunk/txdav

source_changes at macosforge.org source_changes at macosforge.org
Fri Aug 27 19:53:30 PDT 2010


Revision: 6200
          http://trac.macosforge.org/projects/calendarserver/changeset/6200
Author:   cdaboo at apple.com
Date:     2010-08-27 19:53:27 -0700 (Fri, 27 Aug 2010)
Log Message:
-----------
Implement locking for concurrent home provisioning issues.

Modified Paths:
--------------
    CalendarServer/trunk/txdav/base/datastore/subpostgres.py
    CalendarServer/trunk/txdav/caldav/datastore/test/test_sql.py
    CalendarServer/trunk/txdav/carddav/datastore/test/test_sql.py
    CalendarServer/trunk/txdav/common/datastore/sql.py

Modified: CalendarServer/trunk/txdav/base/datastore/subpostgres.py
===================================================================
--- CalendarServer/trunk/txdav/base/datastore/subpostgres.py	2010-08-27 17:45:52 UTC (rev 6199)
+++ CalendarServer/trunk/txdav/base/datastore/subpostgres.py	2010-08-28 02:53:27 UTC (rev 6200)
@@ -255,7 +255,7 @@
         # See: http://www.postgresql.org/docs/8.4/static/kernel-resources.html
         if testMode:
             self.sharedBuffers = 16
-            self.maxConnections = 2
+            self.maxConnections = 4
         else:
             self.sharedBuffers = 30
             self.maxConnections = 20

Modified: CalendarServer/trunk/txdav/caldav/datastore/test/test_sql.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/test/test_sql.py	2010-08-27 17:45:52 UTC (rev 6199)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/test_sql.py	2010-08-28 02:53:27 UTC (rev 6200)
@@ -19,13 +19,17 @@
 L{txdav.caldav.datastore.test.common}.
 """
 
+import time
+
 from txdav.caldav.datastore.test.common import CommonTests as CalendarCommonTests
 
+from txdav.common.datastore.sql import ECALENDARTYPE
 from txdav.common.datastore.test.util import SQLStoreBuilder
 from txdav.common.icommondatastore import NoSuchHomeChildError
 
 from twisted.trial import unittest
 from twisted.internet.defer import inlineCallbacks
+from twisted.internet.threads import deferToThread
 from twext.python.vcomponent import VComponent
 
 
@@ -76,3 +80,46 @@
         Create and return a L{CalendarStore} for testing.
         """
         return self.calendarStore
+
+    @inlineCallbacks
+    def test_homeProvisioningConcurrency(self):
+
+        calendarStore1 = yield buildStore(self, self.notifierFactory)
+        calendarStore2 = yield buildStore(self, self.notifierFactory)
+        calendarStore3 = yield buildStore(self, self.notifierFactory)
+
+        txn1 = calendarStore1.newTransaction()
+        txn2 = calendarStore2.newTransaction()
+        txn3 = calendarStore3.newTransaction()
+        
+        # Provision one home now
+        home_uid2 = txn3.homeWithUID(ECALENDARTYPE, "uid2", create=True)
+        self.assertNotEqual(home_uid2, None)
+        txn3.commit()
+
+        home_uid1_1 = txn1.homeWithUID(ECALENDARTYPE, "uid1", create=True)
+        
+        def _defer_home_uid1_2():
+            home_uid1_2 = txn2.homeWithUID(ECALENDARTYPE, "uid1", create=True)
+            txn2.commit()
+            return home_uid1_2
+        d1 = deferToThread(_defer_home_uid1_2)
+        
+        def _pause_home_uid1_1():
+            time.sleep(1)
+            txn1.commit()
+        d2 = deferToThread(_pause_home_uid1_1)
+        
+        # Verify that we can still get to the existing home - i.e. the lock
+        # on the table allows concurrent reads
+        txn4 = calendarStore3.newTransaction()
+        home_uid2 = txn4.homeWithUID(ECALENDARTYPE, "uid2", create=True)
+        self.assertNotEqual(home_uid2, None)
+        txn4.commit()
+        
+        # Now do the concurrent provision attempt
+        yield d2
+        home_uid1_2 = yield d1
+        
+        self.assertNotEqual(home_uid1_1, None)
+        self.assertNotEqual(home_uid1_2, None)

Modified: CalendarServer/trunk/txdav/carddav/datastore/test/test_sql.py
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/test/test_sql.py	2010-08-27 17:45:52 UTC (rev 6199)
+++ CalendarServer/trunk/txdav/carddav/datastore/test/test_sql.py	2010-08-28 02:53:27 UTC (rev 6200)
@@ -19,15 +19,18 @@
 L{txdav.caldav.datastore.test.common}.
 """
 
+import time
+
 from txdav.carddav.datastore.test.common import CommonTests as AddressBookCommonTests
 
+from txdav.common.datastore.sql import EADDRESSBOOKTYPE
 from txdav.common.datastore.test.util import SQLStoreBuilder
 
 from twisted.trial import unittest
 from twisted.internet.defer import inlineCallbacks
+from twisted.internet.threads import deferToThread
 from twistedcaldav.vcard import Component as VCard
 
-
 theStoreBuilder = SQLStoreBuilder()
 buildStore = theStoreBuilder.buildStore
 
@@ -73,3 +76,46 @@
         """
         return self.addressbookStore
 
+
+    @inlineCallbacks
+    def test_homeProvisioningConcurrency(self):
+
+        addressbookStore1 = yield buildStore(self, self.notifierFactory)
+        addressbookStore2 = yield buildStore(self, self.notifierFactory)
+        addressbookStore3 = yield buildStore(self, self.notifierFactory)
+
+        txn1 = addressbookStore1.newTransaction()
+        txn2 = addressbookStore2.newTransaction()
+        txn3 = addressbookStore3.newTransaction()
+        
+        # Provision one home now
+        home_uid2 = txn3.homeWithUID(EADDRESSBOOKTYPE, "uid2", create=True)
+        self.assertNotEqual(home_uid2, None)
+        txn3.commit()
+
+        home_uid1_1 = txn1.homeWithUID(EADDRESSBOOKTYPE, "uid1", create=True)
+        
+        def _defer_home_uid1_2():
+            home_uid1_2 = txn2.homeWithUID(EADDRESSBOOKTYPE, "uid1", create=True)
+            txn2.commit()
+            return home_uid1_2
+        d1 = deferToThread(_defer_home_uid1_2)
+        
+        def _pause_home_uid1_1():
+            time.sleep(1)
+            txn1.commit()
+        d2 = deferToThread(_pause_home_uid1_1)
+        
+        # Verify that we can still get to the existing home - i.e. the lock
+        # on the table allows concurrent reads
+        txn4 = addressbookStore3.newTransaction()
+        home_uid2 = txn4.homeWithUID(EADDRESSBOOKTYPE, "uid2", create=True)
+        self.assertNotEqual(home_uid2, None)
+        txn4.commit()
+        
+        # Now do the concurrent provision attempt
+        yield d2
+        home_uid1_2 = yield d1
+        
+        self.assertNotEqual(home_uid1_1, None)
+        self.assertNotEqual(home_uid1_2, None)

Modified: CalendarServer/trunk/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql.py	2010-08-27 17:45:52 UTC (rev 6199)
+++ CalendarServer/trunk/txdav/common/datastore/sql.py	2010-08-28 02:53:27 UTC (rev 6200)
@@ -169,13 +169,30 @@
         if not data:
             if not create:
                 return None
+            
+            # Need to lock to prevent race condition
+            # FIXME: this is an entire table lock - ideally we want a row lock
+            # but the row does not exist yet. However, the "exclusive" mode does
+            # allow concurrent reads so the only thing we block is other attempts
+            # to provision a home, which is not too bad
             self.execSQL(
-                "insert into %(name)s (%(column_OWNER_UID)s) values (%%s)" % homeTable,
+                "lock %(name)s in exclusive mode" % homeTable,
+            )
+            
+            # Now test again
+            data = self.execSQL(
+                "select %(column_RESOURCE_ID)s from %(name)s where %(column_OWNER_UID)s = %%s" % homeTable,
                 [uid]
             )
-            home = self.homeWithUID(storeType, uid)
-            home.createdHome()
-            return home
+
+            if not data:
+                self.execSQL(
+                    "insert into %(name)s (%(column_OWNER_UID)s) values (%%s)" % homeTable,
+                    [uid]
+                )
+                home = self.homeWithUID(storeType, uid)
+                home.createdHome()
+                return home
         resid = data[0][0]
 
         if self._notifierFactory:
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20100827/531ff0d0/attachment-0001.html>


More information about the calendarserver-changes mailing list