=============
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()

