[Zodb-checkins] SVN: ZODB/trunk/ Merge rev 27279 from 3.3 branch.

Tim Peters tim.one at comcast.net
Thu Aug 26 12:18:49 EDT 2004


Log message for revision 27280:
  Merge rev 27279 from 3.3 branch.
  
  Transaction.begin() didn't do anything.
  
  begin() is supposed to abort the current transaction, but
  Transaction.begin() did not.  Calling begin() on a transaction
  *manager* worked fine, and is the intended way to do a begin()
  in 3.3.  But calling begin() on a Transaction object is still
  very easy to do (e.g., the older get_transaction().begin()
  spelling still works), and shouldn't be a subtle disaster.
  


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


-=-
Modified: ZODB/trunk/NEWS.txt
===================================================================
--- ZODB/trunk/NEWS.txt	2004-08-26 16:15:50 UTC (rev 27279)
+++ ZODB/trunk/NEWS.txt	2004-08-26 16:18:49 UTC (rev 27280)
@@ -2,6 +2,25 @@
 =========================
 Release date: DD-MMM-YYYY
 
+transaction
+-----------
+
+Growing pains:  ZODB 3.1 and 3.2 had a bug wherein Transaction.begin()
+didn't abort the current transaction if the only pending changes were in a
+subtransaction.  In ZODB 3.3, it's intended that transaction managers be
+used instead of invoking methods directly on Transaction objects, and
+calling begin() on a transaction manager didn't have this old bug.  However,
+Transaction.begin() still exists in 3.3, and it had a worse bug:  it never
+aborted the transaction (not even if changes were pending outside of
+subtransactions).  Transaction.begin() has been changed to abort the
+transaction, although it's still strongly recommended to invoke begin() on
+the relevant transaction manager instead.  For example,
+
+    import transaction
+    transaction.begin()
+
+if using the default ThreadTransactionManager (see news for 3.3a3 below).
+
 BTrees
 ------
 

Modified: ZODB/trunk/src/ZODB/tests/testZODB.py
===================================================================
--- ZODB/trunk/src/ZODB/tests/testZODB.py	2004-08-26 16:15:50 UTC (rev 27279)
+++ ZODB/trunk/src/ZODB/tests/testZODB.py	2004-08-26 16:18:49 UTC (rev 27280)
@@ -344,6 +344,77 @@
         self.obj = DecoyIndependent()
         self.readConflict()
 
+    def checkTxnBeginImpliesAbort(self):
+        # begin() should do an abort() first, if needed.
+        cn = self._db.open()
+        rt = cn.root()
+        rt['a'] = 1
+
+        transaction.begin()  # should abort adding 'a' to the root
+        rt = cn.root()
+        self.assertRaises(KeyError, rt.__getitem__, 'a')
+
+        # A longstanding bug:  this didn't work if changes were only in
+        # subtransactions.
+        transaction.begin()
+        rt = cn.root()
+        rt['a'] = 2
+        transaction.commit(1)
+
+        transaction.begin()
+        rt = cn.root()
+        self.assertRaises(KeyError, rt.__getitem__, 'a')
+
+        # One more time, mixing "top level" and subtransaction changes.
+        transaction.begin()
+        rt = cn.root()
+        rt['a'] = 3
+        transaction.commit(1)
+        rt['b'] = 4
+
+        transaction.begin()
+        rt = cn.root()
+        self.assertRaises(KeyError, rt.__getitem__, 'a')
+        self.assertRaises(KeyError, rt.__getitem__, 'b')
+
+        # That used methods of the default transaction *manager*.  Alas,
+        # that's not necessarily the same as using methods of the current
+        # transaction, and, in fact, when this test was written,
+        # Transaction.begin() didn't do anything (everything from here
+        # down failed).
+        cn = self._db.open()
+        rt = cn.root()
+        rt['a'] = 1
+
+        transaction.get().begin()  # should abort adding 'a' to the root
+        rt = cn.root()
+        self.assertRaises(KeyError, rt.__getitem__, 'a')
+
+        # A longstanding bug:  this didn't work if changes were only in
+        # subtransactions.
+        transaction.get().begin()
+        rt = cn.root()
+        rt['a'] = 2
+        transaction.get().commit(1)
+
+        transaction.get().begin()
+        rt = cn.root()
+        self.assertRaises(KeyError, rt.__getitem__, 'a')
+
+        # One more time, mixing "top level" and subtransaction changes.
+        transaction.get().begin()
+        rt = cn.root()
+        rt['a'] = 3
+        transaction.get().commit(1)
+        rt['b'] = 4
+
+        transaction.get().begin()
+        rt = cn.root()
+        self.assertRaises(KeyError, rt.__getitem__, 'a')
+        self.assertRaises(KeyError, rt.__getitem__, 'b')
+
+        cn.close()
+
 def test_suite():
     return unittest.makeSuite(ZODBTests, 'check')
 

Modified: ZODB/trunk/src/transaction/_transaction.py
===================================================================
--- ZODB/trunk/src/transaction/_transaction.py	2004-08-26 16:15:50 UTC (rev 27279)
+++ ZODB/trunk/src/transaction/_transaction.py	2004-08-26 16:18:49 UTC (rev 27280)
@@ -230,11 +230,14 @@
                 self._resources.append(adapter)
 
     def begin(self):
-        # TODO: I'm not sure how this should be implemented.  Not doing
-        # anything now, but my best guess is: If nothing has happened
-        # yet, it's fine.  Otherwise, abort this transaction and let
-        # the txn manager create a new one.
-        pass
+        if (self._resources or
+              self._sub or
+              self._nonsub or
+              self._synchronizers):
+            self.abort()
+        # Else aborting wouldn't do anything, except if _manager is non-None,
+        # in which case it would do nothing besides uselessly free() this
+        # transaction.
 
     def commit(self, subtransaction=False):
         if not subtransaction and self._sub and self._resources:
@@ -247,8 +250,6 @@
         if not subtransaction:
             for s in self._synchronizers:
                 s.beforeCompletion(self)
-
-        if not subtransaction:
             self.status = Status.COMMITTING
 
         self._commitResources(subtransaction)



More information about the Zodb-checkins mailing list