utils.py
207 lines
| 7.3 KiB
| text/x-python
|
PythonLexer
r733 | # RhodeCode VCSServer provides access to different vcs backends via network. | |||
r850 | # Copyright (C) 2014-2020 RhodeCode GmbH | |||
r733 | # | |||
# This program is free software; you can redistribute it and/or modify | ||||
# it under the terms of the GNU General Public License as published by | ||||
# the Free Software Foundation; either version 3 of the License, or | ||||
# (at your option) any later version. | ||||
# | ||||
# This program is distributed in the hope that it will be useful, | ||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
# GNU General Public License for more details. | ||||
# | ||||
# You should have received a copy of the GNU General Public License | ||||
# along with this program; if not, write to the Free Software Foundation, | ||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||||
import os | ||||
r961 | import time | |||
r733 | import logging | |||
import functools | ||||
r1045 | import decorator | |||
r733 | ||||
from dogpile.cache import CacheRegion | ||||
r1060 | from vcsserver.str_utils import safe_bytes | |||
from vcsserver.utils import sha1 | ||||
r961 | from vcsserver.lib.rc_cache import region_meta | |||
r733 | ||||
log = logging.getLogger(__name__) | ||||
class RhodeCodeCacheRegion(CacheRegion): | ||||
def conditional_cache_on_arguments( | ||||
self, namespace=None, | ||||
expiration_time=None, | ||||
should_cache_fn=None, | ||||
r1042 | to_str=str, | |||
r733 | function_key_generator=None, | |||
condition=True): | ||||
""" | ||||
Custom conditional decorator, that will not touch any dogpile internals if | ||||
condition isn't meet. This works a bit different than should_cache_fn | ||||
And it's faster in cases we don't ever want to compute cached values | ||||
""" | ||||
r1042 | expiration_time_is_callable = callable(expiration_time) | |||
r733 | ||||
if function_key_generator is None: | ||||
function_key_generator = self.function_key_generator | ||||
r751 | def get_or_create_for_user_func(key_generator, user_func, *arg, **kw): | |||
if not condition: | ||||
r1044 | log.debug('Calling un-cached method:%s', user_func.__name__) | |||
r958 | start = time.time() | |||
result = user_func(*arg, **kw) | ||||
total = time.time() - start | ||||
r1044 | log.debug('un-cached method:%s took %.4fs', user_func.__name__, total) | |||
r958 | return result | |||
r751 | ||||
key = key_generator(*arg, **kw) | ||||
timeout = expiration_time() if expiration_time_is_callable \ | ||||
else expiration_time | ||||
r1044 | log.debug('Calling cached method:`%s`', user_func.__name__) | |||
r751 | return self.get_or_create(key, user_func, timeout, should_cache_fn, (arg, kw)) | |||
def cache_decorator(user_func): | ||||
r1042 | if to_str is str: | |||
r733 | # backwards compatible | |||
r751 | key_generator = function_key_generator(namespace, user_func) | |||
r733 | else: | |||
r751 | key_generator = function_key_generator(namespace, user_func, to_str=to_str) | |||
r733 | ||||
r751 | def refresh(*arg, **kw): | |||
""" | ||||
Like invalidate, but regenerates the value instead | ||||
""" | ||||
key = key_generator(*arg, **kw) | ||||
value = user_func(*arg, **kw) | ||||
self.set(key, value) | ||||
return value | ||||
r733 | ||||
def invalidate(*arg, **kw): | ||||
key = key_generator(*arg, **kw) | ||||
self.delete(key) | ||||
def set_(value, *arg, **kw): | ||||
key = key_generator(*arg, **kw) | ||||
self.set(key, value) | ||||
def get(*arg, **kw): | ||||
key = key_generator(*arg, **kw) | ||||
return self.get(key) | ||||
r751 | user_func.set = set_ | |||
user_func.invalidate = invalidate | ||||
user_func.get = get | ||||
user_func.refresh = refresh | ||||
user_func.key_generator = key_generator | ||||
user_func.original = user_func | ||||
r733 | ||||
r751 | # Use `decorate` to preserve the signature of :param:`user_func`. | |||
r961 | return decorator.decorate(user_func, functools.partial( | |||
r751 | get_or_create_for_user_func, key_generator)) | |||
r733 | ||||
r751 | return cache_decorator | |||
r733 | ||||
def make_region(*arg, **kw): | ||||
return RhodeCodeCacheRegion(*arg, **kw) | ||||
def get_default_cache_settings(settings, prefixes=None): | ||||
prefixes = prefixes or [] | ||||
cache_settings = {} | ||||
for key in settings.keys(): | ||||
for prefix in prefixes: | ||||
if key.startswith(prefix): | ||||
name = key.split(prefix)[1].strip() | ||||
val = settings[key] | ||||
r1042 | if isinstance(val, str): | |||
r733 | val = val.strip() | |||
cache_settings[name] = val | ||||
return cache_settings | ||||
def compute_key_from_params(*args): | ||||
""" | ||||
Helper to compute key from given params to be used in cache manager | ||||
""" | ||||
r1048 | return sha1(safe_bytes("_".join(map(str, args)))) | |||
r733 | ||||
r734 | def backend_key_generator(backend): | |||
""" | ||||
Special wrapper that also sends over the backend to the key generator | ||||
""" | ||||
def wrapper(namespace, fn): | ||||
return key_generator(backend, namespace, fn) | ||||
return wrapper | ||||
def key_generator(backend, namespace, fn): | ||||
r733 | fname = fn.__name__ | |||
def generate_key(*args): | ||||
r734 | backend_prefix = getattr(backend, 'key_prefix', None) or 'backend_prefix' | |||
namespace_pref = namespace or 'default_namespace' | ||||
r733 | arg_key = compute_key_from_params(*args) | |||
r734 | final_key = "{}:{}:{}_{}".format(backend_prefix, namespace_pref, fname, arg_key) | |||
r733 | ||||
return final_key | ||||
return generate_key | ||||
r961 | ||||
def get_or_create_region(region_name, region_namespace=None): | ||||
from vcsserver.lib.rc_cache.backends import FileNamespaceBackend | ||||
region_obj = region_meta.dogpile_cache_regions.get(region_name) | ||||
if not region_obj: | ||||
r1074 | reg_keys = list(region_meta.dogpile_cache_regions.keys()) | |||
raise EnvironmentError(f'Region `{region_name}` not in configured: {reg_keys}.') | ||||
r961 | ||||
r1074 | region_uid_name = f'{region_name}:{region_namespace}' | |||
r961 | if isinstance(region_obj.actual_backend, FileNamespaceBackend): | |||
region_exist = region_meta.dogpile_cache_regions.get(region_namespace) | ||||
if region_exist: | ||||
log.debug('Using already configured region: %s', region_namespace) | ||||
return region_exist | ||||
cache_dir = region_meta.dogpile_config_defaults['cache_dir'] | ||||
expiration_time = region_obj.expiration_time | ||||
if not os.path.isdir(cache_dir): | ||||
os.makedirs(cache_dir) | ||||
new_region = make_region( | ||||
name=region_uid_name, | ||||
function_key_generator=backend_key_generator(region_obj.actual_backend) | ||||
) | ||||
namespace_filename = os.path.join( | ||||
r1074 | cache_dir, f"{region_namespace}.cache.dbm") | |||
r961 | # special type that allows 1db per namespace | |||
new_region.configure( | ||||
backend='dogpile.cache.rc.file_namespace', | ||||
expiration_time=expiration_time, | ||||
arguments={"filename": namespace_filename} | ||||
) | ||||
# create and save in region caches | ||||
log.debug('configuring new region: %s', region_uid_name) | ||||
region_obj = region_meta.dogpile_cache_regions[region_namespace] = new_region | ||||
return region_obj | ||||
def clear_cache_namespace(cache_region, cache_namespace_uid, invalidate=False): | ||||
region = get_or_create_region(cache_region, cache_namespace_uid) | ||||
cache_keys = region.backend.list_keys(prefix=cache_namespace_uid) | ||||
num_delete_keys = len(cache_keys) | ||||
if invalidate: | ||||
region.invalidate(hard=False) | ||||
else: | ||||
if num_delete_keys: | ||||
region.delete_multi(cache_keys) | ||||
return num_delete_keys | ||||