Commit 5e6fd05f authored by Jim Fulton's avatar Jim Fulton

Make the cache thread safe again. :/

There are some cases where we want to touch the cache outside of the
I/O thread. Including:

- ClientStorage wants to to invalidata cache entries if it gets a
  conflict error in voting.

- loadBefore can probably be optimized by checking the cache in the
  client thread. (This will be safe for loadBefore, because the intent
  in practice is never to load current data.
parent 062cbecc
......@@ -33,7 +33,7 @@ import time
import ZODB.fsIndex
import zc.lockfile
from ZODB.utils import p64, u64, z64
from ZODB.utils import p64, u64, z64, RLock
import six
from ._compat import PYPY
......@@ -182,6 +182,8 @@ class ClientCache(object):
# currentofs.
self.currentofs = ZEC_HEADER_SIZE
self._lock = RLock()
# self.f is the open file object.
# When we're not reusing an existing file, self.f is left None
# here -- the scan() method must be called then to open the file
......@@ -239,6 +241,7 @@ class ClientCache(object):
return self
def clear(self):
with self._lock:
self.f.seek(ZEC_HEADER_SIZE)
self.f.truncate()
self._initfile(ZEC_HEADER_SIZE)
......@@ -451,6 +454,7 @@ class ClientCache(object):
# new tid must be strictly greater than our current idea of the most
# recent tid.
def setLastTid(self, tid):
with self._lock:
if (not tid) or (tid == z64):
return
if (tid <= self.tid) and self._len:
......@@ -470,6 +474,7 @@ class ClientCache(object):
# @return a transaction id
# @defreturn string, or 8 nulls if no transaction is yet known
def getLastTid(self):
with self._lock:
return self.tid
##
......@@ -479,6 +484,7 @@ class ClientCache(object):
# in the cache
# @defreturn 3-tuple: (string, string, string)
def load(self, oid, before_tid=None):
with self._lock:
ofs = self.current.get(oid)
if ofs is None:
self._trace(0x20, oid)
......@@ -497,7 +503,8 @@ class ClientCache(object):
return None
data = read(ldata)
assert len(data) == ldata, (ofs, self.f.tell(), oid, len(data), ldata)
assert len(data) == ldata, (
ofs, self.f.tell(), oid, len(data), ldata)
# WARNING: The following assert changes the file position.
# We must not depend on this below or we'll fail in optimized mode.
......@@ -533,6 +540,7 @@ class ClientCache(object):
# @return data record, serial number, start tid, and end tid
# @defreturn 4-tuple: (string, string, string, string)
def loadBefore(self, oid, before_tid):
with self._lock:
noncurrent_for_oid = self.noncurrent.get(u64(oid))
if noncurrent_for_oid is None:
result = self.load(oid, before_tid)
......@@ -560,7 +568,8 @@ class ClientCache(object):
size, saved_oid, saved_tid, end_tid, lver, ldata = unpack(
">I8s8s8sHI", read(34))
assert saved_oid == oid, (ofs, self.f.tell(), oid, saved_oid)
assert saved_tid == p64(tid), (ofs, self.f.tell(), oid, saved_tid, tid)
assert saved_tid == p64(tid), (
ofs, self.f.tell(), oid, saved_tid, tid)
assert end_tid != z64, (ofs, self.f.tell(), oid)
assert lver == 0, "Versions aren't supported"
data = read(ldata)
......@@ -591,6 +600,7 @@ class ClientCache(object):
# current.
# @param data the actual data
def store(self, oid, start_tid, end_tid, data):
with self._lock:
seek = self.f.seek
if end_tid is None:
ofs = self.current.get(oid)
......@@ -601,14 +611,16 @@ class ClientCache(object):
assert status == b'a', (ofs, self.f.tell(), oid)
size, saved_oid, saved_tid, end_tid = unpack(
">I8s8s8s", read(28))
assert saved_oid == oid, (ofs, self.f.tell(), oid, saved_oid)
assert saved_oid == oid, (
ofs, self.f.tell(), oid, saved_oid)
assert end_tid == z64, (ofs, self.f.tell(), oid)
if saved_tid == start_tid:
return
raise ValueError("already have current data for oid")
else:
noncurrent_for_oid = self.noncurrent.get(u64(oid))
if noncurrent_for_oid and (u64(start_tid) in noncurrent_for_oid):
if noncurrent_for_oid and (
u64(start_tid) in noncurrent_for_oid):
return
size = allocated_record_overhead + len(data)
......@@ -696,6 +708,7 @@ class ClientCache(object):
# - tid the id of the transaction that wrote a new revision of oid,
# or None to forget all cached info about oid.
def invalidate(self, oid, tid):
with self._lock:
ofs = self.current.get(oid)
if ofs is None:
# 0x10 == invalidate (miss)
......@@ -718,7 +731,8 @@ class ClientCache(object):
self._len -= 1
else:
if tid == saved_tid:
logger.warning("Ignoring invalidation with same tid as current")
logger.warning(
"Ignoring invalidation with same tid as current")
return
self.f.seek(ofs+21)
self.f.write(tid)
......
......@@ -24,8 +24,7 @@ class StaleCache(object):
class IClientCache(zope.interface.Interface):
"""Client cache interface.
Note that caches need not be thread safe, fpr the most part,
except for getLastTid, which may be called from multiple threads.
Note that caches need to be thread safe.
"""
def close():
......
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