Skip to content
This repository was archived by the owner on Apr 26, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/9028.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix a long-standing bug where some caches could grow larger than configured.
2 changes: 1 addition & 1 deletion synapse/util/caches/deferred_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ def metrics_cb():
keylen=keylen,
cache_name=name,
cache_type=cache_type,
size_callback=(lambda d: len(d)) if iterable else None,
size_callback=(lambda d: len(d) or 1) if iterable else None,
metrics_collection_callback=metrics_cb,
apply_cache_factor_from_config=apply_cache_factor_from_config,
) # type: LruCache[KT, VT]
Expand Down
73 changes: 50 additions & 23 deletions tests/util/caches/test_deferred_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,8 @@
class DeferredCacheTestCase(TestCase):
def test_empty(self):
cache = DeferredCache("test")
failed = False
try:
with self.assertRaises(KeyError):
cache.get("foo")
except KeyError:
failed = True

self.assertTrue(failed)

def test_hit(self):
cache = DeferredCache("test")
Expand Down Expand Up @@ -155,13 +150,8 @@ def test_invalidate(self):
cache.prefill(("foo",), 123)
cache.invalidate(("foo",))

failed = False
try:
with self.assertRaises(KeyError):
cache.get(("foo",))
except KeyError:
failed = True

self.assertTrue(failed)

def test_invalidate_all(self):
cache = DeferredCache("testcache")
Expand Down Expand Up @@ -215,13 +205,8 @@ def test_eviction(self):
cache.prefill(2, "two")
cache.prefill(3, "three") # 1 will be evicted

failed = False
try:
with self.assertRaises(KeyError):
cache.get(1)
except KeyError:
failed = True

self.assertTrue(failed)

cache.get(2)
cache.get(3)
Expand All @@ -239,13 +224,55 @@ def test_eviction_lru(self):

cache.prefill(3, "three")

failed = False
try:
with self.assertRaises(KeyError):
cache.get(2)
except KeyError:
failed = True

self.assertTrue(failed)
cache.get(1)
cache.get(3)

def test_eviction_iterable(self):
cache = DeferredCache(
"test", max_entries=3, apply_cache_factor_from_config=False, iterable=True,
)

cache.prefill(1, ["one", "two"])
cache.prefill(2, ["three"])

# Now access 1 again, thus causing 2 to be least-recently used
cache.get(1)

# Now add an item to the cache, which evicts 2.
cache.prefill(3, ["four"])
with self.assertRaises(KeyError):
cache.get(2)

# Ensure 1 & 3 are in the cache.
cache.get(1)
cache.get(3)

# Now access 1 again, thus causing 3 to be least-recently used
cache.get(1)

# Now add an item with multiple elements to the cache
cache.prefill(4, ["five", "six"])

# Both 1 and 3 are evicted since there's too many elements.
with self.assertRaises(KeyError):
cache.get(1)
with self.assertRaises(KeyError):
cache.get(3)

# Now add another item to fill the cache again.
cache.prefill(5, ["seven"])

# Now access 4, thus causing 5 to be least-recently used
cache.get(4)

# Add an empty item.
cache.prefill(6, [])

# 5 gets evicted and replaced since an empty element counts as an item.
with self.assertRaises(KeyError):
cache.get(5)
cache.get(4)
cache.get(6)