|
|
@@ -15,14 +15,15 @@ |
|
|
|
import logging |
|
|
|
import threading |
|
|
|
import weakref |
|
|
|
from enum import Enum |
|
|
|
from functools import wraps |
|
|
|
from typing import ( |
|
|
|
TYPE_CHECKING, |
|
|
|
Any, |
|
|
|
Callable, |
|
|
|
Collection, |
|
|
|
Dict, |
|
|
|
Generic, |
|
|
|
Iterable, |
|
|
|
List, |
|
|
|
Optional, |
|
|
|
Type, |
|
|
@@ -190,7 +191,7 @@ class _Node(Generic[KT, VT]): |
|
|
|
root: "ListNode[_Node]", |
|
|
|
key: KT, |
|
|
|
value: VT, |
|
|
|
cache: "weakref.ReferenceType[LruCache]", |
|
|
|
cache: "weakref.ReferenceType[LruCache[KT, VT]]", |
|
|
|
clock: Clock, |
|
|
|
callbacks: Collection[Callable[[], None]] = (), |
|
|
|
prune_unread_entries: bool = True, |
|
|
@@ -290,6 +291,12 @@ class _Node(Generic[KT, VT]): |
|
|
|
self._global_list_node.update_last_access(clock) |
|
|
|
|
|
|
|
|
|
|
|
class _Sentinel(Enum): |
|
|
|
# defining a sentinel in this way allows mypy to correctly handle the |
|
|
|
# type of a dictionary lookup. |
|
|
|
sentinel = object() |
|
|
|
|
|
|
|
|
|
|
|
class LruCache(Generic[KT, VT]): |
|
|
|
""" |
|
|
|
Least-recently-used cache, supporting prometheus metrics and invalidation callbacks. |
|
|
@@ -302,7 +309,7 @@ class LruCache(Generic[KT, VT]): |
|
|
|
max_size: int, |
|
|
|
cache_name: Optional[str] = None, |
|
|
|
cache_type: Type[Union[dict, TreeCache]] = dict, |
|
|
|
size_callback: Optional[Callable] = None, |
|
|
|
size_callback: Optional[Callable[[VT], int]] = None, |
|
|
|
metrics_collection_callback: Optional[Callable[[], None]] = None, |
|
|
|
apply_cache_factor_from_config: bool = True, |
|
|
|
clock: Optional[Clock] = None, |
|
|
@@ -339,7 +346,7 @@ class LruCache(Generic[KT, VT]): |
|
|
|
else: |
|
|
|
real_clock = clock |
|
|
|
|
|
|
|
cache = cache_type() |
|
|
|
cache: Union[Dict[KT, _Node[KT, VT]], TreeCache] = cache_type() |
|
|
|
self.cache = cache # Used for introspection. |
|
|
|
self.apply_cache_factor_from_config = apply_cache_factor_from_config |
|
|
|
|
|
|
@@ -374,7 +381,7 @@ class LruCache(Generic[KT, VT]): |
|
|
|
# creating more each time we create a `_Node`. |
|
|
|
weak_ref_to_self = weakref.ref(self) |
|
|
|
|
|
|
|
list_root = ListNode[_Node].create_root_node() |
|
|
|
list_root = ListNode[_Node[KT, VT]].create_root_node() |
|
|
|
|
|
|
|
lock = threading.Lock() |
|
|
|
|
|
|
@@ -422,7 +429,7 @@ class LruCache(Generic[KT, VT]): |
|
|
|
def add_node( |
|
|
|
key: KT, value: VT, callbacks: Collection[Callable[[], None]] = () |
|
|
|
) -> None: |
|
|
|
node = _Node( |
|
|
|
node: _Node[KT, VT] = _Node( |
|
|
|
list_root, |
|
|
|
key, |
|
|
|
value, |
|
|
@@ -439,10 +446,10 @@ class LruCache(Generic[KT, VT]): |
|
|
|
if caches.TRACK_MEMORY_USAGE and metrics: |
|
|
|
metrics.inc_memory_usage(node.memory) |
|
|
|
|
|
|
|
def move_node_to_front(node: _Node) -> None: |
|
|
|
def move_node_to_front(node: _Node[KT, VT]) -> None: |
|
|
|
node.move_to_front(real_clock, list_root) |
|
|
|
|
|
|
|
def delete_node(node: _Node) -> int: |
|
|
|
def delete_node(node: _Node[KT, VT]) -> int: |
|
|
|
node.drop_from_lists() |
|
|
|
|
|
|
|
deleted_len = 1 |
|
|
@@ -496,7 +503,7 @@ class LruCache(Generic[KT, VT]): |
|
|
|
|
|
|
|
@synchronized |
|
|
|
def cache_set( |
|
|
|
key: KT, value: VT, callbacks: Iterable[Callable[[], None]] = () |
|
|
|
key: KT, value: VT, callbacks: Collection[Callable[[], None]] = () |
|
|
|
) -> None: |
|
|
|
node = cache.get(key, None) |
|
|
|
if node is not None: |
|
|
@@ -590,8 +597,6 @@ class LruCache(Generic[KT, VT]): |
|
|
|
def cache_contains(key: KT) -> bool: |
|
|
|
return key in cache |
|
|
|
|
|
|
|
self.sentinel = object() |
|
|
|
|
|
|
|
# make sure that we clear out any excess entries after we get resized. |
|
|
|
self._on_resize = evict |
|
|
|
|
|
|
@@ -608,18 +613,18 @@ class LruCache(Generic[KT, VT]): |
|
|
|
self.clear = cache_clear |
|
|
|
|
|
|
|
def __getitem__(self, key: KT) -> VT: |
|
|
|
result = self.get(key, self.sentinel) |
|
|
|
if result is self.sentinel: |
|
|
|
result = self.get(key, _Sentinel.sentinel) |
|
|
|
if result is _Sentinel.sentinel: |
|
|
|
raise KeyError() |
|
|
|
else: |
|
|
|
return cast(VT, result) |
|
|
|
return result |
|
|
|
|
|
|
|
def __setitem__(self, key: KT, value: VT) -> None: |
|
|
|
self.set(key, value) |
|
|
|
|
|
|
|
def __delitem__(self, key: KT, value: VT) -> None: |
|
|
|
result = self.pop(key, self.sentinel) |
|
|
|
if result is self.sentinel: |
|
|
|
result = self.pop(key, _Sentinel.sentinel) |
|
|
|
if result is _Sentinel.sentinel: |
|
|
|
raise KeyError() |
|
|
|
|
|
|
|
def __len__(self) -> int: |
|
|
|