1 # Copyright 2013 Red Hat, Inc.
3 # Licensed under the Apache License, Version 2.0 (the "License"); you may
4 # not use this file except in compliance with the License. You may obtain
5 # a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 # License for the specific language governing permissions and limitations
17 from oslo_utils import timeutils
18 from oslo_concurrency import lockutils
20 from neutron.openstack.common.cache import backends
23 class MemoryBackend(backends.BaseCache):
25 def __init__(self, parsed_url, options=None):
26 super(MemoryBackend, self).__init__(parsed_url, options)
29 def _set_unlocked(self, key, value, ttl=0):
32 expires_at = timeutils.utcnow_ts() + ttl
34 self._cache[key] = (expires_at, value)
37 self._keys_expires[expires_at].add(key)
39 def _set(self, key, value, ttl=0, not_exists=False):
40 with lockutils.lock(key):
42 # NOTE(flaper87): This is needed just in `set`
43 # calls, hence it's not in `_set_unlocked`
44 if not_exists and self._exists_unlocked(key):
47 self._set_unlocked(key, value, ttl)
50 def _get_unlocked(self, key, default=None):
51 now = timeutils.utcnow_ts()
54 timeout, value = self._cache[key]
58 if timeout and now >= timeout:
60 # NOTE(flaper87): Record expired,
61 # remove it from the cache but catch
62 # KeyError and ValueError in case
63 # _purge_expired removed this key already.
70 # NOTE(flaper87): Keys with ttl == 0
71 # don't exist in the _keys_expires dict
72 self._keys_expires[timeout].remove(key)
73 except (KeyError, ValueError):
78 return (timeout, value)
80 def _get(self, key, default=None):
81 with lockutils.lock(key):
82 return self._get_unlocked(key, default)[1]
84 def _exists_unlocked(self, key):
85 now = timeutils.utcnow_ts()
87 timeout = self._cache[key][0]
88 return not timeout or now <= timeout
92 def __contains__(self, key):
93 with lockutils.lock(key):
94 return self._exists_unlocked(key)
96 def _incr_append(self, key, other):
97 with lockutils.lock(key):
98 timeout, value = self._get_unlocked(key)
103 ttl = timeutils.utcnow_ts() - timeout
104 new_value = value + other
105 self._set_unlocked(key, new_value, ttl)
108 def _incr(self, key, delta):
109 if not isinstance(delta, int):
110 raise TypeError('delta must be an int instance')
112 return self._incr_append(key, delta)
114 def _append_tail(self, key, tail):
115 return self._incr_append(key, tail)
117 def _purge_expired(self):
118 """Removes expired keys from the cache."""
120 now = timeutils.utcnow_ts()
121 for timeout in sorted(self._keys_expires.keys()):
123 # NOTE(flaper87): If timeout is greater
124 # than `now`, stop the iteration, remaining
125 # keys have not expired.
129 # NOTE(flaper87): Unset every key in
130 # this set from the cache if its timeout
131 # is equal to `timeout`. (The key might
133 for subkey in self._keys_expires.pop(timeout):
135 if self._cache[subkey][0] == timeout:
136 del self._cache[subkey]
140 def __delitem__(self, key):
141 self._purge_expired()
143 # NOTE(flaper87): Delete the key. Using pop
144 # since it could have been deleted already
145 value = self._cache.pop(key, None)
149 # NOTE(flaper87): Keys with ttl == 0
150 # don't exist in the _keys_expires dict
151 self._keys_expires[value[0]].remove(key)
152 except (KeyError, ValueError):
157 self._keys_expires = collections.defaultdict(set)
159 def _get_many(self, keys, default):
160 return super(MemoryBackend, self)._get_many(keys, default)
162 def _set_many(self, data, ttl=0):
163 return super(MemoryBackend, self)._set_many(data, ttl)
165 def _unset_many(self, keys):
166 return super(MemoryBackend, self)._unset_many(keys)