diff options
author | Antoine Pitrou <solipsis@pitrou.net> | 2016-12-19 09:56:40 (GMT) |
---|---|---|
committer | Antoine Pitrou <solipsis@pitrou.net> | 2016-12-19 09:56:40 (GMT) |
commit | c1ee488962f0a20c2814b14a4c29d6082dd38add (patch) | |
tree | 08884079e3c2da9de5e337ae9d78b6410fa683ef | |
parent | ca3263c50c3b3a3719d2ec3ee7b30b8c669dcb19 (diff) | |
download | cpython-c1ee488962f0a20c2814b14a4c29d6082dd38add.zip cpython-c1ee488962f0a20c2814b14a4c29d6082dd38add.tar.gz cpython-c1ee488962f0a20c2814b14a4c29d6082dd38add.tar.bz2 |
Issue #19542: Fix bugs in WeakValueDictionary.setdefault() and WeakValueDictionary.pop()
when a GC collection happens in another thread.
Original patch and report by Armin Rigo.
-rw-r--r-- | Lib/test/test_weakref.py | 41 | ||||
-rw-r--r-- | Lib/weakref.py | 13 | ||||
-rw-r--r-- | Misc/NEWS | 4 |
3 files changed, 53 insertions, 5 deletions
diff --git a/Lib/test/test_weakref.py b/Lib/test/test_weakref.py index 4cfaa54..28a1cc2 100644 --- a/Lib/test/test_weakref.py +++ b/Lib/test/test_weakref.py @@ -6,6 +6,7 @@ import weakref import operator import contextlib import copy +import time from test import support from test.support import script_helper @@ -72,6 +73,29 @@ class TestBase(unittest.TestCase): self.cbcalled += 1 +@contextlib.contextmanager +def collect_in_thread(period=0.0001): + """ + Ensure GC collections happen in a different thread, at a high frequency. + """ + threading = support.import_module('threading') + please_stop = False + + def collect(): + while not please_stop: + time.sleep(period) + gc.collect() + + with support.disable_gc(): + t = threading.Thread(target=collect) + t.start() + try: + yield + finally: + please_stop = True + t.join() + + class ReferencesTestCase(TestBase): def test_basic_ref(self): @@ -1633,6 +1657,23 @@ class MappingTestCase(TestBase): dict = weakref.WeakKeyDictionary() self.assertRegex(repr(dict), '<WeakKeyDictionary at 0x.*>') + def test_threaded_weak_valued_setdefault(self): + d = weakref.WeakValueDictionary() + with collect_in_thread(): + for i in range(100000): + x = d.setdefault(10, RefCycle()) + self.assertIsNot(x, None) # we never put None in there! + del x + + def test_threaded_weak_valued_pop(self): + d = weakref.WeakValueDictionary() + with collect_in_thread(): + for i in range(100000): + d[10] = RefCycle() + x = d.pop(10, 10) + self.assertIsNot(x, None) # we never put None in there! + + from test import mapping_tests class WeakValueDictionaryTestCase(mapping_tests.BasicTestMappingProtocol): diff --git a/Lib/weakref.py b/Lib/weakref.py index 2968fb9..9f8ef3e 100644 --- a/Lib/weakref.py +++ b/Lib/weakref.py @@ -239,24 +239,27 @@ class WeakValueDictionary(collections.MutableMapping): try: o = self.data.pop(key)() except KeyError: + o = None + if o is None: if args: return args[0] - raise - if o is None: - raise KeyError(key) + else: + raise KeyError(key) else: return o def setdefault(self, key, default=None): try: - wr = self.data[key] + o = self.data[key]() except KeyError: + o = None + if o is None: if self._pending_removals: self._commit_removals() self.data[key] = KeyedRef(default, self._remove, key) return default else: - return wr() + return o def update(*args, **kwargs): if not args: @@ -133,6 +133,10 @@ Core and Builtins Library ------- +- Issue #19542: Fix bugs in WeakValueDictionary.setdefault() and + WeakValueDictionary.pop() when a GC collection happens in another + thread. + - Issue #20191: Fixed a crash in resource.prlimit() when pass a sequence that doesn't own its elements as limits. |