]> review.fuel-infra Code Review - openstack-build/neutron-build.git/blob - neutron/openstack/common/cache/_backends/memory.py
0dbb3f665ce5ed696a2837c7bd330bff5ad5b8d0
[openstack-build/neutron-build.git] / neutron / openstack / common / cache / _backends / memory.py
1 # Copyright 2013 Red Hat, Inc.
2 #
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
6 #
7 #         http://www.apache.org/licenses/LICENSE-2.0
8 #
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
13 #    under the License.
14
15 import collections
16
17 from oslo_utils import timeutils
18 from oslo_concurrency import lockutils
19
20 from neutron.openstack.common.cache import backends
21
22
23 class MemoryBackend(backends.BaseCache):
24
25     def __init__(self, parsed_url, options=None):
26         super(MemoryBackend, self).__init__(parsed_url, options)
27         self._clear()
28
29     def _set_unlocked(self, key, value, ttl=0):
30         expires_at = 0
31         if ttl != 0:
32             expires_at = timeutils.utcnow_ts() + ttl
33
34         self._cache[key] = (expires_at, value)
35
36         if expires_at:
37             self._keys_expires[expires_at].add(key)
38
39     def _set(self, key, value, ttl=0, not_exists=False):
40         with lockutils.lock(key):
41
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):
45                 return False
46
47             self._set_unlocked(key, value, ttl)
48             return True
49
50     def _get_unlocked(self, key, default=None):
51         now = timeutils.utcnow_ts()
52
53         try:
54             timeout, value = self._cache[key]
55         except KeyError:
56             return (0, default)
57
58         if timeout and now >= timeout:
59
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.
64             try:
65                 del self._cache[key]
66             except KeyError:
67                 pass
68
69             try:
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):
74                 pass
75
76             return (0, default)
77
78         return (timeout, value)
79
80     def _get(self, key, default=None):
81         with lockutils.lock(key):
82             return self._get_unlocked(key, default)[1]
83
84     def _exists_unlocked(self, key):
85         now = timeutils.utcnow_ts()
86         try:
87             timeout = self._cache[key][0]
88             return not timeout or now <= timeout
89         except KeyError:
90             return False
91
92     def __contains__(self, key):
93         with lockutils.lock(key):
94             return self._exists_unlocked(key)
95
96     def _incr_append(self, key, other):
97         with lockutils.lock(key):
98             timeout, value = self._get_unlocked(key)
99
100             if value is None:
101                 return None
102
103             ttl = timeutils.utcnow_ts() - timeout
104             new_value = value + other
105             self._set_unlocked(key, new_value, ttl)
106             return new_value
107
108     def _incr(self, key, delta):
109         if not isinstance(delta, int):
110             raise TypeError('delta must be an int instance')
111
112         return self._incr_append(key, delta)
113
114     def _append_tail(self, key, tail):
115         return self._incr_append(key, tail)
116
117     def _purge_expired(self):
118         """Removes expired keys from the cache."""
119
120         now = timeutils.utcnow_ts()
121         for timeout in sorted(self._keys_expires.keys()):
122
123             # NOTE(flaper87): If timeout is greater
124             # than `now`, stop the iteration, remaining
125             # keys have not expired.
126             if now < timeout:
127                 break
128
129             # NOTE(flaper87): Unset every key in
130             # this set from the cache if its timeout
131             # is equal to `timeout`. (The key might
132             # have been updated)
133             for subkey in self._keys_expires.pop(timeout):
134                 try:
135                     if self._cache[subkey][0] == timeout:
136                         del self._cache[subkey]
137                 except KeyError:
138                     continue
139
140     def __delitem__(self, key):
141         self._purge_expired()
142
143         # NOTE(flaper87): Delete the key. Using pop
144         # since it could have been deleted already
145         value = self._cache.pop(key, None)
146
147         if value:
148             try:
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):
153                 pass
154
155     def _clear(self):
156         self._cache = {}
157         self._keys_expires = collections.defaultdict(set)
158
159     def _get_many(self, keys, default):
160         return super(MemoryBackend, self)._get_many(keys, default)
161
162     def _set_many(self, data, ttl=0):
163         return super(MemoryBackend, self)._set_many(data, ttl)
164
165     def _unset_many(self, keys):
166         return super(MemoryBackend, self)._unset_many(keys)