~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/lru_cache.py

  • Committer: Kit Randel
  • Date: 2014-12-15 20:24:42 UTC
  • mto: This revision was merged to the branch mainline in revision 6602.
  • Revision ID: kit.randel@canonical.com-20141215202442-usf2ixhypqg8yh6q
added a note for bug-1400567 to the 2.7b release notes

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
 
17
17
"""A simple least-recently-used (LRU) cache."""
18
18
 
 
19
from __future__ import absolute_import
 
20
 
19
21
from bzrlib import (
 
22
    symbol_versioning,
20
23
    trace,
21
24
    )
22
25
 
25
28
class _LRUNode(object):
26
29
    """This maintains the linked-list which is the lru internals."""
27
30
 
28
 
    __slots__ = ('prev', 'next_key', 'key', 'value', 'cleanup', 'size')
 
31
    __slots__ = ('prev', 'next_key', 'key', 'value')
29
32
 
30
 
    def __init__(self, key, value, cleanup=None):
 
33
    def __init__(self, key, value):
31
34
        self.prev = None
32
35
        self.next_key = _null_key
33
36
        self.key = key
34
37
        self.value = value
35
 
        self.cleanup = cleanup
36
 
        # TODO: We could compute this 'on-the-fly' like we used to, and remove
37
 
        #       one pointer from this object, we just need to decide if it
38
 
        #       actually costs us much of anything in normal usage
39
 
        self.size = None
40
38
 
41
39
    def __repr__(self):
42
40
        if self.prev is None:
46
44
        return '%s(%r n:%r p:%r)' % (self.__class__.__name__, self.key,
47
45
                                     self.next_key, prev_key)
48
46
 
49
 
    def run_cleanup(self):
50
 
        try:
51
 
            if self.cleanup is not None:
52
 
                self.cleanup(self.key, self.value)
53
 
        finally:
54
 
            # cleanup might raise an exception, but we want to make sure
55
 
            # to break refcycles, etc
56
 
            self.cleanup = None
57
 
            self.value = None
58
 
 
59
47
 
60
48
class LRUCache(object):
61
49
    """A class which manages a cache of entries, removing unused ones."""
105
93
    def __len__(self):
106
94
        return len(self._cache)
107
95
 
108
 
    def _walk_lru(self):
109
 
        """Walk the LRU list, only meant to be used in tests."""
110
 
        node = self._most_recently_used
111
 
        if node is not None:
112
 
            if node.prev is not None:
113
 
                raise AssertionError('the _most_recently_used entry is not'
114
 
                                     ' supposed to have a previous entry'
115
 
                                     ' %s' % (node,))
116
 
        while node is not None:
117
 
            if node.next_key is _null_key:
118
 
                if node is not self._least_recently_used:
119
 
                    raise AssertionError('only the last node should have'
120
 
                                         ' no next value: %s' % (node,))
121
 
                node_next = None
122
 
            else:
123
 
                node_next = self._cache[node.next_key]
124
 
                if node_next.prev is not node:
125
 
                    raise AssertionError('inconsistency found, node.next.prev'
126
 
                                         ' != node: %s' % (node,))
127
 
            if node.prev is None:
128
 
                if node is not self._most_recently_used:
129
 
                    raise AssertionError('only the _most_recently_used should'
130
 
                                         ' not have a previous node: %s'
131
 
                                         % (node,))
132
 
            else:
133
 
                if node.prev.next_key != node.key:
134
 
                    raise AssertionError('inconsistency found, node.prev.next'
135
 
                                         ' != node: %s' % (node,))
136
 
            yield node
137
 
            node = node_next
138
 
 
 
96
    @symbol_versioning.deprecated_method(
 
97
        symbol_versioning.deprecated_in((2, 5, 0)))
139
98
    def add(self, key, value, cleanup=None):
140
 
        """Add a new value to the cache.
141
 
 
142
 
        Also, if the entry is ever removed from the cache, call
143
 
        cleanup(key, value).
144
 
 
145
 
        :param key: The key to store it under
146
 
        :param value: The object to store
147
 
        :param cleanup: None or a function taking (key, value) to indicate
148
 
                        'value' should be cleaned up.
149
 
        """
 
99
        if cleanup is not None:
 
100
            raise ValueError("Per-node cleanup functions no longer supported")
 
101
        return self.__setitem__(key, value)
 
102
 
 
103
    def __setitem__(self, key, value):
 
104
        """Add a new value to the cache"""
150
105
        if key is _null_key:
151
106
            raise ValueError('cannot use _null_key as a key')
152
107
        if key in self._cache:
153
108
            node = self._cache[key]
154
 
            try:
155
 
                node.run_cleanup()
156
 
            finally:
157
 
                # Maintain the LRU properties, even if cleanup raises an
158
 
                # exception
159
 
                node.value = value
160
 
                node.cleanup = cleanup
161
 
                self._record_access(node)
 
109
            node.value = value
 
110
            self._record_access(node)
162
111
        else:
163
 
            node = _LRUNode(key, value, cleanup=cleanup)
 
112
            node = _LRUNode(key, value)
164
113
            self._cache[key] = node
165
114
            self._record_access(node)
166
115
 
190
139
        """
191
140
        return self._cache.keys()
192
141
 
193
 
    def items(self):
194
 
        """Get the key:value pairs as a dict."""
 
142
    def as_dict(self):
 
143
        """Get a new dict with the same key:value pairs as the cache"""
195
144
        return dict((k, n.value) for k, n in self._cache.iteritems())
196
145
 
 
146
    items = symbol_versioning.deprecated_method(
 
147
        symbol_versioning.deprecated_in((2, 5, 0)))(as_dict)
 
148
 
197
149
    def cleanup(self):
198
150
        """Clear the cache until it shrinks to the requested size.
199
151
 
204
156
        while len(self._cache) > self._after_cleanup_count:
205
157
            self._remove_lru()
206
158
 
207
 
    def __setitem__(self, key, value):
208
 
        """Add a value to the cache, there will be no cleanup function."""
209
 
        self.add(key, value, cleanup=None)
210
 
 
211
159
    def _record_access(self, node):
212
160
        """Record that key was accessed."""
213
161
        # Move 'node' to the front of the queue
241
189
        # If we have removed all entries, remove the head pointer as well
242
190
        if self._least_recently_used is None:
243
191
            self._most_recently_used = None
244
 
        try:
245
 
            node.run_cleanup()
246
 
        finally:
247
 
            # cleanup might raise an exception, but we want to make sure to
248
 
            # maintain the linked list
249
 
            if node.prev is not None:
250
 
                node.prev.next_key = node.next_key
251
 
            if node.next_key is not _null_key:
252
 
                node_next = self._cache[node.next_key]
253
 
                node_next.prev = node.prev
254
 
            # And remove this node's pointers
255
 
            node.prev = None
256
 
            node.next_key = _null_key
 
192
        if node.prev is not None:
 
193
            node.prev.next_key = node.next_key
 
194
        if node.next_key is not _null_key:
 
195
            node_next = self._cache[node.next_key]
 
196
            node_next.prev = node.prev
 
197
        # And remove this node's pointers
 
198
        node.prev = None
 
199
        node.next_key = _null_key
257
200
 
258
201
    def _remove_lru(self):
259
202
        """Remove one entry from the lru, and handle consequences.
316
259
        self._update_max_size(max_size, after_cleanup_size=after_cleanup_size)
317
260
        LRUCache.__init__(self, max_cache=max(int(max_size/512), 1))
318
261
 
319
 
    def add(self, key, value, cleanup=None):
320
 
        """Add a new value to the cache.
321
 
 
322
 
        Also, if the entry is ever removed from the cache, call
323
 
        cleanup(key, value).
324
 
 
325
 
        :param key: The key to store it under
326
 
        :param value: The object to store
327
 
        :param cleanup: None or a function taking (key, value) to indicate
328
 
                        'value' should be cleaned up.
329
 
        """
 
262
    def __setitem__(self, key, value):
 
263
        """Add a new value to the cache"""
330
264
        if key is _null_key:
331
265
            raise ValueError('cannot use _null_key as a key')
332
266
        node = self._cache.get(key, None)
341
275
            if node is not None:
342
276
                # We won't be replacing the old node, so just remove it
343
277
                self._remove_node(node)
344
 
            if cleanup is not None:
345
 
                cleanup(key, value)
346
278
            return
347
279
        if node is None:
348
 
            node = _LRUNode(key, value, cleanup=cleanup)
 
280
            node = _LRUNode(key, value)
349
281
            self._cache[key] = node
350
282
        else:
351
 
            self._value_size -= node.size
352
 
        node.size = value_len
 
283
            self._value_size -= self._compute_size(node.value)
353
284
        self._value_size += value_len
354
285
        self._record_access(node)
355
286
 
368
299
            self._remove_lru()
369
300
 
370
301
    def _remove_node(self, node):
371
 
        self._value_size -= node.size
 
302
        self._value_size -= self._compute_size(node.value)
372
303
        LRUCache._remove_node(self, node)
373
304
 
374
305
    def resize(self, max_size, after_cleanup_size=None):