Skip to content

Commit 32c9a7a

Browse files
committed
pack: Clear all non-current entries after pack
Else those non-current entries can be used to serve a loadBefore request with data, while, after pack that loadBefore request must return "data deleted" if requested object has current revision >= packtime. Fixes checkPackVSConnectionGet from ZODB from zopefoundation/ZODB#322 which, without this patch fails as e.g. Failure in test checkPackVSConnectionGet (ZEO.tests.testZEO.MappingStorageTests) Traceback (most recent call last): File "/usr/lib/python2.7/unittest/case.py", line 329, in run testMethod() File "/home/kirr/src/wendelin/z/ZODB/src/ZODB/tests/PackableStorage.py", line 636, in checkPackVSConnectionGet raises(ReadConflictError, conn1.get, oid) File "/usr/lib/python2.7/unittest/case.py", line 473, in assertRaises callableObj(*args, **kwargs) File "/usr/lib/python2.7/unittest/case.py", line 116, in __exit__ "{0} not raised".format(exc_name)) AssertionError: ReadConflictError not raised
1 parent 3de995f commit 32c9a7a

File tree

3 files changed

+53
-1
lines changed

3 files changed

+53
-1
lines changed

src/ZEO/ClientStorage.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -559,7 +559,16 @@ def pack(self, t=None, referencesf=None, wait=1, days=0):
559559
if t is None:
560560
t = time.time()
561561
t = t - (days * 86400)
562-
return self._call('pack', t, wait)
562+
ret = self._call('pack', t, wait)
563+
# remove all non-current entries from the cache.
564+
# This way we make sure that loadBefore with before < packtime, won't
565+
# return data from the cache, instead of returning "no data" if requested object
566+
# has current revision >= packtime.
567+
# By clearing all noncurrent entries we might remove more data from the
568+
# cache than is strictly necessary, but since access to noncurrent data
569+
# is seldom, that should not cause problems in practice.
570+
self._cache.clearAllNonCurrent()
571+
return ret
563572

564573
def store(self, oid, serial, data, version, txn):
565574
"""Storage API: store data for an object."""

src/ZEO/cache.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,21 @@ def clear(self):
246246
self.f.truncate()
247247
self._initfile(ZEC_HEADER_SIZE)
248248

249+
# clearAllNonCurrent removes all non-current entries from the cache.
250+
def clearAllNonCurrent(self):
251+
with self._lock:
252+
f = self.f
253+
for oid, tidofs in self.noncurrent.items():
254+
for (tid, ofs) in tidofs.items():
255+
f.seek(ofs)
256+
status = f.read(1)
257+
assert status == b'a', (ofs, f.tell(), oid, tid)
258+
f.seek(ofs)
259+
f.write(b'f')
260+
self._len -= 1
261+
262+
self.noncurrent.clear()
263+
249264
##
250265
# Scan the current contents of the cache file, calling `install`
251266
# for each object found in the cache. This method should only

src/ZEO/tests/test_cache.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,34 @@ def test_clear_zeo_cache(self):
253253
self.assertEqual(cache.load(n3), None)
254254
self.assertEqual(cache.loadBefore(n3, n2), None)
255255

256+
def testClearAllNonCurrent(self):
257+
cache = self.cache
258+
cache.store(p64(1), n1, n2, b'1@1')
259+
cache.store(p64(1), n2, n3, b'1@2')
260+
cache.store(p64(1), n3, None, b'1')
261+
cache.store(p64(2), n2, n3, b'2@2')
262+
cache.store(p64(2), n3, None, b'2')
263+
264+
eq = self.assertEqual
265+
eq(len(cache), 5)
266+
eq(cache.load(p64(1)), (b'1', n3))
267+
eq(cache.loadBefore(p64(1), n3), (b'1@2', n2, n3))
268+
eq(cache.loadBefore(p64(1), n2), (b'1@1', n1, n2))
269+
eq(cache.loadBefore(p64(1), n1), None)
270+
eq(cache.load(p64(2)), (b'2', n3))
271+
eq(cache.loadBefore(p64(2), n3), (b'2@2', n2, n3))
272+
eq(cache.loadBefore(p64(2), n2), None)
273+
274+
cache.clearAllNonCurrent()
275+
eq(len(cache), 2)
276+
eq(cache.load(p64(1)), (b'1', n3))
277+
eq(cache.loadBefore(p64(1), n3), None)
278+
eq(cache.loadBefore(p64(1), n2), None)
279+
eq(cache.loadBefore(p64(1), n1), None)
280+
eq(cache.load(p64(2)), (b'2', n3))
281+
eq(cache.loadBefore(p64(2), n3), None)
282+
eq(cache.loadBefore(p64(2), n2), None)
283+
256284
def testChangingCacheSize(self):
257285
# start with a small cache
258286
data = b'x'

0 commit comments

Comments
 (0)