mirror of
https://github.com/zulip/zulip.git
synced 2025-11-07 07:23:22 +00:00
cache: Add ignore_unhashable_lru_cache function.
This is a wrapper over lru_cache function. It adds following features on
top of lru_cache:
* It will not cache result of functions with unhashable arguments.
* It will clear cache whenever zerver.lib.cache.KEY_PREFIX changes.
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
|
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
|
||||||
|
from django.utils.lru_cache import lru_cache
|
||||||
from django.core.cache import cache as djcache
|
from django.core.cache import cache as djcache
|
||||||
from django.core.cache import caches
|
from django.core.cache import caches
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@@ -454,3 +455,47 @@ def to_dict_cache_key(message):
|
|||||||
def flush_message(sender: Any, **kwargs: Any) -> None:
|
def flush_message(sender: Any, **kwargs: Any) -> None:
|
||||||
message = kwargs['instance']
|
message = kwargs['instance']
|
||||||
cache_delete(to_dict_cache_key_id(message.id))
|
cache_delete(to_dict_cache_key_id(message.id))
|
||||||
|
|
||||||
|
DECORATOR = Callable[[Callable[..., Any]], Callable[..., Any]]
|
||||||
|
|
||||||
|
def ignore_unhashable_lru_cache(maxsize: int=128, typed: bool=False) -> DECORATOR:
|
||||||
|
"""
|
||||||
|
This is a wrapper over lru_cache function. It adds following features on
|
||||||
|
top of lru_cache:
|
||||||
|
|
||||||
|
* It will not cache result of functions with unhashable arguments.
|
||||||
|
* It will clear cache whenever zerver.lib.cache.KEY_PREFIX changes.
|
||||||
|
"""
|
||||||
|
internal_decorator = lru_cache(maxsize=maxsize, typed=typed)
|
||||||
|
|
||||||
|
def decorator(user_function: Callable[..., Any]) -> Callable[..., Any]:
|
||||||
|
cache_enabled_user_function = internal_decorator(user_function)
|
||||||
|
|
||||||
|
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
||||||
|
if not hasattr(cache_enabled_user_function, 'key_prefix'):
|
||||||
|
cache_enabled_user_function.key_prefix = KEY_PREFIX
|
||||||
|
|
||||||
|
if cache_enabled_user_function.key_prefix != KEY_PREFIX:
|
||||||
|
# Clear cache when cache.KEY_PREFIX changes. This is used in
|
||||||
|
# tests.
|
||||||
|
cache_enabled_user_function.cache_clear()
|
||||||
|
cache_enabled_user_function.key_prefix = KEY_PREFIX
|
||||||
|
|
||||||
|
try:
|
||||||
|
return cache_enabled_user_function(*args, **kwargs)
|
||||||
|
except TypeError:
|
||||||
|
# args or kwargs contains an element which is unhashable. In
|
||||||
|
# this case we don't cache the result.
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Deliberately calling this function from outside of exception
|
||||||
|
# handler to get a more descriptive traceback. Otherise traceback
|
||||||
|
# can include the exception from cached_enabled_user_function as
|
||||||
|
# well.
|
||||||
|
return user_function(*args, **kwargs)
|
||||||
|
|
||||||
|
setattr(wrapper, 'cache_info', cache_enabled_user_function.cache_info)
|
||||||
|
setattr(wrapper, 'cache_clear', cache_enabled_user_function.cache_clear)
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ from zerver.decorator import (
|
|||||||
rate_limit, validate_api_key, logged_in_and_active,
|
rate_limit, validate_api_key, logged_in_and_active,
|
||||||
return_success_on_head_request
|
return_success_on_head_request
|
||||||
)
|
)
|
||||||
|
from zerver.lib.cache import ignore_unhashable_lru_cache
|
||||||
from zerver.lib.validator import (
|
from zerver.lib.validator import (
|
||||||
check_string, check_dict, check_dict_only, check_bool, check_float, check_int, check_list, Validator,
|
check_string, check_dict, check_dict_only, check_bool, check_float, check_int, check_list, Validator,
|
||||||
check_variable_type, equals, check_none_or, check_url, check_short_string
|
check_variable_type, equals, check_none_or, check_url, check_short_string
|
||||||
@@ -1285,3 +1286,52 @@ class TestUserAgentParsing(ZulipTestCase):
|
|||||||
user_agents_parsed[ret["name"]] += int(count)
|
user_agents_parsed[ret["name"]] += int(count)
|
||||||
|
|
||||||
self.assertEqual(len(parse_errors), 0)
|
self.assertEqual(len(parse_errors), 0)
|
||||||
|
|
||||||
|
class TestIgnoreUnhashableLRUCache(ZulipTestCase):
|
||||||
|
def test_cache_hit(self) -> None:
|
||||||
|
@ignore_unhashable_lru_cache()
|
||||||
|
def f(arg: Any) -> Any:
|
||||||
|
return arg
|
||||||
|
|
||||||
|
def get_cache_info() -> Tuple[int, int, int]:
|
||||||
|
info = getattr(f, 'cache_info')()
|
||||||
|
hits = getattr(info, 'hits')
|
||||||
|
misses = getattr(info, 'misses')
|
||||||
|
currsize = getattr(info, 'currsize')
|
||||||
|
return hits, misses, currsize
|
||||||
|
|
||||||
|
def clear_cache() -> None:
|
||||||
|
getattr(f, 'cache_clear')()
|
||||||
|
|
||||||
|
# Check hashable argument.
|
||||||
|
result = f(1)
|
||||||
|
hits, misses, currsize = get_cache_info()
|
||||||
|
# First one should be a miss.
|
||||||
|
self.assertEqual(hits, 0)
|
||||||
|
self.assertEqual(misses, 1)
|
||||||
|
self.assertEqual(currsize, 1)
|
||||||
|
self.assertEqual(result, 1)
|
||||||
|
|
||||||
|
result = f(1)
|
||||||
|
hits, misses, currsize = get_cache_info()
|
||||||
|
# Second one should be a hit.
|
||||||
|
self.assertEqual(hits, 1)
|
||||||
|
self.assertEqual(misses, 1)
|
||||||
|
self.assertEqual(currsize, 1)
|
||||||
|
self.assertEqual(result, 1)
|
||||||
|
|
||||||
|
# Check unhashable argument.
|
||||||
|
result = f([1])
|
||||||
|
hits, misses, currsize = get_cache_info()
|
||||||
|
# Cache should not be used.
|
||||||
|
self.assertEqual(hits, 1)
|
||||||
|
self.assertEqual(misses, 1)
|
||||||
|
self.assertEqual(currsize, 1)
|
||||||
|
self.assertEqual(result, [1])
|
||||||
|
|
||||||
|
# Clear cache.
|
||||||
|
clear_cache()
|
||||||
|
hits, misses, currsize = get_cache_info()
|
||||||
|
self.assertEqual(hits, 0)
|
||||||
|
self.assertEqual(misses, 0)
|
||||||
|
self.assertEqual(currsize, 0)
|
||||||
|
|||||||
Reference in New Issue
Block a user