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