diff --git a/frappe/tests/classes/integration_test_case.py b/frappe/tests/classes/integration_test_case.py index 2d44c4a514..a089f2e0cd 100644 --- a/frappe/tests/classes/integration_test_case.py +++ b/frappe/tests/classes/integration_test_case.py @@ -129,7 +129,7 @@ class IntegrationTestCase(UnitTestCase): frappe.db.__class__.sql = orig_sql @contextmanager - def assertRedisCallCounts(self, count: int) -> AbstractContextManager[None]: + def assertRedisCallCounts(self, count: int, *, exact=False) -> AbstractContextManager[None]: from frappe.utils.redis_wrapper import RedisWrapper commands = [] @@ -146,9 +146,11 @@ class IntegrationTestCase(UnitTestCase): orig_execute = RedisWrapper.execute_command RedisWrapper.execute_command = execute_command_and_count yield - self.assertLessEqual( - len(commands), count, msg="commands executed: \n" + "\n".join(str(c) for c in commands) - ) + msg = "commands executed: \n" + "\n".join(str(c) for c in commands) + if exact: + self.assertEqual(len(commands), count, msg=msg) + else: + self.assertLessEqual(len(commands), count, msg=msg) finally: RedisWrapper.execute_command = orig_execute diff --git a/frappe/tests/test_client_cache.py b/frappe/tests/test_client_cache.py index 39cc314f45..ade4ad2b8b 100644 --- a/frappe/tests/test_client_cache.py +++ b/frappe/tests/test_client_cache.py @@ -2,6 +2,7 @@ import time import frappe from frappe.tests import IntegrationTestCase +from frappe.utils.redis_wrapper import _ClientCache TEST_KEY = "42" @@ -35,5 +36,23 @@ class TestClientCache(IntegrationTestCase): # current thread. So we wait. time.sleep(0.1) - with self.assertRedisCallCounts(1): + with self.assertRedisCallCounts(1, exact=True): self.assertEqual(frappe.client_cache.get_value(TEST_KEY), val) + + def test_client_local_cache_ttl(self): + c = _ClientCache(ttl=1) + c.set_value(TEST_KEY, 42) + with self.assertRedisCallCounts(0): + c.get_value(TEST_KEY) + time.sleep(1) + + with self.assertRedisCallCounts(1, exact=True): + c.get_value(TEST_KEY) + + def test_client_cache_maxsize(self): + c = _ClientCache(maxsize=2) + c.set_value(TEST_KEY, 42) + c.set_value(frappe.generate_hash(), 42) + c.set_value(frappe.generate_hash(), 42) + + self.assertEqual(len(c.local_cache), 2) diff --git a/frappe/utils/redis_wrapper.py b/frappe/utils/redis_wrapper.py index 94361c928d..bcfa4831bc 100644 --- a/frappe/utils/redis_wrapper.py +++ b/frappe/utils/redis_wrapper.py @@ -450,16 +450,14 @@ class _ClientCache: if val is None: return None - if len(self.local_cache) >= self.maxsize: - with suppress(RuntimeError): - self.local_cache.pop(next(iter(self.local_cache)), None) - + self.ensure_max_size() self.local_cache[key] = (val, time.monotonic() + self.local_ttl) return val def set_value(self, key, val): key = self.redis.make_key(key) + self.ensure_max_size() self.redis.set_value(key, val, shared=True) self.local_cache[key] = (val, time.monotonic() + self.local_ttl) # XXX: We need to tell redis that we indeed read this key we just wrote @@ -469,6 +467,11 @@ class _ClientCache: # doesn't send invalidation. _ = self.redis.get_value(key, shared=True, use_local_cache=False) + def ensure_max_size(self): + if len(self.local_cache) >= self.maxsize: + with suppress(RuntimeError): + self.local_cache.pop(next(iter(self.local_cache)), None) + def delete_value(self, key): key = self.redis.make_key(key) self.redis.delete_value(key, shared=True)