##// 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 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 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 34 log = logging.getLogger(__name__)
26 35
27 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 40 def configure_dogpile_cache(settings):
32 41 cache_dir = settings.get('cache_dir')
33 42 if cache_dir:
34 43 region_meta.dogpile_config_defaults['cache_dir'] = cache_dir
35 44
36 45 rc_cache_data = get_default_cache_settings(settings, prefixes=['rc_cache.'])
37 46
38 47 # inspect available namespaces
39 48 avail_regions = set()
40 49 for key in rc_cache_data.keys():
41 50 namespace_name = key.split('.', 1)[0]
42 51 avail_regions.add(namespace_name)
43 52 log.debug('dogpile: found following cache regions: %s', avail_regions)
44 53
45 54 # register them into namespace
46 55 for region_name in avail_regions:
47 56 new_region = make_region(
48 57 name=region_name,
49 58 function_key_generator=key_generator
50 59 )
51 60
52 61 new_region.configure_from_config(settings, 'rc_cache.{}.'.format(region_name))
53 62
54 63 log.debug('dogpile: registering a new region %s[%s]',
55 64 region_name, new_region.__dict__)
56 65 region_meta.dogpile_cache_regions[region_name] = new_region
57 66
58 67
59 68 def includeme(config):
60 69 configure_dogpile_cache(config.registry.settings)
@@ -1,51 +1,179 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 import time
19 import errno
18 20 import logging
19 21
20 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 28 from vcsserver.lib.memory_lru_dict import LRUDict, LRUDictDebug
22 29
23 30
24 31 _default_max_size = 1024
25 32
26 33 log = logging.getLogger(__name__)
27 34
28 35
29 36 class LRUMemoryBackend(memory_backend.MemoryBackend):
30 37 pickle_values = False
31 38
32 39 def __init__(self, arguments):
33 40 max_size = arguments.pop('max_size', _default_max_size)
34 41
35 42 LRUDictClass = LRUDict
36 43 if arguments.pop('log_key_count', None):
37 44 LRUDictClass = LRUDictDebug
38 45
39 46 arguments['cache_dict'] = LRUDictClass(max_size)
40 47 super(LRUMemoryBackend, self).__init__(arguments)
41 48
42 49 def delete(self, key):
43 50 try:
44 51 del self._cache[key]
45 52 except KeyError:
46 53 # we don't care if key isn't there at deletion
47 54 pass
48 55
49 56 def delete_multi(self, keys):
50 57 for key in keys:
51 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 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 from vcsserver.utils import safe_str, sha1
23 22 from dogpile.cache import CacheRegion
24 23 from dogpile.cache.util import compat
25 24
25 from vcsserver.utils import safe_str, sha1
26
27
26 28 log = logging.getLogger(__name__)
27 29
28 30
29 31 class RhodeCodeCacheRegion(CacheRegion):
30 32
31 33 def conditional_cache_on_arguments(
32 34 self, namespace=None,
33 35 expiration_time=None,
34 36 should_cache_fn=None,
35 37 to_str=compat.string_type,
36 38 function_key_generator=None,
37 39 condition=True):
38 40 """
39 41 Custom conditional decorator, that will not touch any dogpile internals if
40 42 condition isn't meet. This works a bit different than should_cache_fn
41 43 And it's faster in cases we don't ever want to compute cached values
42 44 """
43 45 expiration_time_is_callable = compat.callable(expiration_time)
44 46
45 47 if function_key_generator is None:
46 48 function_key_generator = self.function_key_generator
47 49
48 50 def decorator(fn):
49 51 if to_str is compat.string_type:
50 52 # backwards compatible
51 53 key_generator = function_key_generator(namespace, fn)
52 54 else:
53 55 key_generator = function_key_generator(namespace, fn, to_str=to_str)
54 56
55 57 @functools.wraps(fn)
56 58 def decorate(*arg, **kw):
57 59 key = key_generator(*arg, **kw)
58 60
59 61 @functools.wraps(fn)
60 62 def creator():
61 63 return fn(*arg, **kw)
62 64
63 65 if not condition:
64 66 return creator()
65 67
66 68 timeout = expiration_time() if expiration_time_is_callable \
67 69 else expiration_time
68 70
69 71 return self.get_or_create(key, creator, timeout, should_cache_fn)
70 72
71 73 def invalidate(*arg, **kw):
72 74 key = key_generator(*arg, **kw)
73 75 self.delete(key)
74 76
75 77 def set_(value, *arg, **kw):
76 78 key = key_generator(*arg, **kw)
77 79 self.set(key, value)
78 80
79 81 def get(*arg, **kw):
80 82 key = key_generator(*arg, **kw)
81 83 return self.get(key)
82 84
83 85 def refresh(*arg, **kw):
84 86 key = key_generator(*arg, **kw)
85 87 value = fn(*arg, **kw)
86 88 self.set(key, value)
87 89 return value
88 90
89 91 decorate.set = set_
90 92 decorate.invalidate = invalidate
91 93 decorate.refresh = refresh
92 94 decorate.get = get
93 95 decorate.original = fn
94 96 decorate.key_generator = key_generator
97 decorate.__wrapped__ = fn
95 98
96 99 return decorate
97 100
98 101 return decorator
99 102
100 103
101 104 def make_region(*arg, **kw):
102 105 return RhodeCodeCacheRegion(*arg, **kw)
103 106
104 107
105 108 def get_default_cache_settings(settings, prefixes=None):
106 109 prefixes = prefixes or []
107 110 cache_settings = {}
108 111 for key in settings.keys():
109 112 for prefix in prefixes:
110 113 if key.startswith(prefix):
111 114 name = key.split(prefix)[1].strip()
112 115 val = settings[key]
113 if isinstance(val, basestring):
116 if isinstance(val, compat.string_types):
114 117 val = val.strip()
115 118 cache_settings[name] = val
116 119 return cache_settings
117 120
118 121
119 122 def compute_key_from_params(*args):
120 123 """
121 124 Helper to compute key from given params to be used in cache manager
122 125 """
123 126 return sha1("_".join(map(safe_str, args)))
124 127
125 128
126 129 def key_generator(namespace, fn):
127 130 fname = fn.__name__
128 131
129 132 def generate_key(*args):
130 133 namespace_pref = namespace or 'default'
131 134 arg_key = compute_key_from_params(*args)
132 135 final_key = "{}:{}_{}".format(namespace_pref, fname, arg_key)
133 136
134 137 return final_key
135 138
136 139 return generate_key
General Comments 0
You need to be logged in to leave comments. Login now