No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.
 
 
 
 
 
 

225 líneas
8.1 KiB

  1. # Copyright 2019-2021 Matrix.org Foundation C.I.C.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. import logging
  15. import os
  16. import re
  17. import threading
  18. from typing import Any, Callable, Dict, Mapping, Optional
  19. import attr
  20. from synapse.types import JsonDict
  21. from synapse.util.check_dependencies import check_requirements
  22. from ._base import Config, ConfigError
  23. logger = logging.getLogger(__name__)
  24. # The prefix for all cache factor-related environment variables
  25. _CACHE_PREFIX = "SYNAPSE_CACHE_FACTOR"
  26. # Map from canonicalised cache name to cache.
  27. _CACHES: Dict[str, Callable[[float], None]] = {}
  28. # a lock on the contents of _CACHES
  29. _CACHES_LOCK = threading.Lock()
  30. _DEFAULT_FACTOR_SIZE = 0.5
  31. _DEFAULT_EVENT_CACHE_SIZE = "10K"
  32. @attr.s(slots=True, auto_attribs=True)
  33. class CacheProperties:
  34. # The default factor size for all caches
  35. default_factor_size: float = float(
  36. os.environ.get(_CACHE_PREFIX, _DEFAULT_FACTOR_SIZE)
  37. )
  38. resize_all_caches_func: Optional[Callable[[], None]] = None
  39. properties = CacheProperties()
  40. def _canonicalise_cache_name(cache_name: str) -> str:
  41. """Gets the canonical form of the cache name.
  42. Since we specify cache names in config and environment variables we need to
  43. ignore case and special characters. For example, some caches have asterisks
  44. in their name to denote that they're not attached to a particular database
  45. function, and these asterisks need to be stripped out
  46. """
  47. cache_name = re.sub(r"[^A-Za-z_1-9]", "", cache_name)
  48. return cache_name.lower()
  49. def add_resizable_cache(
  50. cache_name: str, cache_resize_callback: Callable[[float], None]
  51. ) -> None:
  52. """Register a cache whose size can dynamically change
  53. Args:
  54. cache_name: A reference to the cache
  55. cache_resize_callback: A callback function that will run whenever
  56. the cache needs to be resized
  57. """
  58. # Some caches have '*' in them which we strip out.
  59. cache_name = _canonicalise_cache_name(cache_name)
  60. # sometimes caches are initialised from background threads, so we need to make
  61. # sure we don't conflict with another thread running a resize operation
  62. with _CACHES_LOCK:
  63. _CACHES[cache_name] = cache_resize_callback
  64. # Ensure all loaded caches are sized appropriately
  65. #
  66. # This method should only run once the config has been read,
  67. # as it uses values read from it
  68. if properties.resize_all_caches_func:
  69. properties.resize_all_caches_func()
  70. class CacheConfig(Config):
  71. section = "caches"
  72. _environ: Mapping[str, str] = os.environ
  73. event_cache_size: int
  74. cache_factors: Dict[str, float]
  75. global_factor: float
  76. track_memory_usage: bool
  77. expiry_time_msec: Optional[int]
  78. sync_response_cache_duration: int
  79. @staticmethod
  80. def reset() -> None:
  81. """Resets the caches to their defaults. Used for tests."""
  82. properties.default_factor_size = float(
  83. os.environ.get(_CACHE_PREFIX, _DEFAULT_FACTOR_SIZE)
  84. )
  85. properties.resize_all_caches_func = None
  86. with _CACHES_LOCK:
  87. _CACHES.clear()
  88. def read_config(self, config: JsonDict, **kwargs: Any) -> None:
  89. """Populate this config object with values from `config`.
  90. This method does NOT resize existing or future caches: use `resize_all_caches`.
  91. We use two separate methods so that we can reject bad config before applying it.
  92. """
  93. self.event_cache_size = self.parse_size(
  94. config.get("event_cache_size", _DEFAULT_EVENT_CACHE_SIZE)
  95. )
  96. self.cache_factors = {}
  97. cache_config = config.get("caches") or {}
  98. self.global_factor = cache_config.get("global_factor", _DEFAULT_FACTOR_SIZE)
  99. if type(self.global_factor) not in (int, float):
  100. raise ConfigError("caches.global_factor must be a number.")
  101. # Load cache factors from the config
  102. individual_factors = cache_config.get("per_cache_factors") or {}
  103. if not isinstance(individual_factors, dict):
  104. raise ConfigError("caches.per_cache_factors must be a dictionary")
  105. # Canonicalise the cache names *before* updating with the environment
  106. # variables.
  107. individual_factors = {
  108. _canonicalise_cache_name(key): val
  109. for key, val in individual_factors.items()
  110. }
  111. # Override factors from environment if necessary
  112. individual_factors.update(
  113. {
  114. _canonicalise_cache_name(key[len(_CACHE_PREFIX) + 1 :]): float(val)
  115. for key, val in self._environ.items()
  116. if key.startswith(_CACHE_PREFIX + "_")
  117. }
  118. )
  119. for cache, factor in individual_factors.items():
  120. if type(factor) not in (int, float):
  121. raise ConfigError(
  122. "caches.per_cache_factors.%s must be a number" % (cache,)
  123. )
  124. self.cache_factors[cache] = factor
  125. self.track_memory_usage = cache_config.get("track_memory_usage", False)
  126. if self.track_memory_usage:
  127. check_requirements("cache-memory")
  128. expire_caches = cache_config.get("expire_caches", True)
  129. cache_entry_ttl = cache_config.get("cache_entry_ttl", "30m")
  130. if expire_caches:
  131. self.expiry_time_msec = self.parse_duration(cache_entry_ttl)
  132. else:
  133. self.expiry_time_msec = None
  134. # Backwards compatibility support for the now-removed "expiry_time" config flag.
  135. expiry_time = cache_config.get("expiry_time")
  136. if expiry_time and expire_caches:
  137. logger.warning(
  138. "You have set two incompatible options, expiry_time and expire_caches. Please only use the "
  139. "expire_caches and cache_entry_ttl options and delete the expiry_time option as it is "
  140. "deprecated."
  141. )
  142. if expiry_time:
  143. logger.warning(
  144. "Expiry_time is a deprecated option, please use the expire_caches and cache_entry_ttl options "
  145. "instead."
  146. )
  147. self.expiry_time_msec = self.parse_duration(expiry_time)
  148. self.cache_autotuning = cache_config.get("cache_autotuning")
  149. if self.cache_autotuning:
  150. max_memory_usage = self.cache_autotuning.get("max_cache_memory_usage")
  151. self.cache_autotuning["max_cache_memory_usage"] = self.parse_size(
  152. max_memory_usage
  153. )
  154. target_mem_size = self.cache_autotuning.get("target_cache_memory_usage")
  155. self.cache_autotuning["target_cache_memory_usage"] = self.parse_size(
  156. target_mem_size
  157. )
  158. min_cache_ttl = self.cache_autotuning.get("min_cache_ttl")
  159. self.cache_autotuning["min_cache_ttl"] = self.parse_duration(min_cache_ttl)
  160. self.sync_response_cache_duration = self.parse_duration(
  161. cache_config.get("sync_response_cache_duration", "2m")
  162. )
  163. def resize_all_caches(self) -> None:
  164. """Ensure all cache sizes are up-to-date.
  165. For each cache, run the mapped callback function with either
  166. a specific cache factor or the default, global one.
  167. """
  168. # Set the global factor size, so that new caches are appropriately sized.
  169. properties.default_factor_size = self.global_factor
  170. # Store this function so that it can be called from other classes without
  171. # needing an instance of CacheConfig
  172. properties.resize_all_caches_func = self.resize_all_caches
  173. # block other threads from modifying _CACHES while we iterate it.
  174. with _CACHES_LOCK:
  175. for cache_name, callback in _CACHES.items():
  176. new_factor = self.cache_factors.get(cache_name, self.global_factor)
  177. callback(new_factor)