Merge lp:~jameinel/bzr/1.17-lru-cleanup into lp:~bzr/bzr/trunk-old

Proposed by John A Meinel
Status: Merged
Approved by: Vincent Ladeuil
Approved revision: no longer in the source branch.
Merged at revision: not available
Proposed branch: lp:~jameinel/bzr/1.17-lru-cleanup
Merge into: lp:~bzr/bzr/trunk-old
Diff against target: 168 lines
To merge this branch: bzr merge lp:~jameinel/bzr/1.17-lru-cleanup
Reviewer Review Type Date Requested Status
Vincent Ladeuil Needs Fixing
Review via email: mp+8394@code.launchpad.net
To post a comment you must log in.
Revision history for this message
John A Meinel (jameinel) wrote :

Just a simple cleanup to fix bug #396636

For LRUCache, if you register a cleanup function, that function raising an exception can mess up the linked list.

This just moves the linked list management into a try/finally block.

Revision history for this message
Vincent Ladeuil (vila) wrote :

 review needs_fixing
 merge approved

>>>>> "jam" == John A Meinel <email address hidden> writes:

    jam> class LRUCache(object):
    jam> @@ -156,13 +158,16 @@
    jam> raise ValueError('cannot use _null_key as a key')
    jam> if key in self._cache:
    jam> node = self._cache[key]
    jam> - node.run_cleanup()
    jam> - node.value = value
    jam> - node.cleanup = cleanup
    jam> + try:
    jam> + node.run_cleanup()
    jam> + finally:
    jam> + node.value = value
    jam> + node.cleanup = cleanup
    jam> + self._record_access(node)
    jam> else:
    jam> node = _LRUNode(key, value, cleanup=cleanup)
    jam> self._cache[key] = node
    jam> - self._record_access(node)
    jam> + self._record_access(node)

Don't duplicate that line and add a comment in the finally block,
please.

review: Needs Fixing

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'NEWS'
--- NEWS 2009-07-08 18:05:38 +0000
+++ NEWS 2009-07-08 19:35:13 +0000
@@ -82,12 +82,8 @@
82* Force socket shutdown in threaded http test servers to avoid client hangs82* Force socket shutdown in threaded http test servers to avoid client hangs
83 (pycurl). (Vincent Ladeuil, #383920).83 (pycurl). (Vincent Ladeuil, #383920).
8484
85* The ``log+`` decorator, useful in debugging or profiling, could cause85* ``LRUCache`` will maintain the linked list pointers even if a nodes
86 "AttributeError: 'list' object has no attribute 'next'". This is now86 cleanup function raises an exception. (John Arbash Meinel, #396838)
87 fixed. The log decorator no longer shows the elapsed time or transfer
88 rate because they're available in the log prefixes and the transport
89 activity display respectively.
90 (Martin Pool, #340347)
9187
92* Progress bars are now suppressed again when the environment variable88* Progress bars are now suppressed again when the environment variable
93 ``BZR_PROGRESS_BAR`` is set to ``none``.89 ``BZR_PROGRESS_BAR`` is set to ``none``.
@@ -110,6 +106,13 @@
110 removing the stacking location inside ``.bzr/branch``.106 removing the stacking location inside ``.bzr/branch``.
111 (Robert Collins, #376243)107 (Robert Collins, #376243)
112108
109* The ``log+`` decorator, useful in debugging or profiling, could cause
110 "AttributeError: 'list' object has no attribute 'next'". This is now
111 fixed. The log decorator no longer shows the elapsed time or transfer
112 rate because they're available in the log prefixes and the transport
113 activity display respectively.
114 (Martin Pool, #340347)
115
113* Unshelve works correctly when multiple zero-length files are present on116* Unshelve works correctly when multiple zero-length files are present on
114 the shelf. (Aaron Bentley, #363444)117 the shelf. (Aaron Bentley, #363444)
115118
116119
=== modified file 'bzrlib/lru_cache.py'
--- bzrlib/lru_cache.py 2009-05-01 20:20:37 +0000
+++ bzrlib/lru_cache.py 2009-07-08 19:35:14 +0000
@@ -1,4 +1,4 @@
1# Copyright (C) 2006, 2008 Canonical Ltd1# Copyright (C) 2006, 2008, 2009 Canonical Ltd
2#2#
3# This program is free software; you can redistribute it and/or modify3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by4# it under the terms of the GNU General Public License as published by
@@ -48,11 +48,14 @@
48 self.next_key, prev_key)48 self.next_key, prev_key)
4949
50 def run_cleanup(self):50 def run_cleanup(self):
51 if self.cleanup is not None:51 try:
52 self.cleanup(self.key, self.value)52 if self.cleanup is not None:
53 self.cleanup = None53 self.cleanup(self.key, self.value)
54 # Just make sure to break any refcycles, etc54 finally:
55 self.value = None55 # cleanup might raise an exception, but we want to make sure
56 # to break refcycles, etc
57 self.cleanup = None
58 self.value = None
5659
5760
58class LRUCache(object):61class LRUCache(object):
@@ -156,13 +159,18 @@
156 raise ValueError('cannot use _null_key as a key')159 raise ValueError('cannot use _null_key as a key')
157 if key in self._cache:160 if key in self._cache:
158 node = self._cache[key]161 node = self._cache[key]
159 node.run_cleanup()162 try:
160 node.value = value163 node.run_cleanup()
161 node.cleanup = cleanup164 finally:
165 # Maintain the LRU properties, even if cleanup raises an
166 # exception
167 node.value = value
168 node.cleanup = cleanup
169 self._record_access(node)
162 else:170 else:
163 node = _LRUNode(key, value, cleanup=cleanup)171 node = _LRUNode(key, value, cleanup=cleanup)
164 self._cache[key] = node172 self._cache[key] = node
165 self._record_access(node)173 self._record_access(node)
166174
167 if len(self._cache) > self._max_cache:175 if len(self._cache) > self._max_cache:
168 # Trigger the cleanup176 # Trigger the cleanup
@@ -241,16 +249,19 @@
241 # If we have removed all entries, remove the head pointer as well249 # If we have removed all entries, remove the head pointer as well
242 if self._least_recently_used is None:250 if self._least_recently_used is None:
243 self._most_recently_used = None251 self._most_recently_used = None
244 node.run_cleanup()252 try:
245 # Now remove this node from the linked list253 node.run_cleanup()
246 if node.prev is not None:254 finally:
247 node.prev.next_key = node.next_key255 # cleanup might raise an exception, but we want to make sure to
248 if node.next_key is not _null_key:256 # maintain the linked list
249 node_next = self._cache[node.next_key]257 if node.prev is not None:
250 node_next.prev = node.prev258 node.prev.next_key = node.next_key
251 # And remove this node's pointers259 if node.next_key is not _null_key:
252 node.prev = None260 node_next = self._cache[node.next_key]
253 node.next_key = _null_key261 node_next.prev = node.prev
262 # And remove this node's pointers
263 node.prev = None
264 node.next_key = _null_key
254265
255 def _remove_lru(self):266 def _remove_lru(self):
256 """Remove one entry from the lru, and handle consequences.267 """Remove one entry from the lru, and handle consequences.
257268
=== modified file 'bzrlib/tests/test_lru_cache.py'
--- bzrlib/tests/test_lru_cache.py 2009-04-16 22:06:25 +0000
+++ bzrlib/tests/test_lru_cache.py 2009-07-08 19:35:14 +0000
@@ -1,4 +1,4 @@
1# Copyright (C) 2006, 2008 Canonical Ltd1# Copyright (C) 2006, 2008, 2009 Canonical Ltd
2#2#
3# This program is free software; you can redistribute it and/or modify3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by4# it under the terms of the GNU General Public License as published by
@@ -132,6 +132,43 @@
132 cache[2] = 26132 cache[2] = 26
133 self.assertEqual([(2, 20), (2, 25)], cleanup_called)133 self.assertEqual([(2, 20), (2, 25)], cleanup_called)
134134
135 def test_cleanup_error_maintains_linked_list(self):
136 cleanup_called = []
137 def cleanup_func(key, val):
138 cleanup_called.append((key, val))
139 raise ValueError('failure during cleanup')
140
141 cache = lru_cache.LRUCache(max_cache=10)
142 for i in xrange(10):
143 cache.add(i, i, cleanup=cleanup_func)
144 for i in xrange(10, 20):
145 self.assertRaises(ValueError,
146 cache.add, i, i, cleanup=cleanup_func)
147
148 self.assertEqual([(i, i) for i in xrange(10)], cleanup_called)
149
150 self.assertEqual(range(19, 9, -1), [n.key for n in cache._walk_lru()])
151
152 def test_cleanup_during_replace_still_replaces(self):
153 cleanup_called = []
154 def cleanup_func(key, val):
155 cleanup_called.append((key, val))
156 raise ValueError('failure during cleanup')
157
158 cache = lru_cache.LRUCache(max_cache=10)
159 for i in xrange(10):
160 cache.add(i, i, cleanup=cleanup_func)
161 self.assertRaises(ValueError,
162 cache.add, 1, 20, cleanup=cleanup_func)
163 # We also still update the recent access to this node
164 self.assertEqual([1, 9, 8, 7, 6, 5, 4, 3, 2, 0],
165 [n.key for n in cache._walk_lru()])
166 self.assertEqual(20, cache[1])
167
168 self.assertEqual([(1, 1)], cleanup_called)
169 self.assertEqual([1, 9, 8, 7, 6, 5, 4, 3, 2, 0],
170 [n.key for n in cache._walk_lru()])
171
135 def test_len(self):172 def test_len(self):
136 cache = lru_cache.LRUCache(max_cache=10, after_cleanup_count=10)173 cache = lru_cache.LRUCache(max_cache=10, after_cleanup_count=10)
137174