Browse Source

Fix LRUCache.update for Python 3.5

Python 3.5's OrderedDict does not allow mutation while it is being
iterated over. This breaks "update" if it is called with a dict
larger than the maximum size.

This commit changes the code to a version that does not iterate over
the dict, and should also be a little bit faster.

Closes #2897
Dennis Brakhane 9 năm trước cách đây
mục cha
commit
2d8b83723a

+ 1 - 0
CONTRIBUTORS.txt

@@ -197,3 +197,4 @@ Gerald Manipon, 2015/10/19
 Krzysztof Bujniewicz, 2015/10/21
 Krzysztof Bujniewicz, 2015/10/21
 Sukrit Khera, 2015/10/26
 Sukrit Khera, 2015/10/26
 Dave Smith, 2015/10/27
 Dave Smith, 2015/10/27
+Dennis Brakhane, 2015/10/30

+ 5 - 0
celery/tests/utils/test_functional.py

@@ -63,6 +63,11 @@ class test_LRUCache(Case):
         x[7] = 7
         x[7] = 7
         self.assertEqual(list(x.keys()), [3, 6, 7])
         self.assertEqual(list(x.keys()), [3, 6, 7])
 
 
+    def test_update_larger_than_cache_size(self):
+        x = LRUCache(2)
+        x.update({x: x for x in range(100)})
+        self.assertEqual(list(x.keys()), [98, 99])
+
     def assertSafeIter(self, method, interval=0.01, size=10000):
     def assertSafeIter(self, method, interval=0.01, size=10000):
         if sys.version_info >= (3, 5):
         if sys.version_info >= (3, 5):
             raise SkipTest('Fails on Py3.5')
             raise SkipTest('Fails on Py3.5')

+ 3 - 4
celery/utils/functional.py

@@ -20,7 +20,7 @@ from amqp import promise
 from kombu.utils import cached_property
 from kombu.utils import cached_property
 from kombu.utils.functional import lazy, maybe_evaluate, is_list, maybe_list
 from kombu.utils.functional import lazy, maybe_evaluate, is_list, maybe_list
 
 
-from celery.five import UserDict, UserList, items, keys
+from celery.five import UserDict, UserList, items, keys, range
 
 
 __all__ = ['LRUCache', 'is_list', 'maybe_list', 'memoize', 'mlazy', 'noop',
 __all__ = ['LRUCache', 'is_list', 'maybe_list', 'memoize', 'mlazy', 'noop',
            'first', 'firstmethod', 'chunks', 'padlist', 'mattrgetter', 'uniq',
            'first', 'firstmethod', 'chunks', 'padlist', 'mattrgetter', 'uniq',
@@ -71,9 +71,8 @@ class LRUCache(UserDict):
             data.update(*args, **kwargs)
             data.update(*args, **kwargs)
             if limit and len(data) > limit:
             if limit and len(data) > limit:
                 # pop additional items in case limit exceeded
                 # pop additional items in case limit exceeded
-                # negative overflow will lead to an empty list
-                for item in islice(iter(data), len(data) - limit):
-                    data.pop(item)
+                for _ in range(len(data) - limit):
+                    data.popitem(last=False)
 
 
     def popitem(self, last=True):
     def popitem(self, last=True):
         with self.mutex:
         with self.mutex: