Commit 8c10da5a authored by Jim Fulton's avatar Jim Fulton

Bug fixed:

 Process exits or database closes could cause ZEO caches to have
 incorrect data due to a problem in the way invalidations were processed.
parent 8a150417
...@@ -25,9 +25,10 @@ Bugs Fixed ...@@ -25,9 +25,10 @@ Bugs Fixed
invalidation transaction ids matched the cached transaction ids invalidation transaction ids matched the cached transaction ids
should have been ignored. should have been ignored.
- Shutting down a process while committing a transaction could cause - Shutting down a process while committing a transaction or processing
ZEO client caches to have invalid data. This, in turn caused stale invalidations from the server could cause ZEO persistent client
data to remain in the cache until it was updated. caches to have invalid data. This, in turn caused stale data to
remain in the cache until it was updated.
- Conflict errors didn't invalidate ZEO cache entries. - Conflict errors didn't invalidate ZEO cache entries.
......
...@@ -1183,14 +1183,19 @@ class ClientStorage(object): ...@@ -1183,14 +1183,19 @@ class ClientStorage(object):
if self._cache is None: if self._cache is None:
return return
for oid, _ in self._seriald.iteritems():
self._cache.invalidate(oid, tid)
for oid, data in self._tbuf: for oid, data in self._tbuf:
self._cache.invalidate(oid, tid, False)
# If data is None, we just invalidate. # If data is None, we just invalidate.
if data is not None: if data is not None:
s = self._seriald[oid] s = self._seriald[oid]
if s != ResolvedSerial: if s != ResolvedSerial:
assert s == tid, (s, tid) assert s == tid, (s, tid)
self._cache.store(oid, s, None, data) self._cache.store(oid, s, None, data)
else:
# object deletion
self._cache.invalidate(oid, tid)
if self.fshelper is not None: if self.fshelper is not None:
blobs = self._tbuf.blobs blobs = self._tbuf.blobs
...@@ -1448,6 +1453,7 @@ class ClientStorage(object): ...@@ -1448,6 +1453,7 @@ class ClientStorage(object):
if oid == self._load_oid: if oid == self._load_oid:
self._load_status = 0 self._load_status = 0
self._cache.invalidate(oid, tid) self._cache.invalidate(oid, tid)
self._cache.setLastTid(tid)
if self._db is not None: if self._db is not None:
self._db.invalidate(tid, oids) self._db.invalidate(tid, oids)
......
...@@ -661,20 +661,13 @@ class ClientCache(object): ...@@ -661,20 +661,13 @@ class ClientCache(object):
# - oid object id # - oid object id
# - tid the id of the transaction that wrote a new revision of oid, # - tid the id of the transaction that wrote a new revision of oid,
# or None to forget all cached info about oid. # or None to forget all cached info about oid.
# - server_invalidation, a flag indicating whether the
# invalidation has come from the server. It's possible, due
# to threading issues, that when applying a local
# invalidation after a store, that later invalidations from
# the server may already have arrived.
@locked @locked
def invalidate(self, oid, tid, server_invalidation=True): def invalidate(self, oid, tid):
ofs = self.current.get(oid) ofs = self.current.get(oid)
if ofs is None: if ofs is None:
# 0x10 == invalidate (miss) # 0x10 == invalidate (miss)
self._trace(0x10, oid, tid) self._trace(0x10, oid, tid)
if server_invalidation:
self.setLastTid(tid)
return return
self.f.seek(ofs) self.f.seek(ofs)
...@@ -701,9 +694,6 @@ class ClientCache(object): ...@@ -701,9 +694,6 @@ class ClientCache(object):
# 0x1C = invalidate (hit, saving non-current) # 0x1C = invalidate (hit, saving non-current)
self._trace(0x1C, oid, tid) self._trace(0x1C, oid, tid)
if server_invalidation:
self.setLastTid(tid)
## ##
# Generates (oid, serial) oairs for all objects in the # Generates (oid, serial) oairs for all objects in the
# cache. This generator is used by cache verification. # cache. This generator is used by cache verification.
......
...@@ -83,9 +83,11 @@ class CacheTests(ZODB.tests.util.TestCase): ...@@ -83,9 +83,11 @@ class CacheTests(ZODB.tests.util.TestCase):
self.cache.setLastTid(n2) self.cache.setLastTid(n2)
self.assertEqual(self.cache.getLastTid(), n2) self.assertEqual(self.cache.getLastTid(), n2)
self.assertEqual(self.cache.getLastTid(), n2) self.assertEqual(self.cache.getLastTid(), n2)
self.cache.invalidate(n1, n3) self.cache.setLastTid(n3)
self.assertEqual(self.cache.getLastTid(), n3) self.assertEqual(self.cache.getLastTid(), n3)
# Check that setting tids out of order gives an error:
# the cache complains only when it's non-empty # the cache complains only when it's non-empty
self.cache.store(n1, n3, None, 'x') self.cache.store(n1, n3, None, 'x')
self.assertRaises(ValueError, self.cache.setLastTid, n2) self.assertRaises(ValueError, self.cache.setLastTid, n2)
......
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