Commit 03a326be authored by Jim Fulton's avatar Jim Fulton

Updated to only call storage synchronizers on explicit transaction begin

And in special case where a connectin is opened while a transaction is
active (Zope2).
parent a07bcec8
...@@ -141,7 +141,7 @@ setup(name="ZODB", ...@@ -141,7 +141,7 @@ setup(name="ZODB",
'persistent >= 4.2.0', 'persistent >= 4.2.0',
'BTrees >= 4.2.0', 'BTrees >= 4.2.0',
'ZConfig', 'ZConfig',
'transaction >= 1.5.0', 'transaction >= 1.6.1',
'six', 'six',
'zc.lockfile', 'zc.lockfile',
'zope.interface', 'zope.interface',
......
...@@ -405,7 +405,7 @@ class Connection(ExportImport, object): ...@@ -405,7 +405,7 @@ class Connection(ExportImport, object):
def sync(self): def sync(self):
"""Manually update the view on the database.""" """Manually update the view on the database."""
self.transaction_manager.abort() self.transaction_manager.begin()
def getDebugInfo(self): def getDebugInfo(self):
"""Returns a tuple with different items for debugging the """Returns a tuple with different items for debugging the
...@@ -770,10 +770,16 @@ class Connection(ExportImport, object): ...@@ -770,10 +770,16 @@ class Connection(ExportImport, object):
# We don't do anything before a commit starts. # We don't do anything before a commit starts.
pass pass
def newTransaction(self, transaction=None): def newTransaction(self, transaction, sync=True):
self._readCurrent.clear() self._readCurrent.clear()
if self._mvcc_storage: if self._mvcc_storage:
# XXX It's not clear if we should make calling sync
# dependent on the sync flag here. RelStorage's sync semantics
# are a bit different, plus RelStorage doesn't actually
# work with this code. :/ This will probably be sorted when
# we do:
# https://groups.google.com/forum/#!topic/zodb/QJYmvF0eUUM
self._storage.sync(True) self._storage.sync(True)
# Poll the storage for invalidations. # Poll the storage for invalidations.
mvc_invalidated = self._storage.poll_invalidations() mvc_invalidated = self._storage.poll_invalidations()
...@@ -782,7 +788,8 @@ class Connection(ExportImport, object): ...@@ -782,7 +788,8 @@ class Connection(ExportImport, object):
# we need to flush the whole cache. # we need to flush the whole cache.
self._invalidatedCache = True self._invalidatedCache = True
else: else:
getattr(self._storage, 'sync', noop)() if sync:
getattr(self._storage, 'sync', noop)()
mvc_invalidated = None mvc_invalidated = None
if self.opened: if self.opened:
...@@ -831,7 +838,16 @@ class Connection(ExportImport, object): ...@@ -831,7 +838,16 @@ class Connection(ExportImport, object):
# Now is a good time to collect some garbage. # Now is a good time to collect some garbage.
self._cache.incrgc() self._cache.incrgc()
afterCompletion = newTransaction def afterCompletion(self, transaction):
# Note that we we call newTransaction here for 2 reasons:
# a) Applying invalidations early frees up resources
# early. This is especially useful if the connection isn't
# going to be used in a while.
# b) Non-hygienic applications might start new transactions by
# finalizing previous ones without calling begin. We pass
# False to avoid possiblyt expensive sync calls to not
# penalize well-behaved applications that call begin.
self.newTransaction(transaction, False)
# Transaction-manager synchronization -- ISynchronizer # Transaction-manager synchronization -- ISynchronizer
########################################################################## ##########################################################################
...@@ -977,15 +993,30 @@ class Connection(ExportImport, object): ...@@ -977,15 +993,30 @@ class Connection(ExportImport, object):
self.transaction_manager = transaction_manager self.transaction_manager = transaction_manager
transaction_manager.registerSynch(self)
self.opened = time.time() self.opened = time.time()
if self._reset_counter != global_reset_counter: if self._reset_counter != global_reset_counter:
# New code is in place. Start a new cache. # New code is in place. Start a new cache.
self._resetCache() self._resetCache()
self.newTransaction() # This newTransaction is to deal with some pathalogical cases:
#
# a) Someone opens a connection when a transaction isn't
# active and proceeeds without calling begin on a
# transaction manager. We initialize the transaction for
# the connection, but we don't do a storage sync, since
# this will be done if a well-nehaved application calls
# begin, and we don't want to penalize well-behaved
# transactions by syncing twice, as storage syncs might be
# expensive.
# b) Lots of tests assume that connection transaction
# information is set on open.
#
# Fortunately, this is a cheap operation. It doesn't really
# cost much, if anything.
self.newTransaction(None, False)
transaction_manager.registerSynch(self)
if self._cache is not None: if self._cache is not None:
self._cache.incrgc() # This is a good time to do some GC self._cache.incrgc() # This is a good time to do some GC
......
...@@ -25,27 +25,43 @@ Make a change locally: ...@@ -25,27 +25,43 @@ Make a change locally:
>>> rt = cn.root() >>> rt = cn.root()
>>> rt['a'] = 1 >>> rt['a'] = 1
Sync is called when a connection is open, as that starts a new transaction: 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 >>> st.sync_called
True True
>>> st.sync_called = False >>> st.sync_called = False
BTW, calling ``sync()`` on a connectin starts a new transaction, which
caused ``sync()`` to be called on the storage:
``sync()`` is called by the Connection's ``afterCompletion()`` hook after the >>> cn.sync()
commit completes. >>> 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() >>> transaction.commit()
>>> st.sync_called # False before 3.4 >>> st.sync_called # False before 3.4
True False
``sync()`` is also called by the ``afterCompletion()`` hook after an abort. ``sync()`` is also not called by the ``afterCompletion()`` hook after an abort.
>>> st.sync_called = False >>> st.sync_called = False
>>> rt['b'] = 2 >>> rt['b'] = 2
>>> transaction.abort() >>> transaction.abort()
>>> st.sync_called # False before 3.4 >>> st.sync_called # False before 3.4
True False
And ``sync()`` is called whenever we explicitly start a new transaction, via And ``sync()`` is called whenever we explicitly start a new transaction, via
the ``newTransaction()`` hook. the ``newTransaction()`` hook.
...@@ -63,45 +79,14 @@ traceback then ;-) ...@@ -63,45 +79,14 @@ traceback then ;-)
>>> cn.close() >>> cn.close()
One more, very obscure. It was the case that if the first action a new As a special case, if a synchronizer registers while a transaction is
threaded transaction manager saw was a ``begin()`` call, then synchronizers in flight, then newTransaction and this the storage sync method is
registered after that in the same transaction weren't communicated to the called:
`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
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:
>>> tm.commit()
>>> st.sync_called
True
And try the same thing with a non-threaded transaction manager:
>>> cn.close()
>>> tm = transaction.TransactionManager() >>> tm = transaction.TransactionManager()
>>> st.sync_called = False >>> st.sync_called = False
>>> dummy = tm.begin() # we're doing this _before_ opening a connection >>> _ = tm.begin() # we're doing this _before_ opening a connection
>>> cn = db.open(transaction_manager=tm) >>> cn = db.open(transaction_manager=tm)
>>> rt = cn.root() # make a change
>>> rt['d'] = 4
>>> st.sync_called
True
>>> st.sync_called = False
>>> tm.commit()
>>> st.sync_called >>> st.sync_called
True True
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment