[CalendarServer-changes] [7256] CalendarServer/branches/users/glyph/subtransactions/txdav/common
source_changes at macosforge.org
source_changes at macosforge.org
Thu Mar 24 13:36:51 PDT 2011
Revision: 7256
http://trac.macosforge.org/projects/calendarserver/changeset/7256
Author: glyph at apple.com
Date: 2011-03-24 13:36:51 -0700 (Thu, 24 Mar 2011)
Log Message:
-----------
subtransaction API.
Modified Paths:
--------------
CalendarServer/branches/users/glyph/subtransactions/txdav/common/datastore/sql.py
CalendarServer/branches/users/glyph/subtransactions/txdav/common/icommondatastore.py
Modified: CalendarServer/branches/users/glyph/subtransactions/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/glyph/subtransactions/txdav/common/datastore/sql.py 2011-03-24 20:36:40 UTC (rev 7255)
+++ CalendarServer/branches/users/glyph/subtransactions/txdav/common/datastore/sql.py 2011-03-24 20:36:51 UTC (rev 7256)
@@ -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/branches/users/glyph/subtransactions/txdav/common/icommondatastore.py
===================================================================
--- CalendarServer/branches/users/glyph/subtransactions/txdav/common/icommondatastore.py 2011-03-24 20:36:40 UTC (rev 7255)
+++ CalendarServer/branches/users/glyph/subtransactions/txdav/common/icommondatastore.py 2011-03-24 20:36:51 UTC (rev 7256)
@@ -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/f5b39414/attachment.html>
More information about the calendarserver-changes
mailing list