[Zodb-checkins] SVN: ZODB/branches/3.4/src/transaction/ Fixed a bug
in savepoint rollback. It's not enough to rollback
Jim Fulton
jim at zope.com
Mon Apr 25 12:29:33 EDT 2005
Log message for revision 30165:
Fixed a bug in savepoint rollback. It's not enough to rollback
just the savepoint being rolled back because later savepoints
might involved data managers that hadn't joined when the savepoint
being rolled back was created.
Now, when a data manager joins and we have savepoints, we create a
data manager savepoint for the new data manager and add the
datamanager savepoint to all previous transaction savepoints. Note
that this data manager savepoint can be a special savepoint that just
calls abort on the data manager when it is rolled back.
Changed:
U ZODB/branches/3.4/src/transaction/_transaction.py
U ZODB/branches/3.4/src/transaction/savepoint.txt
U ZODB/branches/3.4/src/transaction/tests/test_savepoint.py
-=-
Modified: ZODB/branches/3.4/src/transaction/_transaction.py
===================================================================
--- ZODB/branches/3.4/src/transaction/_transaction.py 2005-04-25 15:16:20 UTC (rev 30164)
+++ ZODB/branches/3.4/src/transaction/_transaction.py 2005-04-25 16:29:28 UTC (rev 30165)
@@ -264,9 +264,7 @@
self._prior_operation_failed() # doesn't return, it raises
try:
- savepoint = Savepoint(self, optimistic)
- for resource in self._resources:
- savepoint.join(resource)
+ savepoint = Savepoint(self, optimistic, *self._resources)
except:
self._cleanup(self._resources)
self._saveCommitishError() # reraises!
@@ -598,25 +596,42 @@
"""
interface.implements(interfaces.ISavepoint)
- def __init__(self, transaction, optimistic):
+ def __init__(self, transaction, optimistic, *resources):
self.transaction = transaction
- self._savepoints = []
+ self._savepoints = savepoints = []
self.valid = True
self.next = self.previous = None
self.optimistic = optimistic
+
+ for datamanager in resources:
+ try:
+ savepoint = datamanager.savepoint
+ except AttributeError:
+ if not self.optimistic:
+ raise TypeError("Savepoints unsupported", datamanager)
+ savepoint = NoRollbackSavepoint(datamanager)
+ else:
+ savepoint = savepoint()
+
+ savepoints.append(savepoint)
def join(self, datamanager):
- try:
- savepoint = datamanager.savepoint
- except AttributeError:
- if not self.optimistic:
- raise TypeError("Savepoints unsupported", datamanager)
- savepoint = NoRollbackSavepoint(datamanager)
- else:
- savepoint = savepoint()
-
- self._savepoints.append(savepoint)
+
+ # A data manager has joined a transaction *after* a savepoint
+ # was created. A couple of things are different in this case:
+
+ # 1. We need to add it's savepoint to all previous savepoints.
+ # so that if they are rolled back, we roll this was back too.
+
+ # 2. We don't actualy need to ask it for a savepoint. Because
+ # is just joining, then we can abort it if there is an error,
+ # so we use an AbortSavepoint.
+ savepoint = AbortSavepoint(datamanager, self.transaction)
+ while self is not None:
+ self._savepoints.append(savepoint)
+ self = self.previous
+
def rollback(self):
if not self.valid:
raise interfaces.InvalidSavepointRollbackError
@@ -638,6 +653,15 @@
if self.previous is not None:
self.previous._invalidate_previous()
+class AbortSavepoint:
+
+ def __init__(self, datamanager, transaction):
+ self.datamanager = datamanager
+ self.transaction = transaction
+
+ def rollback(self):
+ self.datamanager.abort(self.transaction)
+
class NoRollbackSavepoint:
def __init__(self, datamanager):
Modified: ZODB/branches/3.4/src/transaction/savepoint.txt
===================================================================
--- ZODB/branches/3.4/src/transaction/savepoint.txt 2005-04-25 15:16:20 UTC (rev 30164)
+++ ZODB/branches/3.4/src/transaction/savepoint.txt 2005-04-25 16:29:28 UTC (rev 30165)
@@ -201,7 +201,7 @@
>>> transaction.abort()
However, a flag can be passed to the transaction savepoint method to
-indicate that databases without savepoint support should be tolderated
+indicate that databases without savepoint support should be tolerated
until a savepoint is roled back. This allows transactions to proceed
is there are no reasons to roll back:
@@ -212,8 +212,8 @@
>>> dm_no_sp['name']
'sue'
+ >>> dm_no_sp['name'] = 'sam'
>>> savepoint = transaction.savepoint(1)
- >>> dm_no_sp['name'] = 'sam'
>>> savepoint.rollback()
Traceback (most recent call last):
...
Modified: ZODB/branches/3.4/src/transaction/tests/test_savepoint.py
===================================================================
--- ZODB/branches/3.4/src/transaction/tests/test_savepoint.py 2005-04-25 15:16:20 UTC (rev 30164)
+++ ZODB/branches/3.4/src/transaction/tests/test_savepoint.py 2005-04-25 16:29:28 UTC (rev 30165)
@@ -19,9 +19,49 @@
from zope.testing import doctest
+def testRollbackRollsbackDataManagersThatJoinedLater():
+ """
+
+A savepoint needs to not just rollback it's savepoints, but needs to
+rollback savepoints for data managers that joined savepoints after the
+savepoint:
+
+ >>> import transaction.tests.savepointsample
+ >>> dm = transaction.tests.savepointsample.SampleSavepointDataManager()
+ >>> dm['name'] = 'bob'
+ >>> sp1 = transaction.savepoint()
+ >>> dm['job'] = 'geek'
+ >>> sp2 = transaction.savepoint()
+ >>> dm['salary'] = 'fun'
+ >>> dm2 = transaction.tests.savepointsample.SampleSavepointDataManager()
+ >>> dm2['name'] = 'sally'
+
+ >>> 'name' in dm
+ True
+ >>> 'job' in dm
+ True
+ >>> 'salary' in dm
+ True
+ >>> 'name' in dm2
+ True
+
+ >>> sp1.rollback()
+
+ >>> 'name' in dm
+ True
+ >>> 'job' in dm
+ False
+ >>> 'salary' in dm
+ False
+ >>> 'name' in dm2
+ False
+
+"""
+
def test_suite():
return unittest.TestSuite((
doctest.DocFileSuite('../savepoint.txt'),
+ doctest.DocTestSuite(),
))
if __name__ == '__main__':
More information about the Zodb-checkins
mailing list