139 lines
4.9 KiB
Python
139 lines
4.9 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Copyright 2019 Matrix.org Foundation C.I.C.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
import os
|
|
from typing import Callable, Dict
|
|
|
|
from ._base import Config, ConfigError
|
|
|
|
# The prefix for all cache factor-related environment variables
|
|
_CACHES = {}
|
|
_CACHE_PREFIX = "SYNAPSE_CACHE_FACTOR"
|
|
_DEFAULT_FACTOR_SIZE = 0.5
|
|
_DEFAULT_EVENT_CACHE_SIZE = "10K"
|
|
|
|
|
|
class CacheProperties(object):
|
|
def __init__(self):
|
|
# The default factor size for all caches
|
|
self.default_factor_size = float(
|
|
os.environ.get(_CACHE_PREFIX, _DEFAULT_FACTOR_SIZE)
|
|
)
|
|
self.resize_all_caches_func = None
|
|
|
|
|
|
properties = CacheProperties()
|
|
|
|
|
|
def add_resizable_cache(cache_name: str, cache_resize_callback: Callable):
|
|
"""Register a cache that's size can dynamically change
|
|
|
|
Args:
|
|
cache_name: A reference to the cache
|
|
cache_resize_callback: A callback function that will be ran whenever
|
|
the cache needs to be resized
|
|
"""
|
|
_CACHES[cache_name.lower()] = cache_resize_callback
|
|
|
|
# Ensure all loaded caches are sized appropriately
|
|
#
|
|
# This method should only run once the config has been read,
|
|
# as it uses values read from it
|
|
if properties.resize_all_caches_func:
|
|
properties.resize_all_caches_func()
|
|
|
|
|
|
class CacheConfig(Config):
|
|
section = "caches"
|
|
_environ = os.environ
|
|
|
|
@staticmethod
|
|
def reset():
|
|
"""Resets the caches to their defaults. Used for tests."""
|
|
properties.default_factor_size = float(
|
|
os.environ.get(_CACHE_PREFIX, _DEFAULT_FACTOR_SIZE)
|
|
)
|
|
properties.resize_all_caches_func = None
|
|
_CACHES.clear()
|
|
|
|
def generate_config_section(self, **kwargs):
|
|
return """\
|
|
# Cache configuration
|
|
#
|
|
# 'global_factor' controls the global cache factor. This overrides the
|
|
# "SYNAPSE_CACHE_FACTOR" environment variable.
|
|
#
|
|
# 'per_cache_factors' is a dictionary of cache name to cache factor for that
|
|
# individual cache.
|
|
#
|
|
#caches:
|
|
# global_factor: 0.5
|
|
# per_cache_factors:
|
|
# get_users_who_share_room_with_user: 2
|
|
#
|
|
"""
|
|
|
|
def read_config(self, config, **kwargs):
|
|
self.event_cache_size = self.parse_size(
|
|
config.get("event_cache_size", _DEFAULT_EVENT_CACHE_SIZE)
|
|
)
|
|
self.cache_factors = {} # type: Dict[str, float]
|
|
|
|
cache_config = config.get("caches", {})
|
|
self.global_factor = cache_config.get(
|
|
"global_factor", properties.default_factor_size
|
|
)
|
|
if not isinstance(self.global_factor, (int, float)):
|
|
raise ConfigError("caches.global_factor must be a number.")
|
|
|
|
# Set the global one so that it's reflected in new caches
|
|
properties.default_factor_size = self.global_factor
|
|
|
|
# Load cache factors from the environment, but override them with the
|
|
# ones in the config file if they exist
|
|
individual_factors = {
|
|
key[len(_CACHE_PREFIX) + 1 :].lower(): float(val)
|
|
for key, val in self._environ.items()
|
|
if key.startswith(_CACHE_PREFIX + "_")
|
|
}
|
|
individual_factors_config = cache_config.get("per_cache_factors", {}) or {}
|
|
if not isinstance(individual_factors_config, dict):
|
|
raise ConfigError("caches.per_cache_factors must be a dictionary")
|
|
individual_factors.update(individual_factors_config)
|
|
|
|
for cache, factor in individual_factors.items():
|
|
if not isinstance(factor, (int, float)):
|
|
raise ConfigError(
|
|
"caches.per_cache_factors.%s must be a number" % (cache.lower(),)
|
|
)
|
|
self.cache_factors[cache.lower()] = factor
|
|
|
|
# Resize all caches (if necessary) with the new factors we've loaded
|
|
self.resize_all_caches()
|
|
|
|
# Store this function so that it can be called from other classes without
|
|
# needing an instance of Config
|
|
properties.resize_all_caches_func = self.resize_all_caches
|
|
|
|
def resize_all_caches(self):
|
|
"""Ensure all cache sizes are up to date
|
|
|
|
For each cache, run the mapped callback function with either
|
|
a specific cache factor or the default, global one.
|
|
"""
|
|
for cache_name, callback in _CACHES.items():
|
|
new_factor = self.cache_factors.get(cache_name, self.global_factor)
|
|
callback(new_factor)
|