Merge pull request #538 from matrix-org/erikj/fix_lru_cache
Fix LruCache. Make TreeCache track its own size.
This commit is contained in:
commit
02a9c3be6c
|
@ -37,7 +37,7 @@ class LruCache(object):
|
||||||
"""
|
"""
|
||||||
def __init__(self, max_size, keylen=1, cache_type=dict):
|
def __init__(self, max_size, keylen=1, cache_type=dict):
|
||||||
cache = cache_type()
|
cache = cache_type()
|
||||||
self.size = 0
|
self.cache = cache # Used for introspection.
|
||||||
list_root = []
|
list_root = []
|
||||||
list_root[:] = [list_root, list_root, None, None]
|
list_root[:] = [list_root, list_root, None, None]
|
||||||
|
|
||||||
|
@ -60,7 +60,6 @@ class LruCache(object):
|
||||||
prev_node[NEXT] = node
|
prev_node[NEXT] = node
|
||||||
next_node[PREV] = node
|
next_node[PREV] = node
|
||||||
cache[key] = node
|
cache[key] = node
|
||||||
self.size += 1
|
|
||||||
|
|
||||||
def move_node_to_front(node):
|
def move_node_to_front(node):
|
||||||
prev_node = node[PREV]
|
prev_node = node[PREV]
|
||||||
|
@ -79,7 +78,6 @@ class LruCache(object):
|
||||||
next_node = node[NEXT]
|
next_node = node[NEXT]
|
||||||
prev_node[NEXT] = next_node
|
prev_node[NEXT] = next_node
|
||||||
next_node[PREV] = prev_node
|
next_node[PREV] = prev_node
|
||||||
self.size -= 1
|
|
||||||
|
|
||||||
@synchronized
|
@synchronized
|
||||||
def cache_get(key, default=None):
|
def cache_get(key, default=None):
|
||||||
|
@ -98,7 +96,7 @@ class LruCache(object):
|
||||||
node[VALUE] = value
|
node[VALUE] = value
|
||||||
else:
|
else:
|
||||||
add_node(key, value)
|
add_node(key, value)
|
||||||
if self.size > max_size:
|
if len(cache) > max_size:
|
||||||
todelete = list_root[PREV]
|
todelete = list_root[PREV]
|
||||||
delete_node(todelete)
|
delete_node(todelete)
|
||||||
cache.pop(todelete[KEY], None)
|
cache.pop(todelete[KEY], None)
|
||||||
|
@ -110,7 +108,7 @@ class LruCache(object):
|
||||||
return node[VALUE]
|
return node[VALUE]
|
||||||
else:
|
else:
|
||||||
add_node(key, value)
|
add_node(key, value)
|
||||||
if self.size > max_size:
|
if len(cache) > max_size:
|
||||||
todelete = list_root[PREV]
|
todelete = list_root[PREV]
|
||||||
delete_node(todelete)
|
delete_node(todelete)
|
||||||
cache.pop(todelete[KEY], None)
|
cache.pop(todelete[KEY], None)
|
||||||
|
@ -145,7 +143,7 @@ class LruCache(object):
|
||||||
|
|
||||||
@synchronized
|
@synchronized
|
||||||
def cache_len():
|
def cache_len():
|
||||||
return self.size
|
return len(cache)
|
||||||
|
|
||||||
@synchronized
|
@synchronized
|
||||||
def cache_contains(key):
|
def cache_contains(key):
|
||||||
|
|
|
@ -8,6 +8,7 @@ class TreeCache(object):
|
||||||
Keys must be tuples.
|
Keys must be tuples.
|
||||||
"""
|
"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
self.size = 0
|
||||||
self.root = {}
|
self.root = {}
|
||||||
|
|
||||||
def __setitem__(self, key, value):
|
def __setitem__(self, key, value):
|
||||||
|
@ -20,7 +21,8 @@ class TreeCache(object):
|
||||||
node = self.root
|
node = self.root
|
||||||
for k in key[:-1]:
|
for k in key[:-1]:
|
||||||
node = node.setdefault(k, {})
|
node = node.setdefault(k, {})
|
||||||
node[key[-1]] = value
|
node[key[-1]] = _Entry(value)
|
||||||
|
self.size += 1
|
||||||
|
|
||||||
def get(self, key, default=None):
|
def get(self, key, default=None):
|
||||||
node = self.root
|
node = self.root
|
||||||
|
@ -28,9 +30,10 @@ class TreeCache(object):
|
||||||
node = node.get(k, None)
|
node = node.get(k, None)
|
||||||
if node is None:
|
if node is None:
|
||||||
return default
|
return default
|
||||||
return node.get(key[-1], default)
|
return node.get(key[-1], _Entry(default)).value
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
|
self.size = 0
|
||||||
self.root = {}
|
self.root = {}
|
||||||
|
|
||||||
def pop(self, key, default=None):
|
def pop(self, key, default=None):
|
||||||
|
@ -57,4 +60,33 @@ class TreeCache(object):
|
||||||
break
|
break
|
||||||
node_and_keys[i+1][0].pop(k)
|
node_and_keys[i+1][0].pop(k)
|
||||||
|
|
||||||
|
popped, cnt = _strip_and_count_entires(popped)
|
||||||
|
self.size -= cnt
|
||||||
return popped
|
return popped
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return self.size
|
||||||
|
|
||||||
|
|
||||||
|
class _Entry(object):
|
||||||
|
__slots__ = ["value"]
|
||||||
|
|
||||||
|
def __init__(self, value):
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
|
||||||
|
def _strip_and_count_entires(d):
|
||||||
|
"""Takes an _Entry or dict with leaves of _Entry's, and either returns the
|
||||||
|
value or a dictionary with _Entry's replaced by their values.
|
||||||
|
|
||||||
|
Also returns the count of _Entry's
|
||||||
|
"""
|
||||||
|
if isinstance(d, dict):
|
||||||
|
cnt = 0
|
||||||
|
for key, value in d.items():
|
||||||
|
v, n = _strip_and_count_entires(value)
|
||||||
|
d[key] = v
|
||||||
|
cnt += n
|
||||||
|
return d, cnt
|
||||||
|
else:
|
||||||
|
return d.value, 1
|
||||||
|
|
|
@ -19,6 +19,7 @@ from .. import unittest
|
||||||
from synapse.util.caches.lrucache import LruCache
|
from synapse.util.caches.lrucache import LruCache
|
||||||
from synapse.util.caches.treecache import TreeCache
|
from synapse.util.caches.treecache import TreeCache
|
||||||
|
|
||||||
|
|
||||||
class LruCacheTestCase(unittest.TestCase):
|
class LruCacheTestCase(unittest.TestCase):
|
||||||
|
|
||||||
def test_get_set(self):
|
def test_get_set(self):
|
||||||
|
@ -72,3 +73,9 @@ class LruCacheTestCase(unittest.TestCase):
|
||||||
self.assertEquals(cache.get(("vehicles", "car")), "vroom")
|
self.assertEquals(cache.get(("vehicles", "car")), "vroom")
|
||||||
self.assertEquals(cache.get(("vehicles", "train")), "chuff")
|
self.assertEquals(cache.get(("vehicles", "train")), "chuff")
|
||||||
# Man from del_multi say "Yes".
|
# Man from del_multi say "Yes".
|
||||||
|
|
||||||
|
def test_clear(self):
|
||||||
|
cache = LruCache(1)
|
||||||
|
cache["key"] = 1
|
||||||
|
cache.clear()
|
||||||
|
self.assertEquals(len(cache), 0)
|
||||||
|
|
|
@ -25,6 +25,7 @@ class TreeCacheTestCase(unittest.TestCase):
|
||||||
cache[("b",)] = "B"
|
cache[("b",)] = "B"
|
||||||
self.assertEquals(cache.get(("a",)), "A")
|
self.assertEquals(cache.get(("a",)), "A")
|
||||||
self.assertEquals(cache.get(("b",)), "B")
|
self.assertEquals(cache.get(("b",)), "B")
|
||||||
|
self.assertEquals(len(cache), 2)
|
||||||
|
|
||||||
def test_pop_onelevel(self):
|
def test_pop_onelevel(self):
|
||||||
cache = TreeCache()
|
cache = TreeCache()
|
||||||
|
@ -33,6 +34,7 @@ class TreeCacheTestCase(unittest.TestCase):
|
||||||
self.assertEquals(cache.pop(("a",)), "A")
|
self.assertEquals(cache.pop(("a",)), "A")
|
||||||
self.assertEquals(cache.pop(("a",)), None)
|
self.assertEquals(cache.pop(("a",)), None)
|
||||||
self.assertEquals(cache.get(("b",)), "B")
|
self.assertEquals(cache.get(("b",)), "B")
|
||||||
|
self.assertEquals(len(cache), 1)
|
||||||
|
|
||||||
def test_get_set_twolevel(self):
|
def test_get_set_twolevel(self):
|
||||||
cache = TreeCache()
|
cache = TreeCache()
|
||||||
|
@ -42,6 +44,7 @@ class TreeCacheTestCase(unittest.TestCase):
|
||||||
self.assertEquals(cache.get(("a", "a")), "AA")
|
self.assertEquals(cache.get(("a", "a")), "AA")
|
||||||
self.assertEquals(cache.get(("a", "b")), "AB")
|
self.assertEquals(cache.get(("a", "b")), "AB")
|
||||||
self.assertEquals(cache.get(("b", "a")), "BA")
|
self.assertEquals(cache.get(("b", "a")), "BA")
|
||||||
|
self.assertEquals(len(cache), 3)
|
||||||
|
|
||||||
def test_pop_twolevel(self):
|
def test_pop_twolevel(self):
|
||||||
cache = TreeCache()
|
cache = TreeCache()
|
||||||
|
@ -53,6 +56,7 @@ class TreeCacheTestCase(unittest.TestCase):
|
||||||
self.assertEquals(cache.get(("a", "b")), "AB")
|
self.assertEquals(cache.get(("a", "b")), "AB")
|
||||||
self.assertEquals(cache.pop(("b", "a")), "BA")
|
self.assertEquals(cache.pop(("b", "a")), "BA")
|
||||||
self.assertEquals(cache.pop(("b", "a")), None)
|
self.assertEquals(cache.pop(("b", "a")), None)
|
||||||
|
self.assertEquals(len(cache), 1)
|
||||||
|
|
||||||
def test_pop_mixedlevel(self):
|
def test_pop_mixedlevel(self):
|
||||||
cache = TreeCache()
|
cache = TreeCache()
|
||||||
|
@ -64,3 +68,11 @@ class TreeCacheTestCase(unittest.TestCase):
|
||||||
self.assertEquals(cache.get(("a", "a")), None)
|
self.assertEquals(cache.get(("a", "a")), None)
|
||||||
self.assertEquals(cache.get(("a", "b")), None)
|
self.assertEquals(cache.get(("a", "b")), None)
|
||||||
self.assertEquals(cache.get(("b", "a")), "BA")
|
self.assertEquals(cache.get(("b", "a")), "BA")
|
||||||
|
self.assertEquals(len(cache), 1)
|
||||||
|
|
||||||
|
def test_clear(self):
|
||||||
|
cache = TreeCache()
|
||||||
|
cache[("a",)] = "A"
|
||||||
|
cache[("b",)] = "B"
|
||||||
|
cache.clear()
|
||||||
|
self.assertEquals(len(cache), 0)
|
||||||
|
|
Loading…
Reference in New Issue