=============
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 isn't called when a connectiin is opened, even though that
implicitly starts a new transaction:

    >>> st.sync_called
    False

Sync is only called when we explicitly start a new transaction:

    >>> _ = transaction.begin()

    >>> st.sync_called
    True
    >>> st.sync_called = False

BTW, calling ``sync()`` on a connectin starts a new transaction, which
caused ``sync()`` to be called on the storage:

    >>> cn.sync()
    >>> st.sync_called
    True
    >>> st.sync_called = False

``sync()`` is not called by the Connection's ``afterCompletion()``
hook after the commit completes, because we'll sunc when a new
transaction begins:

    >>> transaction.commit()
    >>> st.sync_called  # False before 3.4
    False

``sync()`` is also not called by the ``afterCompletion()`` hook after an abort.

    >>> st.sync_called = False
    >>> rt['b'] = 2
    >>> transaction.abort()
    >>> st.sync_called  # False before 3.4
    False

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

<<<<<<< HEAD
As a special case, if a synchronizer registers while a transaction is
in flight, then newTransaction and this the storage sync method is
called:
=======
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.

UPDATE: transaction 1.6.1 introduced a change that causes
newTransaction to be called when a synchronizer registeres with a
transaction manager with an active transaction.

    >>> 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
    True
    >>> st.sync_called = False

Now ensure that ``cn.afterCompletion() -> st.sync()`` gets called by commit
despite that the `Connection` registered after the transaction began:
>>>>>>> origin/master

    >>> tm = transaction.TransactionManager()
    >>> st.sync_called = False
    >>> _ = tm.begin()  # we're doing this _before_ opening a connection
    >>> cn = db.open(transaction_manager=tm)
<<<<<<< HEAD
=======
    >>> rt = cn.root()      # make a change
    >>> rt['d'] = 4
    >>> st.sync_called
    True
    >>> st.sync_called = False
    >>> tm.commit()
>>>>>>> origin/master
    >>> st.sync_called
    True

    >>> cn.close()
    >>> db.close()

