Commit 24c61eae authored by Jim Fulton's avatar Jim Fulton

- Call new afterCompletion API on storages to allow them to free

  resources after transaction complete.  See:
  https://github.com/zodb/relstorage/issues/147

- Take advantage of the new transaction-manager explicit mode to avoid
  starting transactions unnecessarily when transactions end.
parent 739761cf
...@@ -2,14 +2,23 @@ ...@@ -2,14 +2,23 @@
Change History Change History
================ ================
5.1.2 (unreleased) 5.2.0 (unreleased)
================== ==================
- Call new afterCompletion API on storages to allow them to free
resources after transaction complete. See:
https://github.com/zodb/relstorage/issues/147
- Take advantage of the new transaction-manager explicit mode to avoid
starting transactions unnecessarily when transactions end.
- ``Connection.new_oid`` delegates to its storage, not the DB. This is - ``Connection.new_oid`` delegates to its storage, not the DB. This is
helpful for improving concurrency in MVCC storages like RelStorage. helpful for improving concurrency in MVCC storages like RelStorage.
See `issue 139 <https://github.com/zopefoundation/ZODB/issues/139`_. See `issue 139 <https://github.com/zopefoundation/ZODB/issues/139`_.
- ``persistent`` is no longer required at setup time. - ``persistent`` is no longer required at setup time.
See `issue 119 <https://github.com/zopefoundation/ZODB/issues/119>`_. See `issue 119 <https://github.com/zopefoundation/ZODB/issues/119>`_.
- ``Connection.close`` and ``Connection.open`` no longer race on - ``Connection.close`` and ``Connection.open`` no longer race on
``self.transaction_manager``, which could lead to ``self.transaction_manager``, which could lead to
``AttributeError``. This was a bug introduced in 5.0.1. See `issue ``AttributeError``. This was a bug introduced in 5.0.1. See `issue
......
...@@ -312,6 +312,9 @@ class Connection(ExportImport, object): ...@@ -312,6 +312,9 @@ class Connection(ExportImport, object):
# Drop transaction manager to release resources and help prevent errors # Drop transaction manager to release resources and help prevent errors
self.transaction_manager = None self.transaction_manager = None
if hasattr(self._storage, 'afterCompletion'):
self._storage.afterCompletion()
if primary: if primary:
for connection in self.connections.values(): for connection in self.connections.values():
if connection is not self: if connection is not self:
...@@ -406,7 +409,6 @@ class Connection(ExportImport, object): ...@@ -406,7 +409,6 @@ class Connection(ExportImport, object):
def abort(self, transaction): def abort(self, transaction):
"""Abort a transaction and forget all changes.""" """Abort a transaction and forget all changes."""
# The order is important here. We want to abort registered # The order is important here. We want to abort registered
# objects before we process the cache. Otherwise, we may un-add # objects before we process the cache. Otherwise, we may un-add
# objects added in savepoints. If they've been modified since # objects added in savepoints. If they've been modified since
...@@ -480,7 +482,6 @@ class Connection(ExportImport, object): ...@@ -480,7 +482,6 @@ class Connection(ExportImport, object):
def commit(self, transaction): def commit(self, transaction):
"""Commit changes to an object""" """Commit changes to an object"""
transaction = transaction.data(self) transaction = transaction.data(self)
if self._savepoint_storage is not None: if self._savepoint_storage is not None:
...@@ -757,7 +758,11 @@ class Connection(ExportImport, object): ...@@ -757,7 +758,11 @@ class Connection(ExportImport, object):
# finalizing previous ones without calling begin. We pass # finalizing previous ones without calling begin. We pass
# False to avoid possiblyt expensive sync calls to not # False to avoid possiblyt expensive sync calls to not
# penalize well-behaved applications that call begin. # penalize well-behaved applications that call begin.
self.newTransaction(transaction, False) if hasattr(self._storage, 'afterCompletion'):
self._storage.afterCompletion()
if not self.explicit_transactions:
self.newTransaction(transaction, False)
# Transaction-manager synchronization -- ISynchronizer # Transaction-manager synchronization -- ISynchronizer
########################################################################## ##########################################################################
...@@ -772,8 +777,9 @@ class Connection(ExportImport, object): ...@@ -772,8 +777,9 @@ class Connection(ExportImport, object):
return self._reader.getState(p) return self._reader.getState(p)
def setstate(self, obj): def setstate(self, obj):
"""Turns the ghost 'obj' into a real object by loading its state from """Load the state for an (ghost) object
the database.""" """
oid = obj._p_oid oid = obj._p_oid
if self.opened is None: if self.opened is None:
...@@ -887,6 +893,9 @@ class Connection(ExportImport, object): ...@@ -887,6 +893,9 @@ class Connection(ExportImport, object):
self.transaction_manager = transaction_manager self.transaction_manager = transaction_manager
self.explicit_transactions = getattr(transaction_manager,
'explicit', False)
self.opened = time.time() self.opened = time.time()
if self._reset_counter != global_reset_counter: if self._reset_counter != global_reset_counter:
......
...@@ -1243,6 +1243,16 @@ class IMVCCPrefetchStorage(IMVCCStorage): ...@@ -1243,6 +1243,16 @@ class IMVCCPrefetchStorage(IMVCCStorage):
more than once. more than once.
""" """
class IMVCCAfterCompletionStorage(IMVCCStorage):
def afterCompletion():
"""Notify a storage that a transaction has ended.
The storage may choose to use this opportunity to release resources.
See ``transaction.interfaces.ISynchronizer.afterCompletion``.
"""
class IStorageCurrentRecordIteration(IStorage): class IStorageCurrentRecordIteration(IStorage):
def record_iternext(next=None): def record_iternext(next=None):
......
...@@ -1060,6 +1060,7 @@ def doctest_lp485456_setattr_in_setstate_doesnt_cause_multiple_stores(): ...@@ -1060,6 +1060,7 @@ def doctest_lp485456_setattr_in_setstate_doesnt_cause_multiple_stores():
>>> conn.close() >>> conn.close()
""" """
class _PlayPersistent(Persistent): class _PlayPersistent(Persistent):
def setValueWithSize(self, size=0): self.value = size*' ' def setValueWithSize(self, size=0): self.value = size*' '
__init__ = setValueWithSize __init__ = setValueWithSize
...@@ -1301,14 +1302,55 @@ class StubStorage: ...@@ -1301,14 +1302,55 @@ class StubStorage:
return z64 return z64
class TestConnectionInterface(unittest.TestCase): class TestConnection(unittest.TestCase):
def test_connection_interface(self): def test_connection_interface(self):
from ZODB.interfaces import IConnection from ZODB.interfaces import IConnection
db = databaseFromString("<zodb>\n<mappingstorage/>\n</zodb>") db = databaseFromString("<zodb>\n<mappingstorage/>\n</zodb>")
cn = db.open() cn = db.open()
verifyObject(IConnection, cn) verifyObject(IConnection, cn)
db.close()
def test_storage_afterCompletionCalled(self):
db = ZODB.DB(None)
conn = db.open()
data = []
conn._storage.afterCompletion = lambda : data.append(None)
conn.transaction_manager.commit()
self.assertEqual(len(data), 1)
conn.close()
self.assertEqual(len(data), 2)
db.close()
def test_explicit_transactions_no_newTransactuon_on_afterCompletion(self):
db = ZODB.DB(None)
# We don't want to depend on latest transaction package, so
# just set attr for test:
tm = transaction.TransactionManager()
tm.explicit = True
conn = db.open(tm)
syncs = []
conn._storage.sync = syncs.append
conn.transaction_manager.begin()
self.assertEqual(len(syncs), 1)
conn.transaction_manager.commit()
self.assertEqual(len(syncs), 1)
conn.transaction_manager.begin()
self.assertEqual(len(syncs), 2)
conn.transaction_manager.abort()
self.assertEqual(len(syncs), 2)
db.close()
# For reference, in non-explicit mode:
db = ZODB.DB(None)
conn = db.open()
conn._storage.sync = syncs.append
conn.transaction_manager.begin()
self.assertEqual(len(syncs), 3)
conn.transaction_manager.abort()
self.assertEqual(len(syncs), 4)
class StubDatabase: class StubDatabase:
...@@ -1330,6 +1372,6 @@ def test_suite(): ...@@ -1330,6 +1372,6 @@ def test_suite():
s = unittest.makeSuite(ConnectionDotAdd) s = unittest.makeSuite(ConnectionDotAdd)
s.addTest(unittest.makeSuite(SetstateErrorLoggingTests)) s.addTest(unittest.makeSuite(SetstateErrorLoggingTests))
s.addTest(doctest.DocTestSuite(checker=checker)) s.addTest(doctest.DocTestSuite(checker=checker))
s.addTest(unittest.makeSuite(TestConnectionInterface)) s.addTest(unittest.makeSuite(TestConnection))
s.addTest(unittest.makeSuite(EstimatedSizeTests)) s.addTest(unittest.makeSuite(EstimatedSizeTests))
return s return s
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