============= Synchronizers ============= Here are some tests that storage ``sync()`` methods get called at appropriate times in the life of a transaction. The tested behavior is new in ZODB 3.4. First define a lightweight storage with a ``sync()`` method: >>> import ZODB >>> from ZODB.MappingStorage import MappingStorage >>> import transaction >>> class SimpleStorage(MappingStorage): ... sync_called = False ... ... def sync(self, *args): ... self.sync_called = True Make a change locally: >>> st = SimpleStorage() >>> db = ZODB.DB(st) >>> cn = db.open() >>> rt = cn.root() >>> rt['a'] = 1 Sync should not have been called yet. >>> st.sync_called # False before 3.4 False ``sync()`` is called by the Connection's ``afterCompletion()`` hook after the commit completes. >>> transaction.commit() >>> st.sync_called # False before 3.4 True ``sync()`` is also called by the ``afterCompletion()`` hook after an abort. >>> st.sync_called = False >>> rt['b'] = 2 >>> transaction.abort() >>> st.sync_called # False before 3.4 True And ``sync()`` is called whenever we explicitly start a new transaction, via the ``newTransaction()`` hook. >>> st.sync_called = False >>> dummy = transaction.begin() >>> 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 our doctest-synthesized module globals no longer exist then. You get 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 synchronizers' ``afterCompletion()`` hooks weren't called when the transaction commited. None of the test suites (ZODB's, Zope 2.8's, or Zope3's) caught that, but apparently Zope 3 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(transaction_manager=tm) >>> rt = cn.root() # make a change >>> rt['c'] = 3 >>> st.sync_called False Now ensure that ``cn.afterCompletion() -> st.sync()`` 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 transaction manager: >>> cn.close() >>> tm = transaction.TransactionManager() >>> st.sync_called = False >>> dummy = tm.begin() # we're doing this _before_ opening a connection >>> cn = db.open(transaction_manager=tm) >>> rt = cn.root() # make a change >>> rt['d'] = 4 >>> st.sync_called False >>> tm.commit() >>> st.sync_called True >>> cn.close() >>> db.close()