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

Tim Peters tim.one at comcast.net
Fri May 6 11:06:27 EDT 2005


Log message for revision 30280:
  Merge rev 30279 from 3.4 branch.
  
  Fix obscure bug.
  
  If a threaded transaction manager ever passed None to
  the Transaction constructor's `synchronizers` argument, then
  synchronizers registered later in the same transaction
  were invisible to the transaction, and so their afterCompletion()
  methods wouldn't get called when the transaction ended.
  

Changed:
  U   ZODB/trunk/NEWS.txt
  U   ZODB/trunk/src/ZODB/tests/synchronizers.txt
  U   ZODB/trunk/src/transaction/_manager.py

-=-
Modified: ZODB/trunk/NEWS.txt
===================================================================
--- ZODB/trunk/NEWS.txt	2005-05-06 15:04:41 UTC (rev 30279)
+++ ZODB/trunk/NEWS.txt	2005-05-06 15:06:26 UTC (rev 30280)
@@ -3,6 +3,23 @@
 Release date: DD-MMM-YYYY
 
 
+What's new in ZODB3 3.4a7?
+==========================
+Release date: 06-May-2005
+
+This was an internal release, to fix an obscure older bug discovered while
+testing the ``ISynchronizer`` enhancements.
+
+transaction
+-----------
+
+- If the first activity seen by a new ``ThreadTransactionManager`` was
+  an explicit ``begin()`` call, then synchronizers registered after that
+  (but still during the first transaction) were not communicated to the
+  transaction object.  As a result, the ``afterCompletion()`` methods of
+  registered synchronizers weren't called when the first transaction ended.
+
+
 What's new in ZODB3 3.4a6?
 ==========================
 Release date: 05-May-2005

Modified: ZODB/trunk/src/ZODB/tests/synchronizers.txt
===================================================================
--- ZODB/trunk/src/ZODB/tests/synchronizers.txt	2005-05-06 15:04:41 UTC (rev 30279)
+++ ZODB/trunk/src/ZODB/tests/synchronizers.txt	2005-05-06 15:06:26 UTC (rev 30280)
@@ -50,7 +50,6 @@
     >>> st.sync_called  # False before 3.4
     True
 
-
 Clean up.  Closing db isn't enough -- closing a DB doesn't close its
 Connections.  Leaving our Connection open here can cause the
 SimpleStorage.sync() method to get called later, during another test, and
@@ -58,4 +57,46 @@
 a weird traceback then ;-)
 
     >>> cn.close()
+
+One more, very obscure.  It was the case that if the first action a new
+threaded transaction manager saw was a begin() call, then synchronizers
+registered after that in the same transaction weren't communicated to
+the Transaction object, and so the storage's afterCompletion() hook wasn't
+called when the transaction commited.  None of the test suites (ZODB's,
+Zope 2.8's, or Zope3's) caught that, but apparently Zope3 takes this path
+at some point when serving pages.
+
+    >>> tm = transaction.ThreadTransactionManager()
+    >>> st.sync_called = False
+    >>> dummy = tm.begin()  # we're doing this _before_ opening a connection
+    >>> cn = db.open(txn_mgr=tm)
+    >>> rt = cn.root()      # make a change
+    >>> rt['c'] = 3
+    >>> st.sync_called
+    False
+
+Now ensure that st.afterCompletion() gets called by commit despite that the
+Connection registered after the transaction began:
+
+    >>> tm.commit()
+    >>> st.sync_called
+    True
+
+And try the same thing with a non-threaded TM:
+
+    >>> cn.close()
+    >>> tm = transaction.TransactionManager()
+    >>> st.sync_called = False
+    >>> dummy = tm.begin()  # we're doing this _before_ opening a connection
+    >>> cn = db.open(txn_mgr=tm)
+    >>> rt = cn.root()      # make a change
+    >>> rt['d'] = 4
+    >>> st.sync_called
+    False
+    >>> tm.commit()
+    >>> st.sync_called
+    True
+
+    >>> cn.close()
     >>> db.close()
+

Modified: ZODB/trunk/src/transaction/_manager.py
===================================================================
--- ZODB/trunk/src/transaction/_manager.py	2005-05-06 15:04:41 UTC (rev 30279)
+++ ZODB/trunk/src/transaction/_manager.py	2005-05-06 15:06:26 UTC (rev 30280)
@@ -33,7 +33,7 @@
 # at top level here.
 
 # Call the ISynchronizer newTransaction() method on every element of
-# WeakSet synchs (or skip it if synchs is None).
+# WeakSet synchs.
 # A transaction manager needs to do this whenever begin() is called.
 # Since it would be good if tm.get() returned the new transaction while
 # newTransaction() is running, calling this has to be delayed until after
@@ -43,6 +43,13 @@
     if synchs:
         synchs.map(lambda s: s.newTransaction(txn))
 
+# Important:  we must always pass a WeakSet (even if empty) to the Transaction
+# constructor:  synchronizers are registered with the TM, but the
+# ISynchronizer xyzCompletion() methods are called by Transactions without
+# consulting the TM, so we need to pass a mutable collection of synchronizers
+# so that Transactions "see" synchronizers that get registered after the
+# Transaction object is constructed.
+
 class TransactionManager(object):
 
     def __init__(self):
@@ -91,6 +98,7 @@
     def __init__(self):
         # _threads maps thread ids to transactions
         self._txns = {}
+
         # _synchs maps a thread id to a WeakSet of registered synchronizers.
         # The WeakSet is passed to the Transaction constructor, because the
         # latter needs to call the synchronizers when it commits.
@@ -101,7 +109,12 @@
         txn = self._txns.get(tid)
         if txn is not None:
             txn.abort()
+
         synchs = self._synchs.get(tid)
+        if synchs is None:
+            from ZODB.utils import WeakSet
+            synchs = self._synchs[tid] = WeakSet()
+
         txn = self._txns[tid] = Transaction(synchs, self)
         _new_transaction(txn, synchs)
         return txn
@@ -111,6 +124,9 @@
         txn = self._txns.get(tid)
         if txn is None:
             synchs = self._synchs.get(tid)
+            if synchs is None:
+                from ZODB.utils import WeakSet
+                synchs = self._synchs[tid] = WeakSet()
             txn = self._txns[tid] = Transaction(synchs, self)
         return txn
 
@@ -120,11 +136,10 @@
         del self._txns[tid]
 
     def registerSynch(self, synch):
-        from ZODB.utils import WeakSet
-
         tid = thread.get_ident()
         ws = self._synchs.get(tid)
         if ws is None:
+            from ZODB.utils import WeakSet
             ws = self._synchs[tid] = WeakSet()
         ws.add(synch)
 



More information about the Zodb-checkins mailing list