Commit b27cb2d1 authored by Tim Peters's avatar Tim Peters

Merge rev 30231 from 3.4 branch.

Port from ZODB 3.2.

Added new test checkSubtxnCommitDoesntGetInvalidations to
verify that a longstanding bug in subtransaction commit is
repaired.

Jim (Fulton) discovered this in ZODB 3.4's code, while implementing
savepoint/rollback.  Same bugs had been there at least since ZODB 3.1.

Also added news about the bug.
parent e4cf6cd8
......@@ -10,8 +10,24 @@ Release date: DD-MMM-2005
transaction
-----------
A ``getBeforeCommitHooks()`` method was added. It returns an iterable
producing the registered beforeCommit hooks.
- A ``getBeforeCommitHooks()`` method was added. It returns an iterable
producing the registered beforeCommit hooks.
- Doing a subtransaction commit erroneously processed invalidations, which
could lead to an inconsistent view of the database. For example, let T be
the transaction of which the subtransaction commit was a part. If T read a
persistent object O's state before the subtransaction commit, did not
commit new state of its own for O during its subtransaction commit, and O
was modified before the subtransaction commit by a different transaction,
then the subtransaction commit processed an invalidation for O, and the
state T read for O originally was discarded in T. If T went on to access O
again, it saw the newly committed (by a different transaction) state for O::
o_attr = O.some_attribute
get_transaction().commit(True)
assert o_attr == O.some_attribute
could fail, and despite that T never modifed O.
What's new in ZODB3 3.4a5?
......
......@@ -29,7 +29,7 @@ Compatibility
-------------
ZODB 3.5 requires Python 2.3.4 or later. For best results, we recommend
Python 2.3.5.
Python 2.3.5. Python 2.4.1 can also be used.
The Zope 2.8 release, and Zope3 releases, should be compatible with this
version of ZODB. Note that Zope 2.7 and higher includes ZEO, so this package
......
......@@ -363,6 +363,70 @@ class ZODBTests(unittest.TestCase):
self.obj = DecoyIndependent()
self.readConflict()
def checkSubtxnCommitDoesntGetInvalidations(self):
# Prior to ZODB 3.2.9 and 3.4, Connection.tpc_finish() processed
# invalidations even for a subtxn commit. This could make
# inconsistent state visible after a subtxn commit. There was a
# suspicion that POSKeyError was possible as a result, but I wasn't
# able to construct a case where that happened.
# Set up the database, to hold
# root --> "p" -> value = 1
# --> "q" -> value = 2
tm1 = transaction.TransactionManager()
conn = self._db.open(txn_mgr=tm1)
r1 = conn.root()
p = P()
p.value = 1
r1["p"] = p
q = P()
q.value = 2
r1["q"] = q
tm1.commit()
# Now txn T1 changes p.value to 3 locally (subtxn commit).
p.value = 3
tm1.commit(True)
# Start new txn T2 with a new connection.
tm2 = transaction.TransactionManager()
cn2 = self._db.open(txn_mgr=tm2)
r2 = cn2.root()
p2 = r2["p"]
self.assertEqual(p._p_oid, p2._p_oid)
# T2 shouldn't see T1's change of p.value to 3, because T1 didn't
# commit yet.
self.assertEqual(p2.value, 1)
# Change p.value to 4, and q.value to 5. Neither should be visible
# to T1, because T1 is still in progress.
p2.value = 4
q2 = r2["q"]
self.assertEqual(q._p_oid, q2._p_oid)
self.assertEqual(q2.value, 2)
q2.value = 5
tm2.commit()
# Back to T1. p and q still have the expected values.
rt = conn.root()
self.assertEqual(rt["p"].value, 3)
self.assertEqual(rt["q"].value, 2)
# Now do another subtxn commit in T1. This shouldn't change what
# T1 sees for p and q.
rt["r"] = P()
tm1.commit(True)
# Doing that subtxn commit in T1 should not process invalidations
# from T2's commit. p.value should still be 3 here (because that's
# what T1 subtxn-committed earlier), and q.value should still be 2.
# Prior to ZODB 3.2.9 and 3.4, q.value was 5 here.
rt = conn.root()
try:
self.assertEqual(rt["p"].value, 3)
self.assertEqual(rt["q"].value, 2)
finally:
tm1.abort()
def checkReadConflictErrorClearedDuringAbort(self):
# When a transaction is aborted, the "memory" of which
# objects were the cause of a ReadConflictError during
......@@ -634,7 +698,7 @@ class PoisonedJar:
def savepoint(self):
if self.break_savepoint:
raise PoisonedError("savepoint fails")
raise PoisonedError("savepoint fails")
def commit(*args):
pass
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment