##// END OF EJS Templates
caches: don't use key_manglers instead prefix keys based on backend.
marcink -
r734:2c6e72c0 default
parent child Browse files
Show More
@@ -1,69 +1,68 b''
1 1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 2 # Copyright (C) 2014-2019 RhodeCode GmbH
3 3 #
4 4 # This program is free software; you can redistribute it and/or modify
5 5 # it under the terms of the GNU General Public License as published by
6 6 # the Free Software Foundation; either version 3 of the License, or
7 7 # (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software Foundation,
16 16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17 17
18 18 import logging
19 19 from dogpile.cache import register_backend
20 20
21 21 register_backend(
22 22 "dogpile.cache.rc.memory_lru", "vcsserver.lib.rc_cache.backends",
23 23 "LRUMemoryBackend")
24 24
25 25 register_backend(
26 26 "dogpile.cache.rc.file_namespace", "vcsserver.lib.rc_cache.backends",
27 27 "FileNamespaceBackend")
28 28
29 29 register_backend(
30 30 "dogpile.cache.rc.redis", "vcsserver.lib.rc_cache.backends",
31 31 "RedisPickleBackend")
32 32
33 33
34 34 log = logging.getLogger(__name__)
35 35
36 36 from . import region_meta
37 from .utils import (get_default_cache_settings, key_generator, make_region)
37 from .utils import (get_default_cache_settings, backend_key_generator, make_region)
38 38
39 39
40 40 def configure_dogpile_cache(settings):
41 41 cache_dir = settings.get('cache_dir')
42 42 if cache_dir:
43 43 region_meta.dogpile_config_defaults['cache_dir'] = cache_dir
44 44
45 45 rc_cache_data = get_default_cache_settings(settings, prefixes=['rc_cache.'])
46 46
47 47 # inspect available namespaces
48 48 avail_regions = set()
49 49 for key in rc_cache_data.keys():
50 50 namespace_name = key.split('.', 1)[0]
51 51 avail_regions.add(namespace_name)
52 52 log.debug('dogpile: found following cache regions: %s', avail_regions)
53 53
54 54 # register them into namespace
55 55 for region_name in avail_regions:
56 56 new_region = make_region(
57 57 name=region_name,
58 function_key_generator=key_generator
58 function_key_generator=None
59 59 )
60 60
61 61 new_region.configure_from_config(settings, 'rc_cache.{}.'.format(region_name))
62
63 log.debug('dogpile: registering a new region %s[%s]',
64 region_name, new_region.__dict__)
62 new_region.function_key_generator = backend_key_generator(new_region.actual_backend)
63 log.debug('dogpile: registering a new region %s[%s]', region_name, new_region.__dict__)
65 64 region_meta.dogpile_cache_regions[region_name] = new_region
66 65
67 66
68 67 def includeme(config):
69 68 configure_dogpile_cache(config.registry.settings)
@@ -1,179 +1,231 b''
1 1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 2 # Copyright (C) 2014-2019 RhodeCode GmbH
3 3 #
4 4 # This program is free software; you can redistribute it and/or modify
5 5 # it under the terms of the GNU General Public License as published by
6 6 # the Free Software Foundation; either version 3 of the License, or
7 7 # (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software Foundation,
16 16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17 17
18 18 import time
19 19 import errno
20 20 import logging
21 21
22 import msgpack
23 from dogpile.cache.api import CachedValue
22 24 from dogpile.cache.backends import memory as memory_backend
23 25 from dogpile.cache.backends import file as file_backend
24 26 from dogpile.cache.backends import redis as redis_backend
25 27 from dogpile.cache.backends.file import NO_VALUE, compat, FileLock
26 28 from dogpile.cache.util import memoized_property
27 29
28 30 from vcsserver.lib.memory_lru_dict import LRUDict, LRUDictDebug
29 31
30 32
31 33 _default_max_size = 1024
32 34
33 35 log = logging.getLogger(__name__)
34 36
35 37
36 38 class LRUMemoryBackend(memory_backend.MemoryBackend):
39 key_prefix = 'lru_mem_backend'
37 40 pickle_values = False
38 41
39 42 def __init__(self, arguments):
40 43 max_size = arguments.pop('max_size', _default_max_size)
41 44
42 45 LRUDictClass = LRUDict
43 46 if arguments.pop('log_key_count', None):
44 47 LRUDictClass = LRUDictDebug
45 48
46 49 arguments['cache_dict'] = LRUDictClass(max_size)
47 50 super(LRUMemoryBackend, self).__init__(arguments)
48 51
49 52 def delete(self, key):
50 53 try:
51 54 del self._cache[key]
52 55 except KeyError:
53 56 # we don't care if key isn't there at deletion
54 57 pass
55 58
56 59 def delete_multi(self, keys):
57 60 for key in keys:
58 61 self.delete(key)
59 62
60 63
61 class Serializer(object):
64 class PickleSerializer(object):
65
62 66 def _dumps(self, value, safe=False):
63 67 try:
64 68 return compat.pickle.dumps(value)
65 69 except Exception:
66 70 if safe:
67 71 return NO_VALUE
68 72 else:
69 73 raise
70 74
71 75 def _loads(self, value, safe=True):
72 76 try:
73 77 return compat.pickle.loads(value)
74 78 except Exception:
75 79 if safe:
76 80 return NO_VALUE
77 81 else:
78 82 raise
79 83
80 84
85 class MsgPackSerializer(object):
86
87 def _dumps(self, value, safe=False):
88 try:
89 return msgpack.packb(value)
90 except Exception:
91 if safe:
92 return NO_VALUE
93 else:
94 raise
95
96 def _loads(self, value, safe=True):
97 """
98 pickle maintained the `CachedValue` wrapper of the tuple
99 msgpack does not, so it must be added back in.
100 """
101 try:
102 value = msgpack.unpackb(value, use_list=False)
103 return CachedValue(*value)
104 except Exception:
105 if safe:
106 return NO_VALUE
107 else:
108 raise
109
110
81 111 import fcntl
82 112 flock_org = fcntl.flock
83 113
84 114
85 115 class CustomLockFactory(FileLock):
86 116
87 117 pass
88 118
89 119
90 class FileNamespaceBackend(Serializer, file_backend.DBMBackend):
120 class FileNamespaceBackend(PickleSerializer, file_backend.DBMBackend):
121 key_prefix = 'file_backend'
91 122
92 123 def __init__(self, arguments):
93 124 arguments['lock_factory'] = CustomLockFactory
94 125 super(FileNamespaceBackend, self).__init__(arguments)
95 126
96 127 def list_keys(self, prefix=''):
128 prefix = '{}:{}'.format(self.key_prefix, prefix)
129
97 130 def cond(v):
98 131 if not prefix:
99 132 return True
100 133
101 134 if v.startswith(prefix):
102 135 return True
103 136 return False
104 137
105 138 with self._dbm_file(True) as dbm:
106 139
107 140 return filter(cond, dbm.keys())
108 141
109 142 def get_store(self):
110 143 return self.filename
111 144
112 145 def get(self, key):
113 146 with self._dbm_file(False) as dbm:
114 147 if hasattr(dbm, 'get'):
115 148 value = dbm.get(key, NO_VALUE)
116 149 else:
117 150 # gdbm objects lack a .get method
118 151 try:
119 152 value = dbm[key]
120 153 except KeyError:
121 154 value = NO_VALUE
122 155 if value is not NO_VALUE:
123 156 value = self._loads(value)
124 157 return value
125 158
126 159 def set(self, key, value):
127 160 with self._dbm_file(True) as dbm:
128 161 dbm[key] = self._dumps(value)
129 162
130 163 def set_multi(self, mapping):
131 164 with self._dbm_file(True) as dbm:
132 165 for key, value in mapping.items():
133 166 dbm[key] = self._dumps(value)
134 167
135 168
136 class RedisPickleBackend(Serializer, redis_backend.RedisBackend):
169 class BaseRedisBackend(redis_backend.RedisBackend):
137 170 def list_keys(self, prefix=''):
138 if prefix:
139 prefix = prefix + '*'
171 prefix = '{}:{}*'.format(self.key_prefix, prefix)
140 172 return self.client.keys(prefix)
141 173
142 174 def get_store(self):
143 175 return self.client.connection_pool
144 176
145 177 def get(self, key):
146 178 value = self.client.get(key)
147 179 if value is None:
148 180 return NO_VALUE
149 181 return self._loads(value)
150 182
183 def get_multi(self, keys):
184 if not keys:
185 return []
186 values = self.client.mget(keys)
187 loads = self._loads
188 return [
189 loads(v) if v is not None else NO_VALUE
190 for v in values]
191
151 192 def set(self, key, value):
152 193 if self.redis_expiration_time:
153 194 self.client.setex(key, self.redis_expiration_time,
154 195 self._dumps(value))
155 196 else:
156 197 self.client.set(key, self._dumps(value))
157 198
158 199 def set_multi(self, mapping):
200 dumps = self._dumps
159 201 mapping = dict(
160 (k, self._dumps(v))
202 (k, dumps(v))
161 203 for k, v in mapping.items()
162 204 )
163 205
164 206 if not self.redis_expiration_time:
165 207 self.client.mset(mapping)
166 208 else:
167 209 pipe = self.client.pipeline()
168 210 for key, value in mapping.items():
169 211 pipe.setex(key, self.redis_expiration_time, value)
170 212 pipe.execute()
171 213
172 214 def get_mutex(self, key):
173 215 u = redis_backend.u
174 216 if self.distributed_lock:
175 217 lock_key = u('_lock_{0}').format(key)
176 218 log.debug('Trying to acquire Redis lock for key %s', lock_key)
177 219 return self.client.lock(lock_key, self.lock_timeout, self.lock_sleep)
178 220 else:
179 221 return None
222
223
224 class RedisPickleBackend(PickleSerializer, BaseRedisBackend):
225 key_prefix = 'redis_pickle_backend'
226 pass
227
228
229 class RedisMsgPackBackend(MsgPackSerializer, BaseRedisBackend):
230 key_prefix = 'redis_msgpack_backend'
231 pass
@@ -1,139 +1,149 b''
1 1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 2 # Copyright (C) 2014-2019 RhodeCode GmbH
3 3 #
4 4 # This program is free software; you can redistribute it and/or modify
5 5 # it under the terms of the GNU General Public License as published by
6 6 # the Free Software Foundation; either version 3 of the License, or
7 7 # (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software Foundation,
16 16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17 17
18 18 import os
19 19 import logging
20 20 import functools
21 21
22 22 from dogpile.cache import CacheRegion
23 23 from dogpile.cache.util import compat
24 24
25 25 from vcsserver.utils import safe_str, sha1
26 26
27 27
28 28 log = logging.getLogger(__name__)
29 29
30 30
31 31 class RhodeCodeCacheRegion(CacheRegion):
32 32
33 33 def conditional_cache_on_arguments(
34 34 self, namespace=None,
35 35 expiration_time=None,
36 36 should_cache_fn=None,
37 37 to_str=compat.string_type,
38 38 function_key_generator=None,
39 39 condition=True):
40 40 """
41 41 Custom conditional decorator, that will not touch any dogpile internals if
42 42 condition isn't meet. This works a bit different than should_cache_fn
43 43 And it's faster in cases we don't ever want to compute cached values
44 44 """
45 45 expiration_time_is_callable = compat.callable(expiration_time)
46 46
47 47 if function_key_generator is None:
48 48 function_key_generator = self.function_key_generator
49 49
50 50 def decorator(fn):
51 51 if to_str is compat.string_type:
52 52 # backwards compatible
53 53 key_generator = function_key_generator(namespace, fn)
54 54 else:
55 55 key_generator = function_key_generator(namespace, fn, to_str=to_str)
56 56
57 57 @functools.wraps(fn)
58 58 def decorate(*arg, **kw):
59 59 key = key_generator(*arg, **kw)
60 60
61 61 @functools.wraps(fn)
62 62 def creator():
63 63 return fn(*arg, **kw)
64 64
65 65 if not condition:
66 66 return creator()
67 67
68 68 timeout = expiration_time() if expiration_time_is_callable \
69 69 else expiration_time
70 70
71 71 return self.get_or_create(key, creator, timeout, should_cache_fn)
72 72
73 73 def invalidate(*arg, **kw):
74 74 key = key_generator(*arg, **kw)
75 75 self.delete(key)
76 76
77 77 def set_(value, *arg, **kw):
78 78 key = key_generator(*arg, **kw)
79 79 self.set(key, value)
80 80
81 81 def get(*arg, **kw):
82 82 key = key_generator(*arg, **kw)
83 83 return self.get(key)
84 84
85 85 def refresh(*arg, **kw):
86 86 key = key_generator(*arg, **kw)
87 87 value = fn(*arg, **kw)
88 88 self.set(key, value)
89 89 return value
90 90
91 91 decorate.set = set_
92 92 decorate.invalidate = invalidate
93 93 decorate.refresh = refresh
94 94 decorate.get = get
95 95 decorate.original = fn
96 96 decorate.key_generator = key_generator
97 97 decorate.__wrapped__ = fn
98 98
99 99 return decorate
100 100
101 101 return decorator
102 102
103 103
104 104 def make_region(*arg, **kw):
105 105 return RhodeCodeCacheRegion(*arg, **kw)
106 106
107 107
108 108 def get_default_cache_settings(settings, prefixes=None):
109 109 prefixes = prefixes or []
110 110 cache_settings = {}
111 111 for key in settings.keys():
112 112 for prefix in prefixes:
113 113 if key.startswith(prefix):
114 114 name = key.split(prefix)[1].strip()
115 115 val = settings[key]
116 116 if isinstance(val, compat.string_types):
117 117 val = val.strip()
118 118 cache_settings[name] = val
119 119 return cache_settings
120 120
121 121
122 122 def compute_key_from_params(*args):
123 123 """
124 124 Helper to compute key from given params to be used in cache manager
125 125 """
126 126 return sha1("_".join(map(safe_str, args)))
127 127
128 128
129 def key_generator(namespace, fn):
129 def backend_key_generator(backend):
130 """
131 Special wrapper that also sends over the backend to the key generator
132 """
133 def wrapper(namespace, fn):
134 return key_generator(backend, namespace, fn)
135 return wrapper
136
137
138 def key_generator(backend, namespace, fn):
130 139 fname = fn.__name__
131 140
132 141 def generate_key(*args):
133 namespace_pref = namespace or 'default'
142 backend_prefix = getattr(backend, 'key_prefix', None) or 'backend_prefix'
143 namespace_pref = namespace or 'default_namespace'
134 144 arg_key = compute_key_from_params(*args)
135 final_key = "{}:{}_{}".format(namespace_pref, fname, arg_key)
145 final_key = "{}:{}:{}_{}".format(backend_prefix, namespace_pref, fname, arg_key)
136 146
137 147 return final_key
138 148
139 149 return generate_key
General Comments 0
You need to be logged in to leave comments. Login now