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

Commit 44bb881

Browse files
authored
Add type hints to expiring cache. (#9730)
1 parent 024f121 commit 44bb881

File tree

8 files changed

+65
-54
lines changed

8 files changed

+65
-54
lines changed

changelog.d/9730.misc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add type hints to expiring cache.

synapse/federation/federation_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ def __init__(self, hs: "HomeServer"):
102102
max_len=1000,
103103
expiry_ms=120 * 1000,
104104
reset_expiry_on_get=False,
105-
)
105+
) # type: ExpiringCache[str, EventBase]
106106

107107
def _clear_tried_cache(self):
108108
"""Clear pdu_destination_tried cache"""

synapse/handlers/device.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -631,7 +631,7 @@ def __init__(self, hs: "HomeServer", device_handler: DeviceHandler):
631631
max_len=10000,
632632
expiry_ms=30 * 60 * 1000,
633633
iterable=True,
634-
)
634+
) # type: ExpiringCache[str, Set[str]]
635635

636636
# Attempt to resync out of sync device lists every 30s.
637637
self._resync_retry_in_progress = False
@@ -760,7 +760,7 @@ async def _need_to_do_resync(
760760
"""Given a list of updates for a user figure out if we need to do a full
761761
resync, or whether we have enough data that we can just apply the delta.
762762
"""
763-
seen_updates = self._seen_updates.get(user_id, set())
763+
seen_updates = self._seen_updates.get(user_id, set()) # type: Set[str]
764764

765765
extremity = await self.store.get_device_list_last_stream_id_for_remote(user_id)
766766

synapse/handlers/e2e_keys.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@
3838
)
3939
from synapse.util import json_decoder, unwrapFirstError
4040
from synapse.util.async_helpers import Linearizer
41-
from synapse.util.caches.expiringcache import ExpiringCache
4241
from synapse.util.retryutils import NotRetryingDestination
4342

4443
if TYPE_CHECKING:
@@ -1292,17 +1291,6 @@ def __init__(self, hs: "HomeServer", e2e_keys_handler: E2eKeysHandler):
12921291
# user_id -> list of updates waiting to be handled.
12931292
self._pending_updates = {} # type: Dict[str, List[Tuple[JsonDict, JsonDict]]]
12941293

1295-
# Recently seen stream ids. We don't bother keeping these in the DB,
1296-
# but they're useful to have them about to reduce the number of spurious
1297-
# resyncs.
1298-
self._seen_updates = ExpiringCache(
1299-
cache_name="signing_key_update_edu",
1300-
clock=self.clock,
1301-
max_len=10000,
1302-
expiry_ms=30 * 60 * 1000,
1303-
iterable=True,
1304-
)
1305-
13061294
async def incoming_signing_key_update(
13071295
self, origin: str, edu_content: JsonDict
13081296
) -> None:

synapse/handlers/sync.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -252,13 +252,13 @@ def __init__(self, hs: "HomeServer"):
252252
self.storage = hs.get_storage()
253253
self.state_store = self.storage.state
254254

255-
# ExpiringCache((User, Device)) -> LruCache(state_key => event_id)
255+
# ExpiringCache((User, Device)) -> LruCache(user_id => event_id)
256256
self.lazy_loaded_members_cache = ExpiringCache(
257257
"lazy_loaded_members_cache",
258258
self.clock,
259259
max_len=0,
260260
expiry_ms=LAZY_LOADED_MEMBERS_CACHE_MAX_AGE,
261-
)
261+
) # type: ExpiringCache[Tuple[str, Optional[str]], LruCache[str, str]]
262262

263263
async def wait_for_sync_for_user(
264264
self,
@@ -733,8 +733,10 @@ async def compute_summary(
733733

734734
def get_lazy_loaded_members_cache(
735735
self, cache_key: Tuple[str, Optional[str]]
736-
) -> LruCache:
737-
cache = self.lazy_loaded_members_cache.get(cache_key)
736+
) -> LruCache[str, str]:
737+
cache = self.lazy_loaded_members_cache.get(
738+
cache_key
739+
) # type: Optional[LruCache[str, str]]
738740
if cache is None:
739741
logger.debug("creating LruCache for %r", cache_key)
740742
cache = LruCache(LAZY_LOADED_MEMBERS_CACHE_MAX_SIZE)

synapse/rest/media/v1/preview_url_resource.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ def __init__(
175175
clock=self.clock,
176176
# don't spider URLs more often than once an hour
177177
expiry_ms=ONE_HOUR,
178-
)
178+
) # type: ExpiringCache[str, ObservableDeferred]
179179

180180
if self._worker_run_media_background_jobs:
181181
self._cleaner_loop = self.clock.looping_call(

synapse/state/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
Callable,
2323
DefaultDict,
2424
Dict,
25+
FrozenSet,
2526
Iterable,
2627
List,
2728
Optional,
@@ -515,7 +516,7 @@ def __init__(self, hs):
515516
expiry_ms=EVICTION_TIMEOUT_SECONDS * 1000,
516517
iterable=True,
517518
reset_expiry_on_get=True,
518-
)
519+
) # type: ExpiringCache[FrozenSet[int], _StateCacheEntry]
519520

520521
#
521522
# stuff for tracking time spent on state-res by room
@@ -536,7 +537,7 @@ async def resolve_state_groups(
536537
state_groups_ids: Dict[int, StateMap[str]],
537538
event_map: Optional[Dict[str, EventBase]],
538539
state_res_store: "StateResolutionStore",
539-
):
540+
) -> _StateCacheEntry:
540541
"""Resolves conflicts between a set of state groups
541542
542543
Always generates a new state group (unless we hit the cache), so should

synapse/util/caches/expiringcache.py

Lines changed: 51 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -15,40 +15,50 @@
1515

1616
import logging
1717
from collections import OrderedDict
18+
from typing import Any, Generic, Optional, TypeVar, Union, overload
19+
20+
import attr
21+
from typing_extensions import Literal
1822

1923
from synapse.config import cache as cache_config
2024
from synapse.metrics.background_process_metrics import run_as_background_process
25+
from synapse.util import Clock
2126
from synapse.util.caches import register_cache
2227

2328
logger = logging.getLogger(__name__)
2429

2530

26-
SENTINEL = object()
31+
SENTINEL = object() # type: Any
32+
2733

34+
T = TypeVar("T")
35+
KT = TypeVar("KT")
36+
VT = TypeVar("VT")
2837

29-
class ExpiringCache:
38+
39+
class ExpiringCache(Generic[KT, VT]):
3040
def __init__(
3141
self,
32-
cache_name,
33-
clock,
34-
max_len=0,
35-
expiry_ms=0,
36-
reset_expiry_on_get=False,
37-
iterable=False,
42+
cache_name: str,
43+
clock: Clock,
44+
max_len: int = 0,
45+
expiry_ms: int = 0,
46+
reset_expiry_on_get: bool = False,
47+
iterable: bool = False,
3848
):
3949
"""
4050
Args:
41-
cache_name (str): Name of this cache, used for logging.
42-
clock (Clock)
43-
max_len (int): Max size of dict. If the dict grows larger than this
51+
cache_name: Name of this cache, used for logging.
52+
clock
53+
max_len: Max size of dict. If the dict grows larger than this
4454
then the oldest items get automatically evicted. Default is 0,
4555
which indicates there is no max limit.
46-
expiry_ms (int): How long before an item is evicted from the cache
56+
expiry_ms: How long before an item is evicted from the cache
4757
in milliseconds. Default is 0, indicating items never get
4858
evicted based on time.
49-
reset_expiry_on_get (bool): If true, will reset the expiry time for
59+
reset_expiry_on_get: If true, will reset the expiry time for
5060
an item on access. Defaults to False.
51-
iterable (bool): If true, the size is calculated by summing the
61+
iterable: If true, the size is calculated by summing the
5262
sizes of all entries, rather than the number of entries.
5363
"""
5464
self._cache_name = cache_name
@@ -62,7 +72,7 @@ def __init__(
6272
self._expiry_ms = expiry_ms
6373
self._reset_expiry_on_get = reset_expiry_on_get
6474

65-
self._cache = OrderedDict()
75+
self._cache = OrderedDict() # type: OrderedDict[KT, _CacheEntry]
6676

6777
self.iterable = iterable
6878

@@ -79,12 +89,12 @@ def f():
7989

8090
self._clock.looping_call(f, self._expiry_ms / 2)
8191

82-
def __setitem__(self, key, value):
92+
def __setitem__(self, key: KT, value: VT) -> None:
8393
now = self._clock.time_msec()
8494
self._cache[key] = _CacheEntry(now, value)
8595
self.evict()
8696

87-
def evict(self):
97+
def evict(self) -> None:
8898
# Evict if there are now too many items
8999
while self._max_size and len(self) > self._max_size:
90100
_key, value = self._cache.popitem(last=False)
@@ -93,7 +103,7 @@ def evict(self):
93103
else:
94104
self.metrics.inc_evictions()
95105

96-
def __getitem__(self, key):
106+
def __getitem__(self, key: KT) -> VT:
97107
try:
98108
entry = self._cache[key]
99109
self.metrics.inc_hits()
@@ -106,7 +116,7 @@ def __getitem__(self, key):
106116

107117
return entry.value
108118

109-
def pop(self, key, default=SENTINEL):
119+
def pop(self, key: KT, default: T = SENTINEL) -> Union[VT, T]:
110120
"""Removes and returns the value with the given key from the cache.
111121
112122
If the key isn't in the cache then `default` will be returned if
@@ -115,29 +125,40 @@ def pop(self, key, default=SENTINEL):
115125
Identical functionality to `dict.pop(..)`.
116126
"""
117127

118-
value = self._cache.pop(key, default)
128+
value = self._cache.pop(key, SENTINEL)
129+
# The key was not found.
119130
if value is SENTINEL:
120-
raise KeyError(key)
131+
if default is SENTINEL:
132+
raise KeyError(key)
133+
return default
121134

122-
return value
135+
return value.value
123136

124-
def __contains__(self, key):
137+
def __contains__(self, key: KT) -> bool:
125138
return key in self._cache
126139

127-
def get(self, key, default=None):
140+
@overload
141+
def get(self, key: KT, default: Literal[None] = None) -> Optional[VT]:
142+
...
143+
144+
@overload
145+
def get(self, key: KT, default: T) -> Union[VT, T]:
146+
...
147+
148+
def get(self, key: KT, default: Optional[T] = None) -> Union[VT, Optional[T]]:
128149
try:
129150
return self[key]
130151
except KeyError:
131152
return default
132153

133-
def setdefault(self, key, value):
154+
def setdefault(self, key: KT, value: VT) -> VT:
134155
try:
135156
return self[key]
136157
except KeyError:
137158
self[key] = value
138159
return value
139160

140-
def _prune_cache(self):
161+
def _prune_cache(self) -> None:
141162
if not self._expiry_ms:
142163
# zero expiry time means don't expire. This should never get called
143164
# since we have this check in start too.
@@ -166,7 +187,7 @@ def _prune_cache(self):
166187
len(self),
167188
)
168189

169-
def __len__(self):
190+
def __len__(self) -> int:
170191
if self.iterable:
171192
return sum(len(entry.value) for entry in self._cache.values())
172193
else:
@@ -190,9 +211,7 @@ def set_cache_factor(self, factor: float) -> bool:
190211
return False
191212

192213

214+
@attr.s(slots=True)
193215
class _CacheEntry:
194-
__slots__ = ["time", "value"]
195-
196-
def __init__(self, time, value):
197-
self.time = time
198-
self.value = value
216+
time = attr.ib(type=int)
217+
value = attr.ib()

0 commit comments

Comments
 (0)