deferred_cache.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515
  1. # Copyright 2015, 2016 OpenMarket Ltd
  2. # Copyright 2018 New Vector Ltd
  3. # Copyright 2020 The Matrix.org Foundation C.I.C.
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. import abc
  17. import enum
  18. import threading
  19. from typing import (
  20. Callable,
  21. Collection,
  22. Dict,
  23. Generic,
  24. MutableMapping,
  25. Optional,
  26. Set,
  27. Sized,
  28. Tuple,
  29. TypeVar,
  30. Union,
  31. cast,
  32. )
  33. from prometheus_client import Gauge
  34. from twisted.internet import defer
  35. from twisted.python.failure import Failure
  36. from synapse.util.async_helpers import ObservableDeferred
  37. from synapse.util.caches.lrucache import LruCache
  38. from synapse.util.caches.treecache import TreeCache, iterate_tree_cache_entry
  39. cache_pending_metric = Gauge(
  40. "synapse_util_caches_cache_pending",
  41. "Number of lookups currently pending for this cache",
  42. ["name"],
  43. )
  44. T = TypeVar("T")
  45. KT = TypeVar("KT")
  46. VT = TypeVar("VT")
  47. class _Sentinel(enum.Enum):
  48. # defining a sentinel in this way allows mypy to correctly handle the
  49. # type of a dictionary lookup.
  50. sentinel = object()
  51. class DeferredCache(Generic[KT, VT]):
  52. """Wraps an LruCache, adding support for Deferred results.
  53. It expects that each entry added with set() will be a Deferred; likewise get()
  54. will return a Deferred.
  55. """
  56. __slots__ = (
  57. "cache",
  58. "thread",
  59. "_pending_deferred_cache",
  60. )
  61. def __init__(
  62. self,
  63. name: str,
  64. max_entries: int = 1000,
  65. tree: bool = False,
  66. iterable: bool = False,
  67. apply_cache_factor_from_config: bool = True,
  68. prune_unread_entries: bool = True,
  69. ):
  70. """
  71. Args:
  72. name: The name of the cache
  73. max_entries: Maximum amount of entries that the cache will hold
  74. tree: Use a TreeCache instead of a dict as the underlying cache type
  75. iterable: If True, count each item in the cached object as an entry,
  76. rather than each cached object
  77. apply_cache_factor_from_config: Whether cache factors specified in the
  78. config file affect `max_entries`
  79. prune_unread_entries: If True, cache entries that haven't been read recently
  80. will be evicted from the cache in the background. Set to False to
  81. opt-out of this behaviour.
  82. """
  83. cache_type = TreeCache if tree else dict
  84. # _pending_deferred_cache maps from the key value to a `CacheEntry` object.
  85. self._pending_deferred_cache: Union[
  86. TreeCache, "MutableMapping[KT, CacheEntry[KT, VT]]"
  87. ] = cache_type()
  88. def metrics_cb() -> None:
  89. cache_pending_metric.labels(name).set(len(self._pending_deferred_cache))
  90. # cache is used for completed results and maps to the result itself, rather than
  91. # a Deferred.
  92. self.cache: LruCache[KT, VT] = LruCache(
  93. max_size=max_entries,
  94. cache_name=name,
  95. cache_type=cache_type,
  96. size_callback=(
  97. (lambda d: len(cast(Sized, d)) or 1)
  98. # Argument 1 to "len" has incompatible type "VT"; expected "Sized"
  99. # We trust that `VT` is `Sized` when `iterable` is `True`
  100. if iterable
  101. else None
  102. ),
  103. metrics_collection_callback=metrics_cb,
  104. apply_cache_factor_from_config=apply_cache_factor_from_config,
  105. prune_unread_entries=prune_unread_entries,
  106. )
  107. self.thread: Optional[threading.Thread] = None
  108. @property
  109. def max_entries(self) -> int:
  110. return self.cache.max_size
  111. def check_thread(self) -> None:
  112. expected_thread = self.thread
  113. if expected_thread is None:
  114. self.thread = threading.current_thread()
  115. else:
  116. if expected_thread is not threading.current_thread():
  117. raise ValueError(
  118. "Cache objects can only be accessed from the main thread"
  119. )
  120. def get(
  121. self,
  122. key: KT,
  123. callback: Optional[Callable[[], None]] = None,
  124. update_metrics: bool = True,
  125. ) -> defer.Deferred:
  126. """Looks the key up in the caches.
  127. For symmetry with set(), this method does *not* follow the synapse logcontext
  128. rules: the logcontext will not be cleared on return, and the Deferred will run
  129. its callbacks in the sentinel context. In other words: wrap the result with
  130. make_deferred_yieldable() before `await`ing it.
  131. Args:
  132. key:
  133. callback: Gets called when the entry in the cache is invalidated
  134. update_metrics: whether to update the cache hit rate metrics
  135. Returns:
  136. A Deferred which completes with the result. Note that this may later fail
  137. if there is an ongoing set() operation which later completes with a failure.
  138. Raises:
  139. KeyError if the key is not found in the cache
  140. """
  141. val = self._pending_deferred_cache.get(key, _Sentinel.sentinel)
  142. if val is not _Sentinel.sentinel:
  143. val.add_invalidation_callback(key, callback)
  144. if update_metrics:
  145. m = self.cache.metrics
  146. assert m # we always have a name, so should always have metrics
  147. m.inc_hits()
  148. return val.deferred(key)
  149. callbacks = (callback,) if callback else ()
  150. val2 = self.cache.get(
  151. key, _Sentinel.sentinel, callbacks=callbacks, update_metrics=update_metrics
  152. )
  153. if val2 is _Sentinel.sentinel:
  154. raise KeyError()
  155. else:
  156. return defer.succeed(val2)
  157. def get_bulk(
  158. self,
  159. keys: Collection[KT],
  160. callback: Optional[Callable[[], None]] = None,
  161. ) -> Tuple[Dict[KT, VT], Optional["defer.Deferred[Dict[KT, VT]]"], Collection[KT]]:
  162. """Bulk lookup of items in the cache.
  163. Returns:
  164. A 3-tuple of:
  165. 1. a dict of key/value of items already cached;
  166. 2. a deferred that resolves to a dict of key/value of items
  167. we're already fetching; and
  168. 3. a collection of keys that don't appear in the previous two.
  169. """
  170. # The cached results
  171. cached = {}
  172. # List of pending deferreds
  173. pending = []
  174. # Dict that gets filled out when the pending deferreds complete
  175. pending_results = {}
  176. # List of keys that aren't in either cache
  177. missing = []
  178. callbacks = (callback,) if callback else ()
  179. for key in keys:
  180. # Check if its in the main cache.
  181. immediate_value = self.cache.get(
  182. key,
  183. _Sentinel.sentinel,
  184. callbacks=callbacks,
  185. )
  186. if immediate_value is not _Sentinel.sentinel:
  187. cached[key] = immediate_value
  188. continue
  189. # Check if its in the pending cache
  190. pending_value = self._pending_deferred_cache.get(key, _Sentinel.sentinel)
  191. if pending_value is not _Sentinel.sentinel:
  192. pending_value.add_invalidation_callback(key, callback)
  193. def completed_cb(value: VT, key: KT) -> VT:
  194. pending_results[key] = value
  195. return value
  196. # Add a callback to fill out `pending_results` when that completes
  197. d = pending_value.deferred(key).addCallback(completed_cb, key)
  198. pending.append(d)
  199. continue
  200. # Not in either cache
  201. missing.append(key)
  202. # If we've got pending deferreds, squash them into a single one that
  203. # returns `pending_results`.
  204. pending_deferred = None
  205. if pending:
  206. pending_deferred = defer.gatherResults(
  207. pending, consumeErrors=True
  208. ).addCallback(lambda _: pending_results)
  209. return (cached, pending_deferred, missing)
  210. def get_immediate(
  211. self, key: KT, default: T, update_metrics: bool = True
  212. ) -> Union[VT, T]:
  213. """If we have a *completed* cached value, return it."""
  214. return self.cache.get(key, default, update_metrics=update_metrics)
  215. def set(
  216. self,
  217. key: KT,
  218. value: "defer.Deferred[VT]",
  219. callback: Optional[Callable[[], None]] = None,
  220. ) -> defer.Deferred:
  221. """Adds a new entry to the cache (or updates an existing one).
  222. The given `value` *must* be a Deferred.
  223. First any existing entry for the same key is invalidated. Then a new entry
  224. is added to the cache for the given key.
  225. Until the `value` completes, calls to `get()` for the key will also result in an
  226. incomplete Deferred, which will ultimately complete with the same result as
  227. `value`.
  228. If `value` completes successfully, subsequent calls to `get()` will then return
  229. a completed deferred with the same result. If it *fails*, the cache is
  230. invalidated and subequent calls to `get()` will raise a KeyError.
  231. If another call to `set()` happens before `value` completes, then (a) any
  232. invalidation callbacks registered in the interim will be called, (b) any
  233. `get()`s in the interim will continue to complete with the result from the
  234. *original* `value`, (c) any future calls to `get()` will complete with the
  235. result from the *new* `value`.
  236. It is expected that `value` does *not* follow the synapse logcontext rules - ie,
  237. if it is incomplete, it runs its callbacks in the sentinel context.
  238. Args:
  239. key: Key to be set
  240. value: a deferred which will complete with a result to add to the cache
  241. callback: An optional callback to be called when the entry is invalidated
  242. """
  243. self.check_thread()
  244. self._pending_deferred_cache.pop(key, None)
  245. # XXX: why don't we invalidate the entry in `self.cache` yet?
  246. # otherwise, we'll add an entry to the _pending_deferred_cache for now,
  247. # and add callbacks to add it to the cache properly later.
  248. entry = CacheEntrySingle[KT, VT](value)
  249. entry.add_invalidation_callback(key, callback)
  250. self._pending_deferred_cache[key] = entry
  251. deferred = entry.deferred(key).addCallbacks(
  252. self._completed_callback,
  253. self._error_callback,
  254. callbackArgs=(entry, key),
  255. errbackArgs=(entry, key),
  256. )
  257. # we return a new Deferred which will be called before any subsequent observers.
  258. return deferred
  259. def start_bulk_input(
  260. self,
  261. keys: Collection[KT],
  262. callback: Optional[Callable[[], None]] = None,
  263. ) -> "CacheMultipleEntries[KT, VT]":
  264. """Bulk set API for use when fetching multiple keys at once from the DB.
  265. Called *before* starting the fetch from the DB, and the caller *must*
  266. call either `complete_bulk(..)` or `error_bulk(..)` on the return value.
  267. """
  268. entry = CacheMultipleEntries[KT, VT]()
  269. entry.add_global_invalidation_callback(callback)
  270. for key in keys:
  271. self._pending_deferred_cache[key] = entry
  272. return entry
  273. def _completed_callback(
  274. self, value: VT, entry: "CacheEntry[KT, VT]", key: KT
  275. ) -> VT:
  276. """Called when a deferred is completed."""
  277. # We check if the current entry matches the entry associated with the
  278. # deferred. If they don't match then it got invalidated.
  279. current_entry = self._pending_deferred_cache.pop(key, None)
  280. if current_entry is not entry:
  281. if current_entry:
  282. self._pending_deferred_cache[key] = current_entry
  283. return value
  284. self.cache.set(key, value, entry.get_invalidation_callbacks(key))
  285. return value
  286. def _error_callback(
  287. self,
  288. failure: Failure,
  289. entry: "CacheEntry[KT, VT]",
  290. key: KT,
  291. ) -> Failure:
  292. """Called when a deferred errors."""
  293. # We check if the current entry matches the entry associated with the
  294. # deferred. If they don't match then it got invalidated.
  295. current_entry = self._pending_deferred_cache.pop(key, None)
  296. if current_entry is not entry:
  297. if current_entry:
  298. self._pending_deferred_cache[key] = current_entry
  299. return failure
  300. for cb in entry.get_invalidation_callbacks(key):
  301. cb()
  302. return failure
  303. def prefill(
  304. self, key: KT, value: VT, callback: Optional[Callable[[], None]] = None
  305. ) -> None:
  306. callbacks = (callback,) if callback else ()
  307. self.cache.set(key, value, callbacks=callbacks)
  308. self._pending_deferred_cache.pop(key, None)
  309. def invalidate(self, key: KT) -> None:
  310. """Delete a key, or tree of entries
  311. If the cache is backed by a regular dict, then "key" must be of
  312. the right type for this cache
  313. If the cache is backed by a TreeCache, then "key" must be a tuple, but
  314. may be of lower cardinality than the TreeCache - in which case the whole
  315. subtree is deleted.
  316. """
  317. self.check_thread()
  318. self.cache.del_multi(key)
  319. # if we have a pending lookup for this key, remove it from the
  320. # _pending_deferred_cache, which will (a) stop it being returned for
  321. # future queries and (b) stop it being persisted as a proper entry
  322. # in self.cache.
  323. entry = self._pending_deferred_cache.pop(key, None)
  324. if entry:
  325. # _pending_deferred_cache.pop should either return a CacheEntry, or, in the
  326. # case of a TreeCache, a dict of keys to cache entries. Either way calling
  327. # iterate_tree_cache_entry on it will do the right thing.
  328. for iter_entry in iterate_tree_cache_entry(entry):
  329. for cb in iter_entry.get_invalidation_callbacks(key):
  330. cb()
  331. def invalidate_all(self) -> None:
  332. self.check_thread()
  333. self.cache.clear()
  334. for key, entry in self._pending_deferred_cache.items():
  335. for cb in entry.get_invalidation_callbacks(key):
  336. cb()
  337. self._pending_deferred_cache.clear()
  338. class CacheEntry(Generic[KT, VT], metaclass=abc.ABCMeta):
  339. """Abstract class for entries in `DeferredCache[KT, VT]`"""
  340. @abc.abstractmethod
  341. def deferred(self, key: KT) -> "defer.Deferred[VT]":
  342. """Get a deferred that a caller can wait on to get the value at the
  343. given key"""
  344. ...
  345. @abc.abstractmethod
  346. def add_invalidation_callback(
  347. self, key: KT, callback: Optional[Callable[[], None]]
  348. ) -> None:
  349. """Add an invalidation callback"""
  350. ...
  351. @abc.abstractmethod
  352. def get_invalidation_callbacks(self, key: KT) -> Collection[Callable[[], None]]:
  353. """Get all invalidation callbacks"""
  354. ...
  355. class CacheEntrySingle(CacheEntry[KT, VT]):
  356. """An implementation of `CacheEntry` wrapping a deferred that results in a
  357. single cache entry.
  358. """
  359. __slots__ = ["_deferred", "_callbacks"]
  360. def __init__(self, deferred: "defer.Deferred[VT]") -> None:
  361. self._deferred = ObservableDeferred(deferred, consumeErrors=True)
  362. self._callbacks: Set[Callable[[], None]] = set()
  363. def deferred(self, key: KT) -> "defer.Deferred[VT]":
  364. return self._deferred.observe()
  365. def add_invalidation_callback(
  366. self, key: KT, callback: Optional[Callable[[], None]]
  367. ) -> None:
  368. if callback is None:
  369. return
  370. self._callbacks.add(callback)
  371. def get_invalidation_callbacks(self, key: KT) -> Collection[Callable[[], None]]:
  372. return self._callbacks
  373. class CacheMultipleEntries(CacheEntry[KT, VT]):
  374. """Cache entry that is used for bulk lookups and insertions."""
  375. __slots__ = ["_deferred", "_callbacks", "_global_callbacks"]
  376. def __init__(self) -> None:
  377. self._deferred: Optional[ObservableDeferred[Dict[KT, VT]]] = None
  378. self._callbacks: Dict[KT, Set[Callable[[], None]]] = {}
  379. self._global_callbacks: Set[Callable[[], None]] = set()
  380. def deferred(self, key: KT) -> "defer.Deferred[VT]":
  381. if not self._deferred:
  382. self._deferred = ObservableDeferred(defer.Deferred(), consumeErrors=True)
  383. return self._deferred.observe().addCallback(lambda res: res[key])
  384. def add_invalidation_callback(
  385. self, key: KT, callback: Optional[Callable[[], None]]
  386. ) -> None:
  387. if callback is None:
  388. return
  389. self._callbacks.setdefault(key, set()).add(callback)
  390. def get_invalidation_callbacks(self, key: KT) -> Collection[Callable[[], None]]:
  391. return self._callbacks.get(key, set()) | self._global_callbacks
  392. def add_global_invalidation_callback(
  393. self, callback: Optional[Callable[[], None]]
  394. ) -> None:
  395. """Add a callback for when any keys get invalidated."""
  396. if callback is None:
  397. return
  398. self._global_callbacks.add(callback)
  399. def complete_bulk(
  400. self,
  401. cache: DeferredCache[KT, VT],
  402. result: Dict[KT, VT],
  403. ) -> None:
  404. """Called when there is a result"""
  405. for key, value in result.items():
  406. cache._completed_callback(value, self, key)
  407. if self._deferred:
  408. self._deferred.callback(result)
  409. def error_bulk(
  410. self, cache: DeferredCache[KT, VT], keys: Collection[KT], failure: Failure
  411. ) -> None:
  412. """Called when bulk lookup failed."""
  413. for key in keys:
  414. cache._error_callback(failure, self, key)
  415. if self._deferred:
  416. self._deferred.errback(failure)