[CalendarServer-changes] [7259] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Thu Mar 24 13:45:33 PDT 2011


Revision: 7259
          http://trac.macosforge.org/projects/calendarserver/changeset/7259
Author:   glyph at apple.com
Date:     2011-03-24 13:45:31 -0700 (Thu, 24 Mar 2011)
Log Message:
-----------
Subtransactions, command blocks, and a fix for concurrent property writes.

Modified Paths:
--------------
    CalendarServer/trunk/twext/enterprise/adbapi2.py
    CalendarServer/trunk/twext/enterprise/test/test_adbapi2.py
    CalendarServer/trunk/txdav/base/propertystore/sql.py
    CalendarServer/trunk/txdav/base/propertystore/test/test_sql.py
    CalendarServer/trunk/txdav/common/datastore/sql.py
    CalendarServer/trunk/txdav/common/icommondatastore.py

Property Changed:
----------------
    CalendarServer/trunk/
    CalendarServer/trunk/support/build.sh
    CalendarServer/trunk/txdav/caldav/datastore/index_file.py
    CalendarServer/trunk/txdav/caldav/datastore/test/test_index_file.py
    CalendarServer/trunk/txdav/carddav/datastore/index_file.py
    CalendarServer/trunk/txdav/carddav/datastore/test/test_index_file.py


Property changes on: CalendarServer/trunk
___________________________________________________________________
Modified: svn:mergeinfo
   - /CalendarServer/branches/config-separation:4379-4443
/CalendarServer/branches/egg-info-351:4589-4625
/CalendarServer/branches/generic-sqlstore:6167-6191
/CalendarServer/branches/new-store:5594-5934
/CalendarServer/branches/new-store-no-caldavfile:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2:5936-5981
/CalendarServer/branches/users/cdaboo/batchupload-6699:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
/CalendarServer/branches/users/cdaboo/pycalendar:7085-7206
/CalendarServer/branches/users/cdaboo/pycard:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187:5188-5440
/CalendarServer/branches/users/glyph/conn-limit:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
/CalendarServer/branches/users/glyph/dalify:6932-7023
/CalendarServer/branches/users/glyph/db-reconnect:6824-6876
/CalendarServer/branches/users/glyph/dont-start-postgres:6592-6614
/CalendarServer/branches/users/glyph/linux-tests:6893-6900
/CalendarServer/branches/users/glyph/more-deferreds-6:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7:6369-6445
/CalendarServer/branches/users/glyph/oracle:7106-7155
/CalendarServer/branches/users/glyph/sendfdport:5388-5424
/CalendarServer/branches/users/glyph/sharedpool:6490-6550
/CalendarServer/branches/users/glyph/sql-store:5929-6073
/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
/CalendarServer/branches/users/sagen/locations-resources:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
/CalendarServer/branches/users/sagen/resources-2:5084-5093
/CalendarServer/branches/users/wsanchez/transations:5515-5593
   + /CalendarServer/branches/config-separation:4379-4443
/CalendarServer/branches/egg-info-351:4589-4625
/CalendarServer/branches/generic-sqlstore:6167-6191
/CalendarServer/branches/new-store:5594-5934
/CalendarServer/branches/new-store-no-caldavfile:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2:5936-5981
/CalendarServer/branches/users/cdaboo/batchupload-6699:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
/CalendarServer/branches/users/cdaboo/pycalendar:7085-7206
/CalendarServer/branches/users/cdaboo/pycard:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187:5188-5440
/CalendarServer/branches/users/glyph/conn-limit:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
/CalendarServer/branches/users/glyph/dalify:6932-7023
/CalendarServer/branches/users/glyph/db-reconnect:6824-6876
/CalendarServer/branches/users/glyph/dont-start-postgres:6592-6614
/CalendarServer/branches/users/glyph/linux-tests:6893-6900
/CalendarServer/branches/users/glyph/more-deferreds-6:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7:6369-6445
/CalendarServer/branches/users/glyph/oracle:7106-7155
/CalendarServer/branches/users/glyph/sendfdport:5388-5424
/CalendarServer/branches/users/glyph/sharedpool:6490-6550
/CalendarServer/branches/users/glyph/sql-store:5929-6073
/CalendarServer/branches/users/glyph/subtransactions:7248-7258
/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
/CalendarServer/branches/users/sagen/locations-resources:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
/CalendarServer/branches/users/sagen/resources-2:5084-5093
/CalendarServer/branches/users/wsanchez/transations:5515-5593


Property changes on: CalendarServer/trunk/support/build.sh
___________________________________________________________________
Modified: svn:mergeinfo
   - /CalendarServer/branches/config-separation/support/build.sh:4379-4443
/CalendarServer/branches/egg-info-351/support/build.sh:4589-4615
/CalendarServer/branches/generic-sqlstore/support/build.sh:6167-6191
/CalendarServer/branches/new-store-no-caldavfile-2/support/build.sh:5936-5981
/CalendarServer/branches/new-store-no-caldavfile/support/build.sh:5911-5935
/CalendarServer/branches/new-store/support/build.sh:5594-5934
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/support/build.sh:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/support/build.sh:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/support/build.sh:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/support/build.sh:4465-4957
/CalendarServer/branches/users/cdaboo/pycalendar/support/build.sh:7085-7206
/CalendarServer/branches/users/cdaboo/pycard/support/build.sh:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/support/build.sh:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/support/build.sh:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/support/build.sh:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/support/build.sh:4971-5080
/CalendarServer/branches/users/glyph/dalify/support/build.sh:6932-7023
/CalendarServer/branches/users/glyph/db-reconnect/support/build.sh:6824-6876
/CalendarServer/branches/users/glyph/dont-start-postgres/support/build.sh:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/support/build.sh:6893-6900
/CalendarServer/branches/users/glyph/more-deferreds-6/support/build.sh:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7/support/build.sh:6369-6445
/CalendarServer/branches/users/glyph/sendfdport/support/build.sh:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/support/build.sh:6490-6550
/CalendarServer/branches/users/glyph/sql-store/support/build.sh:5929-6073
/CalendarServer/branches/users/glyph/use-system-twisted/support/build.sh:5084-5149
/CalendarServer/branches/users/sagen/locations-resources-2/support/build.sh:5052-5061
/CalendarServer/branches/users/sagen/locations-resources/support/build.sh:5032-5051
/CalendarServer/branches/users/sagen/purge_old_events/support/build.sh:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/support/build.sh:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/support/build.sh:4068-4075
/CalendarServer/branches/users/sagen/resources-2/support/build.sh:5084-5093
/CalendarServer/branches/users/wsanchez/transations/support/build.sh:5515-5593
   + /CalendarServer/branches/config-separation/support/build.sh:4379-4443
/CalendarServer/branches/egg-info-351/support/build.sh:4589-4615
/CalendarServer/branches/generic-sqlstore/support/build.sh:6167-6191
/CalendarServer/branches/new-store/support/build.sh:5594-5934
/CalendarServer/branches/new-store-no-caldavfile/support/build.sh:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2/support/build.sh:5936-5981
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/support/build.sh:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/support/build.sh:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/support/build.sh:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/support/build.sh:4465-4957
/CalendarServer/branches/users/cdaboo/pycalendar/support/build.sh:7085-7206
/CalendarServer/branches/users/cdaboo/pycard/support/build.sh:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/support/build.sh:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/support/build.sh:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/support/build.sh:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/support/build.sh:4971-5080
/CalendarServer/branches/users/glyph/dalify/support/build.sh:6932-7023
/CalendarServer/branches/users/glyph/db-reconnect/support/build.sh:6824-6876
/CalendarServer/branches/users/glyph/dont-start-postgres/support/build.sh:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/support/build.sh:6893-6900
/CalendarServer/branches/users/glyph/more-deferreds-6/support/build.sh:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7/support/build.sh:6369-6445
/CalendarServer/branches/users/glyph/sendfdport/support/build.sh:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/support/build.sh:6490-6550
/CalendarServer/branches/users/glyph/sql-store/support/build.sh:5929-6073
/CalendarServer/branches/users/glyph/subtransactions/support/build.sh:7248-7258
/CalendarServer/branches/users/glyph/use-system-twisted/support/build.sh:5084-5149
/CalendarServer/branches/users/sagen/locations-resources/support/build.sh:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2/support/build.sh:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events/support/build.sh:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/support/build.sh:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/support/build.sh:4068-4075
/CalendarServer/branches/users/sagen/resources-2/support/build.sh:5084-5093
/CalendarServer/branches/users/wsanchez/transations/support/build.sh:5515-5593

Modified: CalendarServer/trunk/twext/enterprise/adbapi2.py
===================================================================
--- CalendarServer/trunk/twext/enterprise/adbapi2.py	2011-03-24 20:37:12 UTC (rev 7258)
+++ CalendarServer/trunk/twext/enterprise/adbapi2.py	2011-03-24 20:45:31 UTC (rev 7259)
@@ -1,4 +1,3 @@
-from twext.enterprise.ienterprise import IDerivedParameter
 # -*- test-case-name: twext.enterprise.test.test_adbapi2 -*-
 ##
 # Copyright (c) 2010 Apple Inc. All rights reserved.
@@ -40,6 +39,7 @@
 
 from twisted.internet.defer import inlineCallbacks
 from twisted.internet.defer import returnValue
+from twisted.internet.defer import DeferredList
 from twisted.internet.defer import Deferred
 from twisted.protocols.amp import Boolean
 from twisted.python.failure import Failure
@@ -52,7 +52,10 @@
 
 from twext.internet.threadutils import ThreadHolder
 from twisted.internet.defer import succeed
+
 from twext.enterprise.ienterprise import ConnectionError
+from twext.enterprise.ienterprise import IDerivedParameter
+
 from twisted.internet.defer import fail
 from twext.enterprise.ienterprise import (
     AlreadyFinishedError, IAsyncTransaction, POSTGRES_DIALECT
@@ -166,12 +169,24 @@
 
     def _end(self, really):
         """
-        Common logic for commit or abort.  Executed in the cursor main thread.
+        Common logic for commit or abort.  Executed in the main reactor thread.
+
+        @param really: the callable to execute in the cursor thread to actually
+            do the commit or rollback.
+
+        @return: a L{Deferred} which fires when the database logic has
+            completed.
+
+        @raise: L{AlreadyFinishedError} if the transaction has already been
+            committed or aborted.
         """
         if not self._completed:
             self._completed = True
             def reallySomething():
-                # Executed in the cursor thread.
+                """
+                Do the database work and set appropriate flags.  Executed in the
+                cursor thread.
+                """
                 if self._cursor is None:
                     return
                 really()
@@ -264,6 +279,11 @@
     implements(IAsyncTransaction)
 
     def __init__(self, pool):
+        """
+        Initialize a L{_WaitingTxn} based on a L{ConnectionPool}.  (The C{pool}
+        is used only to reflect C{dialect} and C{paramstyle} attributes; not
+        remembered or modified in any way.)
+        """
         self._spool = []
         self.paramstyle = pool.paramstyle
         self.dialect = pool.dialect
@@ -313,6 +333,10 @@
 
 
     def abort(self):
+        """
+        Succeed and do nothing.  The actual logic for this method is mostly
+        implemented by L{_SingleTxn._stopWaiting}.
+        """
         return succeed(None)
 
 
@@ -332,12 +356,19 @@
 
     This is the only L{IAsyncTransaction} implementation exposed to application
     code.
+
+    It's also the only implementor of the C{commandBlock} method for grouping
+    commands together.
     """
 
     def __init__(self, pool, baseTxn):
-        self._pool     = pool
-        self._baseTxn  = baseTxn
-        self._complete = False
+        self._pool           = pool
+        self._baseTxn        = baseTxn
+        self._complete       = False
+        self._currentBlock   = None
+        self._blockedQueue   = None
+        self._pendingBlocks  = []
+        self._stillExecuting = []
 
 
     def __repr__(self):
@@ -358,22 +389,86 @@
         spooledBase._unspool(baseTxn)
 
 
-    def execSQL(self, *a, **kw):
+    def execSQL(self, sql, args=None, raiseOnZeroRowCount=None):
+        return self._execSQLForBlock(sql, args, raiseOnZeroRowCount, None)
+
+
+    def _execSQLForBlock(self, sql, args, raiseOnZeroRowCount, block):
+        """
+        Execute some SQL for a particular L{CommandBlock}; or, if the given
+        C{block} is C{None}, execute it in the outermost transaction context.
+        """
         self._checkComplete()
-        return super(_SingleTxn, self).execSQL(*a, **kw)
+        if block is None and self._blockedQueue is not None:
+            return self._blockedQueue.execSQL(sql, args, raiseOnZeroRowCount)
+        # 'block' should always be _currentBlock at this point.
+        d = super(_SingleTxn, self).execSQL(sql, args, raiseOnZeroRowCount)
+        self._stillExecuting.append(d)
+        def itsDone(result):
+            self._stillExecuting.remove(d)
+            self._checkNextBlock()
+            return result
+        d.addBoth(itsDone)
+        return d
 
 
+    def _checkNextBlock(self):
+        """
+        Check to see if there are any blocks pending statements waiting to
+        execute, and execute the next one if there are no outstanding execute
+        calls.
+        """
+        if self._stillExecuting:
+            # If we're still executing statements, nevermind.  We'll get called
+            # again by the 'itsDone' callback above.
+            return
+
+        if self._currentBlock is not None:
+            # If there's still a current block, then keep it going.  We'll be
+            # called by the '_finishExecuting' callback below.
+            return
+
+        # There's no block executing now.  What to do?
+        if self._pendingBlocks:
+            # If there are pending blocks, start one of them.
+            self._currentBlock = self._pendingBlocks.pop(0)
+            d = self._currentBlock._startExecuting()
+            d.addCallback(self._finishExecuting)
+        elif self._blockedQueue is not None:
+            # If there aren't any pending blocks any more, and there are spooled
+            # statements that aren't part of a block, unspool all the statements
+            # that have been held up until this point.
+            bq = self._blockedQueue
+            self._blockedQueue = None
+            bq._unspool(self)
+
+
+    def _finishExecuting(self, result):
+        """
+        The active block just finished executing.  Clear it and see if there are
+        more blocks to execute, or if all the blocks are done and we should
+        execute any queued free statements.
+        """
+        self._currentBlock = None
+        self._checkNextBlock()
+
+
     def commit(self):
+        if self._blockedQueue is not None:
+            # We're in the process of executing a block of commands.  Wait until
+            # they're done.  (Commit will be repeated in _checkNextBlock.)
+            return self._blockedQueue.commit()
+
         self._markComplete()
         return super(_SingleTxn, self).commit()
 
 
     def abort(self):
         self._markComplete()
+        result = super(_SingleTxn, self).abort()
         if self in self._pool._waiting:
             self._stopWaiting()
-            return succeed(None)
-        return super(_SingleTxn, self).abort()
+        return result
 
 
     def _stopWaiting(self):
@@ -400,7 +495,116 @@
         self._complete = True
 
 
+    def commandBlock(self):
+        """
+        Create a L{CommandBlock} which will wait for all currently spooled
+        commands to complete before executing its own.
+        """
+        self._checkComplete()
+        block = CommandBlock(self)
+        if self._currentBlock is None:
+            self._blockedQueue = _WaitingTxn(self._pool)
+            # FIXME: test the case where it's ready immediately.
+            self._checkNextBlock()
+        return block
 
+
+
+class _Unspooler(object):
+    def __init__(self, orig):
+        self.orig = orig
+
+
+    def execSQL(self, sql, args=None, raiseOnZeroRowCount=None):
+        """
+        Execute some SQL, but don't track a new Deferred.
+        """
+        return self.orig.execSQL(sql, args, raiseOnZeroRowCount, False)
+
+
+
+class CommandBlock(object):
+    """
+    A partial implementation of L{IAsyncTransaction} that will group execSQL
+    calls together.
+
+    Does not implement commit() or abort(), because this will simply group
+    commands.  In order to implement sub-transactions or checkpoints, some
+    understanding of the SQL dialect in use by the underlying connection is
+    required.  Instead, it provides 'end'.
+    """
+
+    def __init__(self, singleTxn):
+        self._singleTxn = singleTxn
+        self.paramstyle = singleTxn.paramstyle
+        self.dialect = singleTxn.dialect
+        self._spool = _WaitingTxn(singleTxn._pool)
+        self._started = False
+        self._ended = False
+        self._waitingForEnd = []
+        self._endDeferred = Deferred()
+        singleTxn._pendingBlocks.append(self)
+
+
+    def _startExecuting(self):
+        self._started = True
+        self._spool._unspool(_Unspooler(self))
+        return self._endDeferred
+
+
+    def execSQL(self, sql, args=None, raiseOnZeroRowCount=None, track=True):
+        """
+        Execute some SQL within this command block.
+
+        @param sql: the SQL string to execute.
+
+        @param args: the SQL arguments.
+
+        @param raiseOnZeroRowCount: see L{IAsyncTransaction.execSQL}
+
+        @param track: an internal parameter; was this called by application code
+            or as part of unspooling some previously-queued requests?  True if
+            application code, False if unspooling.
+        """
+        if track and self._ended:
+            raise AlreadyFinishedError()
+        self._singleTxn._checkComplete()
+        if self._singleTxn._currentBlock is self and self._started:
+            d = self._singleTxn._execSQLForBlock(
+                sql, args, raiseOnZeroRowCount, self)
+        else:
+            d = self._spool.execSQL(sql, args, raiseOnZeroRowCount)
+        if track:
+            self._trackForEnd(d)
+        return d
+
+
+    def _trackForEnd(self, d):
+        """
+        Watch the following L{Deferred}, since we need to watch it to determine
+        when C{end} should be considered done, and the next CommandBlock or
+        regular SQL statement should be unqueued.
+        """
+        self._waitingForEnd.append(d)
+
+
+    def end(self):
+        """
+        The block of commands has completed.  Allow other SQL to run on the
+        underlying L{IAsyncTransaction}.
+        """
+        # FIXME: test the case where end() is called when it's not the current
+        # executing block.
+        if self._ended:
+            raise AlreadyFinishedError()
+        self._ended = True
+        # TODO: maybe this should return a Deferred that's a clone of
+        # _endDeferred, so that callers can determine when the block is really
+        # complete?  Struggling for an actual use-case on that one.
+        DeferredList(self._waitingForEnd).chainDeferred(self._endDeferred)
+
+
+
 class _ConnectingPseudoTxn(object):
 
     _retry = None
@@ -555,6 +759,8 @@
         @return: an L{IAsyncTransaction}
         """
         if self._stopping:
+            # FIXME: should be wrapping a _SingleTxn around this to get
+            # .commandBlock()
             return _NoTxn(self)
         if self._free:
             basetxn = self._free.pop(0)

Modified: CalendarServer/trunk/twext/enterprise/test/test_adbapi2.py
===================================================================
--- CalendarServer/trunk/twext/enterprise/test/test_adbapi2.py	2011-03-24 20:37:12 UTC (rev 7258)
+++ CalendarServer/trunk/twext/enterprise/test/test_adbapi2.py	2011-03-24 20:45:31 UTC (rev 7259)
@@ -27,6 +27,7 @@
 
 from twisted.internet.defer import Deferred
 from twext.enterprise.ienterprise import ConnectionError
+from twext.enterprise.ienterprise import AlreadyFinishedError
 from twext.enterprise.adbapi2 import ConnectionPool
 
 
@@ -146,6 +147,7 @@
         # not entirely correct, but all we care about is its truth value.
         self.description = False
         self.variables = []
+        self.allExecutions = []
 
 
     @property
@@ -158,6 +160,7 @@
         self.connection.executions += 1
         if self.connection._executeFailQueue:
             raise self.connection._executeFailQueue.pop(0)()
+        self.allExecutions.append((sql, args))
         self.sql = sql
         self.description = True
         self.rowcount = 1
@@ -424,7 +427,8 @@
         and thereby frees up the resources it is holding.
         """
         a = self.pool.connection()
-        [[[counter, echo]]] = resultOf(a.execSQL("alpha"))
+        alphaResult = resultOf(a.execSQL("alpha"))
+        [[[counter, echo]]] = alphaResult
         self.assertEquals(len(self.factory.connections), 1)
         self.assertEquals(len(self.holders), 1)
         [holder] = self.holders
@@ -652,6 +656,24 @@
         self.assertEquals(resultOf(self.pool.stopService()), [None])
 
 
+    def test_abortSpooled(self):
+        """
+        Aborting a still-spooled transaction (one which has no statements being
+        executed) will result in all of its Deferreds immediately failing and
+        none of the queued statements being executed.
+        """
+        # Use up the available connections ...
+        for i in xrange(self.pool.maxConnections):
+            self.pool.connection()
+        # ... so that this one has to be spooled.
+        spooled = self.pool.connection()
+        result = resultOf(spooled.execSQL("alpha"))
+        # sanity check, it would be bad if this actually executed.
+        self.assertEqual(result, [])
+        spooled.abort()
+        self.assertEqual(result[0].type, ConnectionError)
+
+
     def test_waitForAlreadyAbortedTransaction(self):
         """
         L{ConnectionPool.stopService} will wait for all transactions to shut
@@ -847,3 +869,171 @@
         self.assertEquals(self.factory.connections[0].closed, True)
         self.assertEquals(self.factory.connections[1].closed, False)
 
+
+    def test_commandBlock(self):
+        """
+        L{IAsyncTransaction.commandBlock} returns an L{IAsyncTransaction}
+        provider which ensures that a block of commands are executed together.
+        """
+        txn = self.pool.connection()
+        a = resultOf(txn.execSQL("a"))
+        cb = txn.commandBlock()
+        b = resultOf(cb.execSQL("b"))
+        d = resultOf(txn.execSQL("d"))
+        c = resultOf(cb.execSQL("c"))
+        cb.end()
+        e = resultOf(txn.execSQL("e"))
+        self.assertEquals(self.factory.connections[0].cursors[0].allExecutions,
+                          [("a", []), ("b", []), ("c", []), ("d", []),
+                           ("e", [])])
+        self.assertEquals(len(a), 1)
+        self.assertEquals(len(b), 1)
+        self.assertEquals(len(c), 1)
+        self.assertEquals(len(d), 1)
+        self.assertEquals(len(e), 1)
+
+
+    def test_commandBlockWithLatency(self):
+        """
+        A block returned by L{IAsyncTransaction.commandBlock} won't start
+        executing until all SQL statements scheduled before it have completed.
+        """
+        self.pauseHolders()
+        txn = self.pool.connection()
+        a = resultOf(txn.execSQL("a"))
+        b = resultOf(txn.execSQL("b"))
+        cb = txn.commandBlock()
+        c = resultOf(cb.execSQL("c"))
+        d = resultOf(cb.execSQL("d"))
+        e = resultOf(txn.execSQL("e"))
+        cb.end()
+        self.flushHolders()
+
+        self.assertEquals(self.factory.connections[0].cursors[0].allExecutions,
+                          [("a", []), ("b", []), ("c", []), ("d", []),
+                           ("e", [])])
+
+        self.assertEquals(len(a), 1)
+        self.assertEquals(len(b), 1)
+        self.assertEquals(len(c), 1)
+        self.assertEquals(len(d), 1)
+        self.assertEquals(len(e), 1)
+
+
+    def test_twoCommandBlocks(self, flush=lambda : None):
+        """
+        When execution of one command block is complete, it will proceed to the
+        next queued block, then to regular SQL executed on the transaction.
+        """
+        txn = self.pool.connection()
+        cb1 = txn.commandBlock()
+        cb2 = txn.commandBlock()
+        txn.execSQL("e")
+        cb1.execSQL("a")
+        cb2.execSQL("c")
+        cb1.execSQL("b")
+        cb2.execSQL("d")
+        cb2.end()
+        cb1.end()
+        flush()
+        self.assertEquals(self.factory.connections[0].cursors[0].allExecutions,
+                          [("a", []), ("b", []), ("c", []), ("d", []),
+                           ("e", [])])
+
+
+    def test_twoCommandBlocksLatently(self):
+        """
+        Same as L{test_twoCommandBlocks}, but with slower callbacks.
+        """
+        self.pauseHolders()
+        self.test_twoCommandBlocks(self.flushHolders)
+
+
+    def test_commandBlockEndTwice(self):
+        """
+        L{CommandBlock.end} will raise L{AlreadyFinishedError} when called more
+        than once.
+        """
+        txn = self.pool.connection()
+        block = txn.commandBlock()
+        block.end()
+        self.assertRaises(AlreadyFinishedError, block.end)
+
+
+    def test_commandBlockDelaysCommit(self):
+        """
+        Some command blocks need to run asynchronously, without the overall
+        transaction-managing code knowing how far they've progressed.  Therefore
+        when you call {IAsyncTransaction.commit}(), it should not actually take
+        effect if there are any pending command blocks.
+        """
+        txn = self.pool.connection()
+        block = txn.commandBlock()
+        commitResult = resultOf(txn.commit())
+        block.execSQL("in block")
+        self.assertEquals(commitResult, [])
+        self.assertEquals(self.factory.connections[0].cursors[0].allExecutions,
+                          [("in block", [])])
+        block.end()
+        self.assertEquals(commitResult, [None])
+
+
+    def test_commandBlockDoesntDelayAbort(self):
+        """
+        A L{CommandBlock} can't possibly have anything interesting to say about
+        a transaction that gets rolled back, so C{abort} applies immediately;
+        all outstanding C{execSQL}s will fail immediately, on both command
+        blocks and on the transaction itself.
+        """
+        txn = self.pool.connection()
+        block = txn.commandBlock()
+        block2 = txn.commandBlock()
+        abortResult = resultOf(txn.abort())
+        self.assertEquals(abortResult, [None])
+        self.assertRaises(AlreadyFinishedError, block2.execSQL, "bar")
+        self.assertRaises(AlreadyFinishedError, block.execSQL, "foo")
+        self.assertRaises(AlreadyFinishedError, txn.execSQL, "baz")
+        self.assertEquals(self.factory.connections[0].cursors[0].allExecutions,
+                          [])
+        # end() should _not_ raise an exception, because this is the sort of
+        # thing that might be around a try/finally or try/except; it's just
+        # putting the commandBlock itself into a state consistent with the
+        # transaction.
+        block.end()
+        block2.end()
+
+
+    def test_endedBlockDoesntExecuteMoreSQL(self):
+        """
+        Attempting to execute SQL on a L{CommandBlock} which has had C{end}
+        called on it will result in an L{AlreadyFinishedError}.
+        """
+        txn = self.pool.connection()
+        block = txn.commandBlock()
+        block.end()
+        self.assertRaises(AlreadyFinishedError, block.execSQL, "hello")
+        self.assertEquals(self.factory.connections[0].cursors[0].allExecutions,
+                          [])
+
+
+    def test_commandBlockAfterCommitRaises(self):
+        """
+        Once an L{IAsyncTransaction} has been committed, L{commandBlock} raises
+        an exception.
+        """
+        txn = self.pool.connection()
+        txn.commit()
+        self.assertRaises(AlreadyFinishedError, txn.commandBlock)
+
+
+    def test_commandBlockAfterAbortRaises(self):
+        """
+        Once an L{IAsyncTransaction} has been committed, L{commandBlock} raises
+        an exception.
+        """
+        txn = self.pool.connection()
+        txn.abort()
+        self.assertRaises(AlreadyFinishedError, txn.commandBlock)
+
+
+

Modified: CalendarServer/trunk/txdav/base/propertystore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/base/propertystore/sql.py	2011-03-24 20:37:12 UTC (rev 7258)
+++ CalendarServer/trunk/txdav/base/propertystore/sql.py	2011-03-24 20:45:31 UTC (rev 7259)
@@ -55,6 +55,23 @@
                         Where=prop.RESOURCE_ID == Parameter("resourceID"))
 
 
+    @inlineCallbacks
+    def _refresh(self, txn):
+        """
+        Load, or re-load, this object with the given transaction; first from
+        memcache, then pulling from the database again.
+        """
+        # Cache existing properties in this object
+        # Look for memcache entry first
+        rows = yield self._cacher.get(str(self._resourceID))
+        if rows is None:
+            rows = yield self._allWithID.on(txn, resourceID=self._resourceID)
+            yield self._cacher.set(str(self._resourceID),
+                                   rows if rows is not None else ())
+        for name, uid, value in rows:
+            self._cached[(name, uid)] = value
+
+
     @classmethod
     @inlineCallbacks
     def load(cls, defaultuser, txn, resourceID, created=False):
@@ -64,16 +81,7 @@
         self._resourceID = resourceID
         self._cached = {}
         if not created:
-            # Cache existing properties in this object
-            # Look for memcache entry first
-            rows = yield self._cacher.get(str(self._resourceID))
-            if rows is None:
-                rows = yield self._allWithID.on(txn,
-                                                resourceID=self._resourceID)
-                yield self._cacher.set(str(self._resourceID),
-                                       rows if rows is not None else ())
-            for name, uid, value in rows:
-                self._cached[(name, uid)] = value
+            yield self._refresh(txn)
         returnValue(self)
 
 
@@ -170,16 +178,29 @@
         key_str = key.toString()
         value_str = value.toxml()
 
-        if (key_str, uid) in self._cached:
-            self._updateQuery.on(self._txn, resourceID=self._resourceID,
-                                 value=value_str, name=key_str, uid=uid)
-        else:
-            self._insertQuery.on(self._txn, resourceID=self._resourceID,
-                                 value=value_str, name=key_str, uid=uid)
+        tried = []
+
+        wasCached = [(key_str, uid) in self._cached]
         self._cached[(key_str, uid)] = value_str
-        self._cacher.delete(str(self._resourceID))
+        @inlineCallbacks
+        def trySetItem(txn):
+            if tried:
+                yield self._refresh(txn)
+                wasCached[:] = [(key_str, uid) in self._cached]
+            tried.append(True)
+            if wasCached[0]:
+                yield self._updateQuery.on(
+                    txn, resourceID=self._resourceID, value=value_str,
+                    name=key_str, uid=uid)
+            else:
+                yield self._insertQuery.on(
+                    txn, resourceID=self._resourceID, value=value_str,
+                    name=key_str, uid=uid)
+            self._cacher.delete(str(self._resourceID))
+        self._txn.subtransaction(trySetItem)
 
 
+
     _deleteQuery = Delete(
         prop, Where=(prop.RESOURCE_ID == Parameter("resourceID")).And(
             prop.NAME == Parameter("name")).And(

Modified: CalendarServer/trunk/txdav/base/propertystore/test/test_sql.py
===================================================================
--- CalendarServer/trunk/txdav/base/propertystore/test/test_sql.py	2011-03-24 20:37:12 UTC (rev 7258)
+++ CalendarServer/trunk/txdav/base/propertystore/test/test_sql.py	2011-03-24 20:45:31 UTC (rev 7259)
@@ -23,21 +23,24 @@
 
 from txdav.common.datastore.test.util import buildStore, StubNotifierFactory
 
-from txdav.base.propertystore.base import PropertyName
-from txdav.base.propertystore.test import base
+from txdav.base.propertystore.test.base import (
+    PropertyStoreTest, propertyName, propertyValue)
 
 from twistedcaldav import memcacher
+from twisted.internet.defer import gatherResults
+from twext.enterprise.ienterprise import AlreadyFinishedError
 from twistedcaldav.config import config
 
 try:
     from txdav.base.propertystore.sql import PropertyStore
 except ImportError, e:
+    # XXX: when could this ever fail?
     PropertyStore = None
     importErrorMessage = str(e)
 
 
 
-class PropertyStoreTest(base.PropertyStoreTest):
+class PropertyStoreTest(PropertyStoreTest):
 
 
     @inlineCallbacks
@@ -111,10 +114,52 @@
         self.propertyStore2._globalKeys = store._globalKeys
 
 
+    @inlineCallbacks
+    def test_concurrentInsertion(self):
+        """
+        When two property stores set the same value, both should succeed, and
+        update the cache.  Whoever wins the race (i.e. updates last) will set
+        the last property value.
+        """
+        pname = propertyName("concurrent")
+        pval1 = propertyValue("alpha")
+        pval2 = propertyValue("beta")
+        concurrentTxn = self.store.newTransaction()
+        @inlineCallbacks
+        def maybeAbortIt():
+            try:
+                yield concurrentTxn.abort()
+            except AlreadyFinishedError:
+                pass
+        self.addCleanup(maybeAbortIt)
+        concurrentPropertyStore = yield PropertyStore.load(
+            "user01", concurrentTxn, 1
+        )
+        concurrentPropertyStore[pname] = pval1
+        race = []
+        def tiebreaker(label):
+            # Let's not get into the business of figuring out who the database
+            # concurrency rules are suppsoed to pick; it might differ.  We just
+            # take the answer we're given for who gets to be the final writer,
+            # and make sure that matches the property read in the next
+            # transaction.
+            def breaktie(result):
+                race.append(label)
+                return result
+            return breaktie
+        a = concurrentTxn.commit().addCallback(tiebreaker('a'))
+        self.propertyStore[pname] = pval2
+        b = self._txn.commit().addCallback(tiebreaker('b'))
+        del self._txn
+        self.assertEquals((yield gatherResults([a, b])), [None, None])
+        yield self._abort(self.propertyStore)
+        winner = {'a': pval1,
+                  'b': pval2}[race[-1]]
+        self.assertEquals(self.propertyStore[pname], winner)
 
+
+
 if PropertyStore is None:
     PropertyStoreTest.skip = importErrorMessage
 
 
-def propertyName(name):
-    return PropertyName("http://calendarserver.org/ns/test/", name)


Property changes on: CalendarServer/trunk/txdav/caldav/datastore/index_file.py
___________________________________________________________________
Modified: svn:mergeinfo
   - /CalendarServer/branches/config-separation/txdav/caldav/datastore/index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/caldav/datastore/index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/index_file.py:6167-6191
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/caldav/datastore/index_file.py:5936-5981
/CalendarServer/branches/new-store-no-caldavfile/txdav/caldav/datastore/index_file.py:5911-5935
/CalendarServer/branches/new-store/txdav/caldav/datastore/index_file.py:5594-5934
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/caldav/datastore/index_file.py:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/caldav/datastore/index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/caldav/datastore/index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/caldav/datastore/index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/caldav/datastore/index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/pycalendar/txdav/caldav/datastore/index_file.py:7085-7206
/CalendarServer/branches/users/cdaboo/pycard/txdav/caldav/datastore/index_file.py:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/caldav/datastore/index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/caldav/datastore/index_file.py:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/txdav/caldav/datastore/index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/caldav/datastore/index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/caldav/datastore/index_file.py:6932-7023
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/caldav/datastore/index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/caldav/datastore/index_file.py:6893-6900
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/caldav/datastore/index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/index_file.py:6369
/CalendarServer/branches/users/glyph/oracle/txdav/caldav/datastore/index_file.py:7106-7155
/CalendarServer/branches/users/glyph/sendfdport/txdav/caldav/datastore/index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/caldav/datastore/index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/caldav/datastore/index_file.py:5929-6073
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/caldav/datastore/index_file.py:5084-5149
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/caldav/datastore/index_file.py:5052-5061
/CalendarServer/branches/users/sagen/locations-resources/txdav/caldav/datastore/index_file.py:5032-5051
/CalendarServer/branches/users/sagen/purge_old_events/txdav/caldav/datastore/index_file.py:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/caldav/datastore/index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/caldav/datastore/index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/caldav/datastore/index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/caldav/datastore/index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/index.py:6322-6394
   + /CalendarServer/branches/config-separation/txdav/caldav/datastore/index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/caldav/datastore/index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/index_file.py:6167-6191
/CalendarServer/branches/new-store/txdav/caldav/datastore/index_file.py:5594-5934
/CalendarServer/branches/new-store-no-caldavfile/txdav/caldav/datastore/index_file.py:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/caldav/datastore/index_file.py:5936-5981
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/caldav/datastore/index_file.py:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/caldav/datastore/index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/caldav/datastore/index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/caldav/datastore/index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/caldav/datastore/index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/pycalendar/txdav/caldav/datastore/index_file.py:7085-7206
/CalendarServer/branches/users/cdaboo/pycard/txdav/caldav/datastore/index_file.py:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/caldav/datastore/index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/caldav/datastore/index_file.py:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/txdav/caldav/datastore/index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/caldav/datastore/index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/caldav/datastore/index_file.py:6932-7023
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/caldav/datastore/index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/caldav/datastore/index_file.py:6893-6900
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/caldav/datastore/index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/index_file.py:6369
/CalendarServer/branches/users/glyph/oracle/txdav/caldav/datastore/index_file.py:7106-7155
/CalendarServer/branches/users/glyph/sendfdport/txdav/caldav/datastore/index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/caldav/datastore/index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/caldav/datastore/index_file.py:5929-6073
/CalendarServer/branches/users/glyph/subtransactions/txdav/caldav/datastore/index_file.py:7248-7258
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/caldav/datastore/index_file.py:5084-5149
/CalendarServer/branches/users/sagen/locations-resources/txdav/caldav/datastore/index_file.py:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/caldav/datastore/index_file.py:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events/txdav/caldav/datastore/index_file.py:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/caldav/datastore/index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/caldav/datastore/index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/caldav/datastore/index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/caldav/datastore/index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/index.py:6322-6394


Property changes on: CalendarServer/trunk/txdav/caldav/datastore/test/test_index_file.py
___________________________________________________________________
Modified: svn:mergeinfo
   - /CalendarServer/branches/config-separation/txdav/caldav/datastore/test/test_index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/caldav/datastore/test/test_index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/test_index_file.py:6167-6191
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/caldav/datastore/test/test_index_file.py:5936-5981
/CalendarServer/branches/new-store-no-caldavfile/txdav/caldav/datastore/test/test_index_file.py:5911-5935
/CalendarServer/branches/new-store/txdav/caldav/datastore/test/test_index_file.py:5594-5934
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/caldav/datastore/test/test_index_file.py:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/caldav/datastore/test/test_index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/caldav/datastore/test/test_index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/caldav/datastore/test/test_index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/caldav/datastore/test/test_index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/pycalendar/txdav/caldav/datastore/test/test_index_file.py:7085-7206
/CalendarServer/branches/users/cdaboo/pycard/txdav/caldav/datastore/test/test_index_file.py:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/caldav/datastore/test/test_index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/caldav/datastore/test/test_index_file.py:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/txdav/caldav/datastore/test/test_index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/caldav/datastore/test/test_index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/caldav/datastore/test/test_index_file.py:6932-7023
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/caldav/datastore/test/test_index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/caldav/datastore/test/test_index_file.py:6893-6900
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/caldav/datastore/test/test_index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/test/test_index_file.py:6369
/CalendarServer/branches/users/glyph/oracle/txdav/caldav/datastore/test/test_index_file.py:7106-7155
/CalendarServer/branches/users/glyph/sendfdport/txdav/caldav/datastore/test/test_index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/caldav/datastore/test/test_index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/caldav/datastore/test/test_index_file.py:5929-6073
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/caldav/datastore/test/test_index_file.py:5084-5149
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/caldav/datastore/test/test_index_file.py:5052-5061
/CalendarServer/branches/users/sagen/locations-resources/txdav/caldav/datastore/test/test_index_file.py:5032-5051
/CalendarServer/branches/users/sagen/purge_old_events/txdav/caldav/datastore/test/test_index_file.py:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/caldav/datastore/test/test_index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/caldav/datastore/test/test_index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/caldav/datastore/test/test_index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/caldav/datastore/test/test_index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/test/test_index.py:6322-6394
   + /CalendarServer/branches/config-separation/txdav/caldav/datastore/test/test_index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/caldav/datastore/test/test_index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/test_index_file.py:6167-6191
/CalendarServer/branches/new-store/txdav/caldav/datastore/test/test_index_file.py:5594-5934
/CalendarServer/branches/new-store-no-caldavfile/txdav/caldav/datastore/test/test_index_file.py:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/caldav/datastore/test/test_index_file.py:5936-5981
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/caldav/datastore/test/test_index_file.py:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/caldav/datastore/test/test_index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/caldav/datastore/test/test_index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/caldav/datastore/test/test_index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/caldav/datastore/test/test_index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/pycalendar/txdav/caldav/datastore/test/test_index_file.py:7085-7206
/CalendarServer/branches/users/cdaboo/pycard/txdav/caldav/datastore/test/test_index_file.py:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/caldav/datastore/test/test_index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/caldav/datastore/test/test_index_file.py:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/txdav/caldav/datastore/test/test_index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/caldav/datastore/test/test_index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/caldav/datastore/test/test_index_file.py:6932-7023
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/caldav/datastore/test/test_index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/caldav/datastore/test/test_index_file.py:6893-6900
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/caldav/datastore/test/test_index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/test/test_index_file.py:6369
/CalendarServer/branches/users/glyph/oracle/txdav/caldav/datastore/test/test_index_file.py:7106-7155
/CalendarServer/branches/users/glyph/sendfdport/txdav/caldav/datastore/test/test_index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/caldav/datastore/test/test_index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/caldav/datastore/test/test_index_file.py:5929-6073
/CalendarServer/branches/users/glyph/subtransactions/txdav/caldav/datastore/test/test_index_file.py:7248-7258
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/caldav/datastore/test/test_index_file.py:5084-5149
/CalendarServer/branches/users/sagen/locations-resources/txdav/caldav/datastore/test/test_index_file.py:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/caldav/datastore/test/test_index_file.py:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events/txdav/caldav/datastore/test/test_index_file.py:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/caldav/datastore/test/test_index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/caldav/datastore/test/test_index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/caldav/datastore/test/test_index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/caldav/datastore/test/test_index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/test/test_index.py:6322-6394


Property changes on: CalendarServer/trunk/txdav/carddav/datastore/index_file.py
___________________________________________________________________
Modified: svn:mergeinfo
   - /CalendarServer/branches/config-separation/txdav/carddav/datastore/index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/carddav/datastore/index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/index_file.py:6167-6191
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/carddav/datastore/index_file.py:5936-5981
/CalendarServer/branches/new-store-no-caldavfile/txdav/carddav/datastore/index_file.py:5911-5935
/CalendarServer/branches/new-store/txdav/carddav/datastore/index_file.py:5594-5934
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/carddav/datastore/index_file.py:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/carddav/datastore/index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/carddav/datastore/index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/carddav/datastore/index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/carddav/datastore/index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/pycalendar/txdav/carddav/datastore/index_file.py:7085-7206
/CalendarServer/branches/users/cdaboo/pycard/txdav/carddav/datastore/index_file.py:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/carddav/datastore/index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/carddav/datastore/index_file.py:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/txdav/carddav/datastore/index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/carddav/datastore/index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/carddav/datastore/index_file.py:6932-7023
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/carddav/datastore/index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/carddav/datastore/index_file.py:6893-6900
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/carddav/datastore/index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/index_file.py:6369
/CalendarServer/branches/users/glyph/oracle/txdav/carddav/datastore/index_file.py:7106-7155
/CalendarServer/branches/users/glyph/sendfdport/txdav/carddav/datastore/index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/carddav/datastore/index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/carddav/datastore/index_file.py:5929-6073
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/carddav/datastore/index_file.py:5084-5149
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/carddav/datastore/index_file.py:5052-5061
/CalendarServer/branches/users/sagen/locations-resources/txdav/carddav/datastore/index_file.py:5032-5051
/CalendarServer/branches/users/sagen/purge_old_events/txdav/carddav/datastore/index_file.py:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/carddav/datastore/index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/carddav/datastore/index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/carddav/datastore/index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/carddav/datastore/index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/vcardindex.py:6322-6394
   + /CalendarServer/branches/config-separation/txdav/carddav/datastore/index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/carddav/datastore/index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/index_file.py:6167-6191
/CalendarServer/branches/new-store/txdav/carddav/datastore/index_file.py:5594-5934
/CalendarServer/branches/new-store-no-caldavfile/txdav/carddav/datastore/index_file.py:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/carddav/datastore/index_file.py:5936-5981
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/carddav/datastore/index_file.py:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/carddav/datastore/index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/carddav/datastore/index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/carddav/datastore/index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/carddav/datastore/index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/pycalendar/txdav/carddav/datastore/index_file.py:7085-7206
/CalendarServer/branches/users/cdaboo/pycard/txdav/carddav/datastore/index_file.py:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/carddav/datastore/index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/carddav/datastore/index_file.py:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/txdav/carddav/datastore/index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/carddav/datastore/index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/carddav/datastore/index_file.py:6932-7023
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/carddav/datastore/index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/carddav/datastore/index_file.py:6893-6900
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/carddav/datastore/index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/index_file.py:6369
/CalendarServer/branches/users/glyph/oracle/txdav/carddav/datastore/index_file.py:7106-7155
/CalendarServer/branches/users/glyph/sendfdport/txdav/carddav/datastore/index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/carddav/datastore/index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/carddav/datastore/index_file.py:5929-6073
/CalendarServer/branches/users/glyph/subtransactions/txdav/carddav/datastore/index_file.py:7248-7258
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/carddav/datastore/index_file.py:5084-5149
/CalendarServer/branches/users/sagen/locations-resources/txdav/carddav/datastore/index_file.py:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/carddav/datastore/index_file.py:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events/txdav/carddav/datastore/index_file.py:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/carddav/datastore/index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/carddav/datastore/index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/carddav/datastore/index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/carddav/datastore/index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/vcardindex.py:6322-6394


Property changes on: CalendarServer/trunk/txdav/carddav/datastore/test/test_index_file.py
___________________________________________________________________
Modified: svn:mergeinfo
   - /CalendarServer/branches/config-separation/txdav/carddav/datastore/test/test_index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/carddav/datastore/test/test_index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/test/test_index_file.py:6167-6191
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/carddav/datastore/test/test_index_file.py:5936-5981
/CalendarServer/branches/new-store-no-caldavfile/txdav/carddav/datastore/test/test_index_file.py:5911-5935
/CalendarServer/branches/new-store/txdav/carddav/datastore/test/test_index_file.py:5594-5934
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/carddav/datastore/test/test_index_file.py:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/carddav/datastore/test/test_index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/carddav/datastore/test/test_index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/carddav/datastore/test/test_index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/carddav/datastore/test/test_index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/pycalendar/txdav/carddav/datastore/test/test_index_file.py:7085-7206
/CalendarServer/branches/users/cdaboo/pycard/txdav/carddav/datastore/test/test_index_file.py:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/carddav/datastore/test/test_index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/carddav/datastore/test/test_index_file.py:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/txdav/carddav/datastore/test/test_index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/carddav/datastore/test/test_index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/carddav/datastore/test/test_index_file.py:6932-7023
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/carddav/datastore/test/test_index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/carddav/datastore/test/test_index_file.py:6893-6900
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/carddav/datastore/test/test_index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/test/test_index_file.py:6369
/CalendarServer/branches/users/glyph/oracle/txdav/carddav/datastore/test/test_index_file.py:7106-7155
/CalendarServer/branches/users/glyph/sendfdport/txdav/carddav/datastore/test/test_index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/carddav/datastore/test/test_index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/carddav/datastore/test/test_index_file.py:5929-6073
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/carddav/datastore/test/test_index_file.py:5084-5149
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/carddav/datastore/test/test_index_file.py:5052-5061
/CalendarServer/branches/users/sagen/locations-resources/txdav/carddav/datastore/test/test_index_file.py:5032-5051
/CalendarServer/branches/users/sagen/purge_old_events/txdav/carddav/datastore/test/test_index_file.py:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/carddav/datastore/test/test_index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/carddav/datastore/test/test_index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/carddav/datastore/test/test_index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/carddav/datastore/test/test_index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/test/test_vcardindex.py:6322-6394
   + /CalendarServer/branches/config-separation/txdav/carddav/datastore/test/test_index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/carddav/datastore/test/test_index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/test/test_index_file.py:6167-6191
/CalendarServer/branches/new-store/txdav/carddav/datastore/test/test_index_file.py:5594-5934
/CalendarServer/branches/new-store-no-caldavfile/txdav/carddav/datastore/test/test_index_file.py:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/carddav/datastore/test/test_index_file.py:5936-5981
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/carddav/datastore/test/test_index_file.py:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/carddav/datastore/test/test_index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/carddav/datastore/test/test_index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/carddav/datastore/test/test_index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/carddav/datastore/test/test_index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/pycalendar/txdav/carddav/datastore/test/test_index_file.py:7085-7206
/CalendarServer/branches/users/cdaboo/pycard/txdav/carddav/datastore/test/test_index_file.py:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/carddav/datastore/test/test_index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/carddav/datastore/test/test_index_file.py:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/txdav/carddav/datastore/test/test_index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/carddav/datastore/test/test_index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/carddav/datastore/test/test_index_file.py:6932-7023
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/carddav/datastore/test/test_index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/carddav/datastore/test/test_index_file.py:6893-6900
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/carddav/datastore/test/test_index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/test/test_index_file.py:6369
/CalendarServer/branches/users/glyph/oracle/txdav/carddav/datastore/test/test_index_file.py:7106-7155
/CalendarServer/branches/users/glyph/sendfdport/txdav/carddav/datastore/test/test_index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/carddav/datastore/test/test_index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/carddav/datastore/test/test_index_file.py:5929-6073
/CalendarServer/branches/users/glyph/subtransactions/txdav/carddav/datastore/test/test_index_file.py:7248-7258
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/carddav/datastore/test/test_index_file.py:5084-5149
/CalendarServer/branches/users/sagen/locations-resources/txdav/carddav/datastore/test/test_index_file.py:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/carddav/datastore/test/test_index_file.py:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events/txdav/carddav/datastore/test/test_index_file.py:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/carddav/datastore/test/test_index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/carddav/datastore/test/test_index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/carddav/datastore/test/test_index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/carddav/datastore/test/test_index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/test/test_vcardindex.py:6322-6394

Modified: CalendarServer/trunk/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql.py	2011-03-24 20:37:12 UTC (rev 7258)
+++ CalendarServer/trunk/txdav/common/datastore/sql.py	2011-03-24 20:45:31 UTC (rev 7259)
@@ -36,6 +36,7 @@
 from twisted.python import hashlib
 from twisted.python.modules import getModule
 from twisted.python.util import FancyEqMixin
+from twisted.python.failure import Failure
 
 from twisted.internet.defer import inlineCallbacks, returnValue, succeed
 
@@ -54,11 +55,12 @@
 from txdav.common.icommondatastore import HomeChildNameNotAllowedError, \
     HomeChildNameAlreadyExistsError, NoSuchHomeChildError, \
     ObjectResourceNameNotAllowedError, ObjectResourceNameAlreadyExistsError, \
-    NoSuchObjectResourceError
+    NoSuchObjectResourceError, AllRetriesFailed
 from txdav.common.inotifications import INotificationCollection, \
     INotificationObject
 
 from twext.python.clsprop import classproperty
+from twext.enterprise.ienterprise import AlreadyFinishedError
 from twext.enterprise.dal.syntax import Delete
 from twext.enterprise.dal.syntax import Insert
 from twext.enterprise.dal.syntax import Len
@@ -238,6 +240,85 @@
         self._postCommitOperations.append(operation)
 
 
+    _savepointCounter = 0
+
+    def _savepoint(self):
+        """
+        Generate a new SavepointAction whose name is unique in this transaction.
+        """
+        self._savepointCounter += 1
+        return SavepointAction('sp%d' % (self._savepointCounter,))
+
+
+    @inlineCallbacks
+    def subtransaction(self, thunk, retries=1):
+        """
+        Create a limited transaction object, which provides only SQL execution,
+        and run a function in a sub-transaction (savepoint) context, with that
+        object to execute SQL on.
+
+        @param thunk: a 1-argument callable which returns a Deferred when it is
+            done.  If this Deferred fails, 
+
+        @param retries: the number of times to re-try C{thunk} before deciding
+            that it's legitimately failed.
+
+        @return: a L{Deferred} which fires or fails according to the logic in
+            C{thunk}.  If it succeeds, it will return the value that C{thunk}
+            returned.
+        """
+        # Right now this code is covered mostly by the automated property store
+        # tests.  It should have more direct test coverage.
+
+        # TODO: we should really have a list of acceptable exceptions for
+        # failure and not blanket catch, but that involves more knowledge of the
+        # database driver in use than we currently possess at this layer.
+        block = self._sqlTxn.commandBlock()
+        sp = self._savepoint()
+        failuresToMaybeLog = []
+        triesLeft = retries + 1
+        try:
+            while True:
+                yield sp.acquire(block)
+                try:
+                    result = yield thunk(block)
+                except:
+                    failuresToMaybeLog.append(Failure())
+                    yield sp.rollback(block)
+                    if triesLeft:
+                        triesLeft -= 1
+                        # Important to get the new block before the old one has
+                        # been completed; since we almost certainly have some
+                        # writes to do, the caller of commit() will expect that
+                        # they actually get done, even if they didn't actually
+                        # block or yield to wait for them!  (c.f. property
+                        # store writes.)
+                        newBlock = self._sqlTxn.commandBlock()
+                        block.end()
+                        block = newBlock
+                        sp = self._savepoint()
+                    else:
+                        block.end()
+                        for f in failuresToMaybeLog:
+                            # TODO: direct tests, to make sure error logging
+                            # happens correctly in all cases.
+                            log.err(f)
+                        raise AllRetriesFailed()
+                else:
+                    yield sp.release(block)
+                    block.end()
+                    returnValue(result)
+        except AlreadyFinishedError:
+            # Interfering agents may disrupt our plans by calling abort()
+            # halfway through trying to do this subtransaction.  In that case -
+            # and only that case - acquire() or release() or commandBlock() may
+            # raise an AlreadyFinishedError (either synchronously, or in the
+            # case of the first two, possibly asynchronously as well).  We can
+            # safely ignore this, because it can't have any real effect; our
+            # caller shouldn't be paying attention anyway.
+            block.end()
+
+
     def execSQL(self, *a, **kw):
         """
         Execute some SQL (delegate to L{IAsyncTransaction}).

Modified: CalendarServer/trunk/txdav/common/icommondatastore.py
===================================================================
--- CalendarServer/trunk/txdav/common/icommondatastore.py	2011-03-24 20:37:12 UTC (rev 7258)
+++ CalendarServer/trunk/txdav/common/icommondatastore.py	2011-03-24 20:45:31 UTC (rev 7259)
@@ -108,6 +108,12 @@
     Uh, oh.
     """
 
+class AllRetriesFailed(CommonStoreError):
+    """
+    In a re-tried subtransaction, all attempts failed to produce useful
+    progress.  Other exceptions will be logged.
+    """
+
 # Indexing / sync tokens
 
 class ReservationError(LookupError):
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20110324/03447f89/attachment-0001.html>


More information about the calendarserver-changes mailing list