From 98c88f3db87833dec12aa6617d9b0ae19dae5913 Mon Sep 17 00:00:00 2001 From: Tim Peters <tim.one@comcast.net> Date: Wed, 31 Aug 2005 21:27:28 +0000 Subject: [PATCH] Merge rev 38051 from 3.4 branch. Code and new test to ensure that making a savepoint triggers cache gc. --- NEWS.txt | 31 ++++++++---- src/ZODB/Connection.py | 8 +++- src/ZODB/tests/testConnectionSavepoint.py | 57 ++++++++++++++++++++++- 3 files changed, 86 insertions(+), 10 deletions(-) diff --git a/NEWS.txt b/NEWS.txt index 30bce689..6825dcf2 100644 --- a/NEWS.txt +++ b/NEWS.txt @@ -16,6 +16,13 @@ Zope3 development). These are the dates of the internal releases: Savepoints ---------- +- (3.5.0) As for deprecated subtransaction commits, the intent was + that making a savepoint would invoke incremental garbage collection on + Connection memory caches, to try to reduce the number of objects in + cache to the configured cache size. Due to an oversight, this didn't + happen, and stopped happening for subtransaction commits too. Making a + savepoint (or doing a subtransaction commit) does invoke cache gc now. + - (3.5a3) When a savepoint is made, the states of objects modified so far are saved to a temporary storage (an instance of class ``TmpStore``, although that's an internal implementation detail). That storage needs @@ -52,11 +59,15 @@ Subtransactions are deprecated to:: - transaction.savepoint() + transaction.savepoint(True) + + That is, make a savepoint, and forget it. As shown, it's best to pass + ``True`` for the optional ``optimistic`` argument in this case: because + there's no possibility of asking for a rollback later, there's no need + to insist that all data managers support rollback. - That is, make a savepoint, and forget it. In rarer cases, a - subtransaction commit is followed later by a subtransaction abort. In - that case, change the initial:: + In rarer cases, a subtransaction commit is followed later by a + subtransaction abort. In that case, change the initial:: transaction.commit(1) @@ -229,11 +240,15 @@ Subtransactions to:: - transaction.savepoint() + transaction.savepoint(True) + + That is, make a savepoint, and forget it. As shown, it's best to pass + ``True`` for the optional ``optimistic`` argument in this case: because + there's no possibility of asking for a rollback later, there's no need + to insist that all data managers support rollback. - That is, make a savepoint, and forget it. In rarer cases, a - subtransaction commit is followed later by a subtransaction abort. In - that case, change the initial:: + In rarer cases, a subtransaction commit is followed later by a + subtransaction abort. In that case, change the initial:: transaction.commit(1) diff --git a/src/ZODB/Connection.py b/src/ZODB/Connection.py index 29e5de24..38a100e1 100644 --- a/src/ZODB/Connection.py +++ b/src/ZODB/Connection.py @@ -1063,7 +1063,13 @@ class Connection(ExportImport, object): self._registered_objects = [] state = self._storage.position, self._storage.index.copy() - return Savepoint(self, state) + result = Savepoint(self, state) + # While the interface doesn't guarantee this, savepoints are + # sometimes used just to "break up" very long transactions, and as + # a pragmatic matter this is a good time to reduce the cache + # memory burden. + self.cacheGC() + return result def _rollback(self, state): self._abort() diff --git a/src/ZODB/tests/testConnectionSavepoint.py b/src/ZODB/tests/testConnectionSavepoint.py index 2592721d..7366238c 100644 --- a/src/ZODB/tests/testConnectionSavepoint.py +++ b/src/ZODB/tests/testConnectionSavepoint.py @@ -85,7 +85,62 @@ def testCantCloseConnectionWithActiveSavepoint(): >>> db.close() """ - + +def testSavepointDoesCacheGC(): + """\ +Although the interface doesn't guarantee this internal detail, making a +savepoint should do incremental gc on connection memory caches. Indeed, +one traditional use for savepoints (started by the older, related +"subtransaction commit" idea) is simply to free memory space midstream +during a long transaction. Before ZODB 3.4.2, making a savepoint failed +to trigger cache gc, and this test verifies that it now does. + + >>> import ZODB + >>> from ZODB.tests.MinPO import MinPO + >>> from ZODB.MappingStorage import MappingStorage + >>> import transaction + >>> CACHESIZE = 5 # something tiny + >>> LOOPCOUNT = CACHESIZE * 10 + >>> st = MappingStorage("Test") + >>> db = ZODB.DB(st, cache_size=CACHESIZE) + >>> cn = db.open() + >>> rt = cn.root() + +Now attach substantially more than CACHESIZE persistent objects to the root: + + >>> for i in range(LOOPCOUNT): + ... rt[i] = MinPO(i) + >>> transaction.commit() + +Now modify all of them; the cache should contain LOOPCOUNT MinPO objects +then, + 1 for the root object: + + >>> for i in range(LOOPCOUNT): + ... obj = rt[i] + ... obj.value = -i + >>> len(cn._cache) == LOOPCOUNT + 1 + True + +Making a savepoint at this time used to leave the cache holding the same +number of objects. Make sure the cache shrinks now instead. + + >>> dummy = transaction.savepoint() + >>> len(cn._cache) <= CACHESIZE + 1 + True + +Verify all the values are as expected: + + >>> failures = [] + >>> for i in range(LOOPCOUNT): + ... obj = rt[i] + ... if obj.value != -i: + ... failures.append(obj) + >>> failures + [] + + >>> transaction.abort() + >>> db.close() +""" def test_suite(): return unittest.TestSuite(( -- 2.30.9