[Zodb-checkins] SVN: ZODB/trunk/ Merge rev 30231 from 3.4 branch.

Tim Peters tim.one at comcast.net
Mon May 2 17:22:32 EDT 2005


Log message for revision 30232:
  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.
  

Changed:
  U   ZODB/trunk/NEWS.txt
  U   ZODB/trunk/README.txt
  U   ZODB/trunk/src/ZODB/tests/testZODB.py

-=-
Modified: ZODB/trunk/NEWS.txt
===================================================================
--- ZODB/trunk/NEWS.txt	2005-05-02 21:21:00 UTC (rev 30231)
+++ ZODB/trunk/NEWS.txt	2005-05-02 21:22:32 UTC (rev 30232)
@@ -10,10 +10,26 @@
 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?
 ==========================
 Release date: 25-Apr-2005

Modified: ZODB/trunk/README.txt
===================================================================
--- ZODB/trunk/README.txt	2005-05-02 21:21:00 UTC (rev 30231)
+++ ZODB/trunk/README.txt	2005-05-02 21:22:32 UTC (rev 30232)
@@ -29,7 +29,7 @@
 -------------
 
 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

Modified: ZODB/trunk/src/ZODB/tests/testZODB.py
===================================================================
--- ZODB/trunk/src/ZODB/tests/testZODB.py	2005-05-02 21:21:00 UTC (rev 30231)
+++ ZODB/trunk/src/ZODB/tests/testZODB.py	2005-05-02 21:22:32 UTC (rev 30232)
@@ -363,6 +363,70 @@
         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 @@
 
     def savepoint(self):
         if self.break_savepoint:
-            raise PoisonedError("savepoint fails")        
+            raise PoisonedError("savepoint fails")
 
     def commit(*args):
         pass



More information about the Zodb-checkins mailing list