]> granicus.if.org Git - python/commitdiff
Issue #19542: Fix bugs in WeakValueDictionary.setdefault() and WeakValueDictionary...
authorAntoine Pitrou <solipsis@pitrou.net>
Mon, 19 Dec 2016 09:56:40 +0000 (10:56 +0100)
committerAntoine Pitrou <solipsis@pitrou.net>
Mon, 19 Dec 2016 09:56:40 +0000 (10:56 +0100)
when a GC collection happens in another thread.

Original patch and report by Armin Rigo.

Lib/test/test_weakref.py
Lib/weakref.py
Misc/NEWS

index 4cfaa5488640399340f4d6e3c7a38370edf9d653..28a1cc2bfa906f7e5dba0cb9e13bfd26a8cacb4a 100644 (file)
@@ -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):
index 2968fb9f726e69bae7b83213cbe04163d2844275..9f8ef3eb8c6510b0856d3b1605d6298307921512 100644 (file)
@@ -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:
index f613d0deb992f236f4d9e00c170d70a4ee3d3e94..a2c2881f76f49330fc0c29d0e72afd27990daad1 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -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.