~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/lru_cache.py

  • Committer: John Arbash Meinel
  • Date: 2011-04-20 15:06:17 UTC
  • mto: This revision was merged to the branch mainline in revision 5836.
  • Revision ID: john@arbash-meinel.com-20110420150617-i41caxgemg32tq1r
Start adding tests that _worth_saving_limit works as expected.

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
 
 
21
19
from bzrlib import (
22
 
    symbol_versioning,
23
20
    trace,
24
21
    )
25
22
 
28
25
class _LRUNode(object):
29
26
    """This maintains the linked-list which is the lru internals."""
30
27
 
31
 
    __slots__ = ('prev', 'next_key', 'key', 'value')
 
28
    __slots__ = ('prev', 'next_key', 'key', 'value', 'cleanup', 'size')
32
29
 
33
 
    def __init__(self, key, value):
 
30
    def __init__(self, key, value, cleanup=None):
34
31
        self.prev = None
35
32
        self.next_key = _null_key
36
33
        self.key = key
37
34
        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
38
40
 
39
41
    def __repr__(self):
40
42
        if self.prev is None:
44
46
        return '%s(%r n:%r p:%r)' % (self.__class__.__name__, self.key,
45
47
                                     self.next_key, prev_key)
46
48
 
 
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
 
47
59
 
48
60
class LRUCache(object):
49
61
    """A class which manages a cache of entries, removing unused ones."""
93
105
    def __len__(self):
94
106
        return len(self._cache)
95
107
 
96
 
    @symbol_versioning.deprecated_method(
97
 
        symbol_versioning.deprecated_in((2, 5, 0)))
 
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
 
98
139
    def add(self, key, value, cleanup=None):
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"""
 
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
        """
105
150
        if key is _null_key:
106
151
            raise ValueError('cannot use _null_key as a key')
107
152
        if key in self._cache:
108
153
            node = self._cache[key]
109
 
            node.value = value
110
 
            self._record_access(node)
 
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)
111
162
        else:
112
 
            node = _LRUNode(key, value)
 
163
            node = _LRUNode(key, value, cleanup=cleanup)
113
164
            self._cache[key] = node
114
165
            self._record_access(node)
115
166
 
139
190
        """
140
191
        return self._cache.keys()
141
192
 
142
 
    def as_dict(self):
143
 
        """Get a new dict with the same key:value pairs as the cache"""
 
193
    def items(self):
 
194
        """Get the key:value pairs as a dict."""
144
195
        return dict((k, n.value) for k, n in self._cache.iteritems())
145
196
 
146
 
    items = symbol_versioning.deprecated_method(
147
 
        symbol_versioning.deprecated_in((2, 5, 0)))(as_dict)
148
 
 
149
197
    def cleanup(self):
150
198
        """Clear the cache until it shrinks to the requested size.
151
199
 
156
204
        while len(self._cache) > self._after_cleanup_count:
157
205
            self._remove_lru()
158
206
 
 
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
 
159
211
    def _record_access(self, node):
160
212
        """Record that key was accessed."""
161
213
        # Move 'node' to the front of the queue
189
241
        # If we have removed all entries, remove the head pointer as well
190
242
        if self._least_recently_used is None:
191
243
            self._most_recently_used = None
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
 
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
200
257
 
201
258
    def _remove_lru(self):
202
259
        """Remove one entry from the lru, and handle consequences.
259
316
        self._update_max_size(max_size, after_cleanup_size=after_cleanup_size)
260
317
        LRUCache.__init__(self, max_cache=max(int(max_size/512), 1))
261
318
 
262
 
    def __setitem__(self, key, value):
263
 
        """Add a new value to the cache"""
 
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
        """
264
330
        if key is _null_key:
265
331
            raise ValueError('cannot use _null_key as a key')
266
332
        node = self._cache.get(key, None)
275
341
            if node is not None:
276
342
                # We won't be replacing the old node, so just remove it
277
343
                self._remove_node(node)
 
344
            if cleanup is not None:
 
345
                cleanup(key, value)
278
346
            return
279
347
        if node is None:
280
 
            node = _LRUNode(key, value)
 
348
            node = _LRUNode(key, value, cleanup=cleanup)
281
349
            self._cache[key] = node
282
350
        else:
283
 
            self._value_size -= self._compute_size(node.value)
 
351
            self._value_size -= node.size
 
352
        node.size = value_len
284
353
        self._value_size += value_len
285
354
        self._record_access(node)
286
355
 
299
368
            self._remove_lru()
300
369
 
301
370
    def _remove_node(self, node):
302
 
        self._value_size -= self._compute_size(node.value)
 
371
        self._value_size -= node.size
303
372
        LRUCache._remove_node(self, node)
304
373
 
305
374
    def resize(self, max_size, after_cleanup_size=None):