deferred_cache.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  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 enum
  17. import threading
  18. from typing import (
  19. Callable,
  20. Generic,
  21. Iterable,
  22. MutableMapping,
  23. Optional,
  24. TypeVar,
  25. Union,
  26. cast,
  27. )
  28. from prometheus_client import Gauge
  29. from twisted.internet import defer
  30. from twisted.python import failure
  31. from twisted.python.failure import Failure
  32. from synapse.util.async_helpers import ObservableDeferred
  33. from synapse.util.caches.lrucache import LruCache
  34. from synapse.util.caches.treecache import TreeCache, iterate_tree_cache_entry
  35. cache_pending_metric = Gauge(
  36. "synapse_util_caches_cache_pending",
  37. "Number of lookups currently pending for this cache",
  38. ["name"],
  39. )
  40. T = TypeVar("T")
  41. KT = TypeVar("KT")
  42. VT = TypeVar("VT")
  43. class _Sentinel(enum.Enum):
  44. # defining a sentinel in this way allows mypy to correctly handle the
  45. # type of a dictionary lookup.
  46. sentinel = object()
  47. class DeferredCache(Generic[KT, VT]):
  48. """Wraps an LruCache, adding support for Deferred results.
  49. It expects that each entry added with set() will be a Deferred; likewise get()
  50. will return a Deferred.
  51. """
  52. __slots__ = (
  53. "cache",
  54. "thread",
  55. "_pending_deferred_cache",
  56. )
  57. def __init__(
  58. self,
  59. name: str,
  60. max_entries: int = 1000,
  61. tree: bool = False,
  62. iterable: bool = False,
  63. apply_cache_factor_from_config: bool = True,
  64. prune_unread_entries: bool = True,
  65. ):
  66. """
  67. Args:
  68. name: The name of the cache
  69. max_entries: Maximum amount of entries that the cache will hold
  70. keylen: The length of the tuple used as the cache key. Ignored unless
  71. `tree` is True.
  72. tree: Use a TreeCache instead of a dict as the underlying cache type
  73. iterable: If True, count each item in the cached object as an entry,
  74. rather than each cached object
  75. apply_cache_factor_from_config: Whether cache factors specified in the
  76. config file affect `max_entries`
  77. """
  78. cache_type = TreeCache if tree else dict
  79. # _pending_deferred_cache maps from the key value to a `CacheEntry` object.
  80. self._pending_deferred_cache: Union[
  81. TreeCache, "MutableMapping[KT, CacheEntry]"
  82. ] = cache_type()
  83. def metrics_cb() -> None:
  84. cache_pending_metric.labels(name).set(len(self._pending_deferred_cache))
  85. # cache is used for completed results and maps to the result itself, rather than
  86. # a Deferred.
  87. self.cache: LruCache[KT, VT] = LruCache(
  88. max_size=max_entries,
  89. cache_name=name,
  90. cache_type=cache_type,
  91. size_callback=(lambda d: len(d) or 1) if iterable else None,
  92. metrics_collection_callback=metrics_cb,
  93. apply_cache_factor_from_config=apply_cache_factor_from_config,
  94. prune_unread_entries=prune_unread_entries,
  95. )
  96. self.thread: Optional[threading.Thread] = None
  97. @property
  98. def max_entries(self) -> int:
  99. return self.cache.max_size
  100. def check_thread(self) -> None:
  101. expected_thread = self.thread
  102. if expected_thread is None:
  103. self.thread = threading.current_thread()
  104. else:
  105. if expected_thread is not threading.current_thread():
  106. raise ValueError(
  107. "Cache objects can only be accessed from the main thread"
  108. )
  109. def get(
  110. self,
  111. key: KT,
  112. callback: Optional[Callable[[], None]] = None,
  113. update_metrics: bool = True,
  114. ) -> defer.Deferred:
  115. """Looks the key up in the caches.
  116. For symmetry with set(), this method does *not* follow the synapse logcontext
  117. rules: the logcontext will not be cleared on return, and the Deferred will run
  118. its callbacks in the sentinel context. In other words: wrap the result with
  119. make_deferred_yieldable() before `await`ing it.
  120. Args:
  121. key:
  122. callback: Gets called when the entry in the cache is invalidated
  123. update_metrics (bool): whether to update the cache hit rate metrics
  124. Returns:
  125. A Deferred which completes with the result. Note that this may later fail
  126. if there is an ongoing set() operation which later completes with a failure.
  127. Raises:
  128. KeyError if the key is not found in the cache
  129. """
  130. callbacks = [callback] if callback else []
  131. val = self._pending_deferred_cache.get(key, _Sentinel.sentinel)
  132. if val is not _Sentinel.sentinel:
  133. val.callbacks.update(callbacks)
  134. if update_metrics:
  135. m = self.cache.metrics
  136. assert m # we always have a name, so should always have metrics
  137. m.inc_hits()
  138. return val.deferred.observe()
  139. val2 = self.cache.get(
  140. key, _Sentinel.sentinel, callbacks=callbacks, update_metrics=update_metrics
  141. )
  142. if val2 is _Sentinel.sentinel:
  143. raise KeyError()
  144. else:
  145. return defer.succeed(val2)
  146. def get_immediate(
  147. self, key: KT, default: T, update_metrics: bool = True
  148. ) -> Union[VT, T]:
  149. """If we have a *completed* cached value, return it."""
  150. return self.cache.get(key, default, update_metrics=update_metrics)
  151. def set(
  152. self,
  153. key: KT,
  154. value: "defer.Deferred[VT]",
  155. callback: Optional[Callable[[], None]] = None,
  156. ) -> defer.Deferred:
  157. """Adds a new entry to the cache (or updates an existing one).
  158. The given `value` *must* be a Deferred.
  159. First any existing entry for the same key is invalidated. Then a new entry
  160. is added to the cache for the given key.
  161. Until the `value` completes, calls to `get()` for the key will also result in an
  162. incomplete Deferred, which will ultimately complete with the same result as
  163. `value`.
  164. If `value` completes successfully, subsequent calls to `get()` will then return
  165. a completed deferred with the same result. If it *fails*, the cache is
  166. invalidated and subequent calls to `get()` will raise a KeyError.
  167. If another call to `set()` happens before `value` completes, then (a) any
  168. invalidation callbacks registered in the interim will be called, (b) any
  169. `get()`s in the interim will continue to complete with the result from the
  170. *original* `value`, (c) any future calls to `get()` will complete with the
  171. result from the *new* `value`.
  172. It is expected that `value` does *not* follow the synapse logcontext rules - ie,
  173. if it is incomplete, it runs its callbacks in the sentinel context.
  174. Args:
  175. key: Key to be set
  176. value: a deferred which will complete with a result to add to the cache
  177. callback: An optional callback to be called when the entry is invalidated
  178. """
  179. if not isinstance(value, defer.Deferred):
  180. raise TypeError("not a Deferred")
  181. callbacks = [callback] if callback else []
  182. self.check_thread()
  183. existing_entry = self._pending_deferred_cache.pop(key, None)
  184. if existing_entry:
  185. existing_entry.invalidate()
  186. # XXX: why don't we invalidate the entry in `self.cache` yet?
  187. # we can save a whole load of effort if the deferred is ready.
  188. if value.called:
  189. result = value.result
  190. if not isinstance(result, failure.Failure):
  191. self.cache.set(key, cast(VT, result), callbacks)
  192. return value
  193. # otherwise, we'll add an entry to the _pending_deferred_cache for now,
  194. # and add callbacks to add it to the cache properly later.
  195. observable = ObservableDeferred(value, consumeErrors=True)
  196. observer = observable.observe()
  197. entry = CacheEntry(deferred=observable, callbacks=callbacks)
  198. self._pending_deferred_cache[key] = entry
  199. def compare_and_pop() -> bool:
  200. """Check if our entry is still the one in _pending_deferred_cache, and
  201. if so, pop it.
  202. Returns true if the entries matched.
  203. """
  204. existing_entry = self._pending_deferred_cache.pop(key, None)
  205. if existing_entry is entry:
  206. return True
  207. # oops, the _pending_deferred_cache has been updated since
  208. # we started our query, so we are out of date.
  209. #
  210. # Better put back whatever we took out. (We do it this way
  211. # round, rather than peeking into the _pending_deferred_cache
  212. # and then removing on a match, to make the common case faster)
  213. if existing_entry is not None:
  214. self._pending_deferred_cache[key] = existing_entry
  215. return False
  216. def cb(result: VT) -> None:
  217. if compare_and_pop():
  218. self.cache.set(key, result, entry.callbacks)
  219. else:
  220. # we're not going to put this entry into the cache, so need
  221. # to make sure that the invalidation callbacks are called.
  222. # That was probably done when _pending_deferred_cache was
  223. # updated, but it's possible that `set` was called without
  224. # `invalidate` being previously called, in which case it may
  225. # not have been. Either way, let's double-check now.
  226. entry.invalidate()
  227. def eb(_fail: Failure) -> None:
  228. compare_and_pop()
  229. entry.invalidate()
  230. # once the deferred completes, we can move the entry from the
  231. # _pending_deferred_cache to the real cache.
  232. #
  233. observer.addCallbacks(cb, eb)
  234. # we return a new Deferred which will be called before any subsequent observers.
  235. return observable.observe()
  236. def prefill(
  237. self, key: KT, value: VT, callback: Optional[Callable[[], None]] = None
  238. ) -> None:
  239. callbacks = [callback] if callback else []
  240. self.cache.set(key, value, callbacks=callbacks)
  241. def invalidate(self, key: KT) -> None:
  242. """Delete a key, or tree of entries
  243. If the cache is backed by a regular dict, then "key" must be of
  244. the right type for this cache
  245. If the cache is backed by a TreeCache, then "key" must be a tuple, but
  246. may be of lower cardinality than the TreeCache - in which case the whole
  247. subtree is deleted.
  248. """
  249. self.check_thread()
  250. self.cache.del_multi(key)
  251. # if we have a pending lookup for this key, remove it from the
  252. # _pending_deferred_cache, which will (a) stop it being returned
  253. # for future queries and (b) stop it being persisted as a proper entry
  254. # in self.cache.
  255. entry = self._pending_deferred_cache.pop(key, None)
  256. # run the invalidation callbacks now, rather than waiting for the
  257. # deferred to resolve.
  258. if entry:
  259. # _pending_deferred_cache.pop should either return a CacheEntry, or, in the
  260. # case of a TreeCache, a dict of keys to cache entries. Either way calling
  261. # iterate_tree_cache_entry on it will do the right thing.
  262. for entry in iterate_tree_cache_entry(entry):
  263. entry.invalidate()
  264. def invalidate_all(self) -> None:
  265. self.check_thread()
  266. self.cache.clear()
  267. for entry in self._pending_deferred_cache.values():
  268. entry.invalidate()
  269. self._pending_deferred_cache.clear()
  270. class CacheEntry:
  271. __slots__ = ["deferred", "callbacks", "invalidated"]
  272. def __init__(
  273. self, deferred: ObservableDeferred, callbacks: Iterable[Callable[[], None]]
  274. ):
  275. self.deferred = deferred
  276. self.callbacks = set(callbacks)
  277. self.invalidated = False
  278. def invalidate(self) -> None:
  279. if not self.invalidated:
  280. self.invalidated = True
  281. for callback in self.callbacks:
  282. callback()
  283. self.callbacks.clear()