From 1a0fd52c5cd75a3fd134d982f7d0ed37315d580d Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 14 Sep 2016 13:26:59 +0200 Subject: [PATCH] Avoid unbounded growth in dict_resize Each time dict_resize is called, it gets a new, larger size `> minused`. If this is triggered many times, it will keep growing in size by a factor of two each time, as the previous size is passed as minused for the next call. Set the lower bound at minused (inclusive), rather than exclusive, so that the size does not continue to increase for repeated calls. includes test for split-dict resizing triggered by popping items from obj.__dict__ a simpler test might be available --- Lib/test/test_dict.py | 26 ++++++++++++++++++++++++++ Objects/dictobject.c | 4 ++-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_dict.py b/Lib/test/test_dict.py index ed66ddb..f73a28f 100644 --- a/Lib/test/test_dict.py +++ b/Lib/test/test_dict.py @@ -1038,6 +1038,32 @@ class DictTest(unittest.TestCase): support.check_free_after_iterating(self, lambda d: iter(d.values()), dict) support.check_free_after_iterating(self, lambda d: iter(d.items()), dict) + def test_class_dict_pop(self): + class DictPopper(object): + def __init__(self): + # calling this in `__init__` causes growth across multiple + # instances. If not called in `__init__`, size resets for + # each instance. + self.pop_attr() + + def pop_attr(self): + """Set an attribute and then remove it from self.__dict__""" + # Trigger bug in split-dict resizing by adding an item and then + # removing int This dict.pop is more expensive each time it is + # called. + self.attr = 1 + self.__dict__.pop('attr', None) + + # new instance each time: + for i in range(100): + d = DictPopper() + d.pop_attr() + + # re-use instance: + d = DictPopper() + for i in range(100): + d.pop_attr() + from test import mapping_tests class GeneralMappingTests(mapping_tests.BasicTestMappingProtocol): diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 06c54b5..24fe509 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -1168,9 +1168,9 @@ dictresize(PyDictObject *mp, Py_ssize_t minused) PyObject **oldvalues; PyDictKeyEntry *ep0; - /* Find the smallest table size > minused. */ + /* Find the smallest table size >= minused. */ for (newsize = PyDict_MINSIZE; - newsize <= minused && newsize > 0; + newsize < minused && newsize > 0; newsize <<= 1) ; if (newsize <= 0) { -- 2.10.0