25
26
class _LRUNode(object):
26
27
"""This maintains the linked-list which is the lru internals."""
28
__slots__ = ('prev', 'next', 'key', 'value', 'cleanup', 'size')
29
__slots__ = ('prev', 'next_key', 'key', 'value', 'cleanup', 'size')
30
31
def __init__(self, key, value, cleanup=None):
33
self.next_key = _null_key
35
36
self.cleanup = cleanup
73
70
# The "HEAD" of the lru linked list
74
71
self._most_recently_used = None
75
72
# The "TAIL" of the lru linked list
76
self._last_recently_used = None
73
self._least_recently_used = None
77
74
self._update_max_cache(max_cache, after_cleanup_count)
79
76
def __contains__(self, key):
80
77
return key in self._cache
82
79
def __getitem__(self, key):
83
node = self._cache[key]
84
82
# Inlined from _record_access to decrease the overhead of __getitem__
85
83
# We also have more knowledge about structure if __getitem__ is
86
84
# succeeding, then we know that self._most_recently_used must not be
90
88
# Nothing to do, this node is already at the head of the queue
92
elif node is self._last_recently_used:
93
self._last_recently_used = node.prev
94
90
# Remove this node from the old location
95
91
node_prev = node.prev
97
if node_prev is not None:
98
node_prev.next = node_next
99
if node_next is not None:
92
next_key = node.next_key
93
# benchmarking shows that the lookup of _null_key in globals is faster
94
# than the attribute lookup for (node is self._least_recently_used)
95
if next_key is _null_key:
96
# 'node' is the _least_recently_used, because it doesn't have a
97
# 'next' item. So move the current lru to the previous node.
98
self._least_recently_used = node_prev
100
node_next = cache[next_key]
100
101
node_next.prev = node_prev
101
# Insert this node to the front of the list
102
node_prev.next_key = next_key
103
# Insert this node at the front of the list
104
node.next_key = mru.key
104
106
self._most_recently_used = node
117
119
' supposed to have a previous entry'
119
121
while node is not None:
120
if node.next is None:
121
if node is not self._last_recently_used:
122
if node.next_key is _null_key:
123
if node is not self._least_recently_used:
122
124
raise AssertionError('only the last node should have'
123
125
' no next value: %s' % (node,))
125
if node.next.prev is not node:
128
node_next = self._cache[node.next_key]
129
if node_next.prev is not node:
126
130
raise AssertionError('inconsistency found, node.next.prev'
127
131
' != node: %s' % (node,))
128
132
if node.prev is None:
131
135
' not have a previous node: %s'
134
if node.prev.next is not node:
138
if node.prev.next_key != node.key:
135
139
raise AssertionError('inconsistency found, node.prev.next'
136
140
' != node: %s' % (node,))
140
144
def add(self, key, value, cleanup=None):
141
145
"""Add a new value to the cache.
207
213
# Move 'node' to the front of the queue
208
214
if self._most_recently_used is None:
209
215
self._most_recently_used = node
210
self._last_recently_used = node
216
self._least_recently_used = node
212
218
elif node is self._most_recently_used:
213
219
# Nothing to do, this node is already at the head of the queue
215
elif node is self._last_recently_used:
216
self._last_recently_used = node.prev
217
221
# We've taken care of the tail pointer, remove the node, and insert it
224
if node is self._least_recently_used:
225
self._least_recently_used = node.prev
220
226
if node.prev is not None:
221
node.prev.next = node.next
222
if node.next is not None:
223
node.next.prev = node.prev
227
node.prev.next_key = node.next_key
228
if node.next_key is not _null_key:
229
node_next = self._cache[node.next_key]
230
node_next.prev = node.prev
225
node.next = self._most_recently_used
232
node.next_key = self._most_recently_used.key
233
self._most_recently_used.prev = node
226
234
self._most_recently_used = node
227
node.next.prev = node
230
237
def _remove_node(self, node):
231
if node is self._last_recently_used:
232
self._last_recently_used = node.prev
238
if node is self._least_recently_used:
239
self._least_recently_used = node.prev
233
240
self._cache.pop(node.key)
234
241
# If we have removed all entries, remove the head pointer as well
235
if self._last_recently_used is None:
242
if self._least_recently_used is None:
236
243
self._most_recently_used = None
237
244
node.run_cleanup()
238
245
# Now remove this node from the linked list
239
246
if node.prev is not None:
240
node.prev.next = node.next
241
if node.next is not None:
242
node.next.prev = node.prev
247
node.prev.next_key = node.next_key
248
if node.next_key is not _null_key:
249
node_next = self._cache[node.next_key]
250
node_next.prev = node.prev
243
251
# And remove this node's pointers
253
node.next_key = _null_key
247
255
def _remove_lru(self):
248
256
"""Remove one entry from the lru, and handle consequences.
316
324
:param cleanup: None or a function taking (key, value) to indicate
317
325
'value' should be cleaned up.
328
raise ValueError('cannot use _null_key as a key')
319
329
node = self._cache.get(key, None)
320
330
value_len = self._compute_size(value)
321
331
if value_len >= self._after_cleanup_size: