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
1=== modified file 'NEWS'
2--- NEWS 2009-07-08 18:05:38 +0000
3+++ NEWS 2009-07-08 19:35:13 +0000
4@@ -82,12 +82,8 @@
5 * Force socket shutdown in threaded http test servers to avoid client hangs
6 (pycurl). (Vincent Ladeuil, #383920).
7
8-* The ``log+`` decorator, useful in debugging or profiling, could cause
9- "AttributeError: 'list' object has no attribute 'next'". This is now
10- fixed. The log decorator no longer shows the elapsed time or transfer
11- rate because they're available in the log prefixes and the transport
12- activity display respectively.
13- (Martin Pool, #340347)
14+* ``LRUCache`` will maintain the linked list pointers even if a nodes
15+ cleanup function raises an exception. (John Arbash Meinel, #396838)
16
17 * Progress bars are now suppressed again when the environment variable
18 ``BZR_PROGRESS_BAR`` is set to ``none``.
19@@ -110,6 +106,13 @@
20 removing the stacking location inside ``.bzr/branch``.
21 (Robert Collins, #376243)
22
23+* The ``log+`` decorator, useful in debugging or profiling, could cause
24+ "AttributeError: 'list' object has no attribute 'next'". This is now
25+ fixed. The log decorator no longer shows the elapsed time or transfer
26+ rate because they're available in the log prefixes and the transport
27+ activity display respectively.
28+ (Martin Pool, #340347)
29+
30 * Unshelve works correctly when multiple zero-length files are present on
31 the shelf. (Aaron Bentley, #363444)
32
33
34=== modified file 'bzrlib/lru_cache.py'
35--- bzrlib/lru_cache.py 2009-05-01 20:20:37 +0000
36+++ bzrlib/lru_cache.py 2009-07-08 19:35:14 +0000
37@@ -1,4 +1,4 @@
38-# Copyright (C) 2006, 2008 Canonical Ltd
39+# Copyright (C) 2006, 2008, 2009 Canonical Ltd
40 #
41 # This program is free software; you can redistribute it and/or modify
42 # it under the terms of the GNU General Public License as published by
43@@ -48,11 +48,14 @@
44 self.next_key, prev_key)
45
46 def run_cleanup(self):
47- if self.cleanup is not None:
48- self.cleanup(self.key, self.value)
49- self.cleanup = None
50- # Just make sure to break any refcycles, etc
51- self.value = None
52+ try:
53+ if self.cleanup is not None:
54+ self.cleanup(self.key, self.value)
55+ finally:
56+ # cleanup might raise an exception, but we want to make sure
57+ # to break refcycles, etc
58+ self.cleanup = None
59+ self.value = None
60
61
62 class LRUCache(object):
63@@ -156,13 +159,18 @@
64 raise ValueError('cannot use _null_key as a key')
65 if key in self._cache:
66 node = self._cache[key]
67- node.run_cleanup()
68- node.value = value
69- node.cleanup = cleanup
70+ try:
71+ node.run_cleanup()
72+ finally:
73+ # Maintain the LRU properties, even if cleanup raises an
74+ # exception
75+ node.value = value
76+ node.cleanup = cleanup
77+ self._record_access(node)
78 else:
79 node = _LRUNode(key, value, cleanup=cleanup)
80 self._cache[key] = node
81- self._record_access(node)
82+ self._record_access(node)
83
84 if len(self._cache) > self._max_cache:
85 # Trigger the cleanup
86@@ -241,16 +249,19 @@
87 # If we have removed all entries, remove the head pointer as well
88 if self._least_recently_used is None:
89 self._most_recently_used = None
90- node.run_cleanup()
91- # Now remove this node from the linked list
92- if node.prev is not None:
93- node.prev.next_key = node.next_key
94- if node.next_key is not _null_key:
95- node_next = self._cache[node.next_key]
96- node_next.prev = node.prev
97- # And remove this node's pointers
98- node.prev = None
99- node.next_key = _null_key
100+ try:
101+ node.run_cleanup()
102+ finally:
103+ # cleanup might raise an exception, but we want to make sure to
104+ # maintain the linked list
105+ if node.prev is not None:
106+ node.prev.next_key = node.next_key
107+ if node.next_key is not _null_key:
108+ node_next = self._cache[node.next_key]
109+ node_next.prev = node.prev
110+ # And remove this node's pointers
111+ node.prev = None
112+ node.next_key = _null_key
113
114 def _remove_lru(self):
115 """Remove one entry from the lru, and handle consequences.
116
117=== modified file 'bzrlib/tests/test_lru_cache.py'
118--- bzrlib/tests/test_lru_cache.py 2009-04-16 22:06:25 +0000
119+++ bzrlib/tests/test_lru_cache.py 2009-07-08 19:35:14 +0000
120@@ -1,4 +1,4 @@
121-# Copyright (C) 2006, 2008 Canonical Ltd
122+# Copyright (C) 2006, 2008, 2009 Canonical Ltd
123 #
124 # This program is free software; you can redistribute it and/or modify
125 # it under the terms of the GNU General Public License as published by
126@@ -132,6 +132,43 @@
127 cache[2] = 26
128 self.assertEqual([(2, 20), (2, 25)], cleanup_called)
129
130+ def test_cleanup_error_maintains_linked_list(self):
131+ cleanup_called = []
132+ def cleanup_func(key, val):
133+ cleanup_called.append((key, val))
134+ raise ValueError('failure during cleanup')
135+
136+ cache = lru_cache.LRUCache(max_cache=10)
137+ for i in xrange(10):
138+ cache.add(i, i, cleanup=cleanup_func)
139+ for i in xrange(10, 20):
140+ self.assertRaises(ValueError,
141+ cache.add, i, i, cleanup=cleanup_func)
142+
143+ self.assertEqual([(i, i) for i in xrange(10)], cleanup_called)
144+
145+ self.assertEqual(range(19, 9, -1), [n.key for n in cache._walk_lru()])
146+
147+ def test_cleanup_during_replace_still_replaces(self):
148+ cleanup_called = []
149+ def cleanup_func(key, val):
150+ cleanup_called.append((key, val))
151+ raise ValueError('failure during cleanup')
152+
153+ cache = lru_cache.LRUCache(max_cache=10)
154+ for i in xrange(10):
155+ cache.add(i, i, cleanup=cleanup_func)
156+ self.assertRaises(ValueError,
157+ cache.add, 1, 20, cleanup=cleanup_func)
158+ # We also still update the recent access to this node
159+ self.assertEqual([1, 9, 8, 7, 6, 5, 4, 3, 2, 0],
160+ [n.key for n in cache._walk_lru()])
161+ self.assertEqual(20, cache[1])
162+
163+ self.assertEqual([(1, 1)], cleanup_called)
164+ self.assertEqual([1, 9, 8, 7, 6, 5, 4, 3, 2, 0],
165+ [n.key for n in cache._walk_lru()])
166+
167 def test_len(self):
168 cache = lru_cache.LRUCache(max_cache=10, after_cleanup_count=10)
169