Skip to content
This repository was archived by the owner on Apr 26, 2024. It is now read-only.

Commit 7ff22d6

Browse files
authored
Fix LruCache corruption bug with a size_callback that can return 0 (#11454)
When all entries in an `LruCache` have a size of 0 according to the provided `size_callback`, and `drop_from_cache` is called on a cache node, the node would be unlinked from the LRU linked list but remain in the cache dictionary. An assertion would be later be tripped due to the inconsistency. Avoid unintentionally calling `__len__` and use a strict `is None` check instead when unwrapping the weak reference.
1 parent 5a0b652 commit 7ff22d6

File tree

3 files changed

+17
-1
lines changed

3 files changed

+17
-1
lines changed

changelog.d/11454.bugfix

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix an `LruCache` corruption bug, introduced in 1.38.0, that would cause certain requests to fail until the next Synapse restart.

synapse/util/caches/lrucache.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,10 @@ def drop_from_cache(self) -> None:
271271
removed from all lists.
272272
"""
273273
cache = self._cache()
274-
if not cache or not cache.pop(self.key, None):
274+
if (
275+
cache is None
276+
or cache.pop(self.key, _Sentinel.sentinel) is _Sentinel.sentinel
277+
):
275278
# `cache.pop` should call `drop_from_lists()`, unless this Node had
276279
# already been removed from the cache.
277280
self.drop_from_lists()

tests/util/test_lrucache.py

+12
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# limitations under the License.
1414

1515

16+
from typing import List
1617
from unittest.mock import Mock
1718

1819
from synapse.util.caches.lrucache import LruCache, setup_expire_lru_cache_entries
@@ -261,6 +262,17 @@ def test_evict(self):
261262
self.assertEquals(cache["key4"], [4])
262263
self.assertEquals(cache["key5"], [5, 6])
263264

265+
def test_zero_size_drop_from_cache(self) -> None:
266+
"""Test that `drop_from_cache` works correctly with 0-sized entries."""
267+
cache: LruCache[str, List[int]] = LruCache(5, size_callback=lambda x: 0)
268+
cache["key1"] = []
269+
270+
self.assertEqual(len(cache), 0)
271+
cache.cache["key1"].drop_from_cache()
272+
self.assertIsNone(
273+
cache.pop("key1"), "Cache entry should have been evicted but wasn't"
274+
)
275+
264276

265277
class TimeEvictionTestCase(unittest.HomeserverTestCase):
266278
"""Test that time based eviction works correctly."""

0 commit comments

Comments
 (0)