[Python-checkins] cpython (2.7): Issue #19542: Fix bugs in WeakValueDictionary.setdefault() and

antoine.pitrou python-checkins at python.org
Mon Dec 19 05:13:57 EST 2016


https://hg.python.org/cpython/rev/bcb1f0698401
changeset:   105741:bcb1f0698401
branch:      2.7
parent:      105721:34b9fe09a2b7
user:        Antoine Pitrou <solipsis at pitrou.net>
date:        Mon Dec 19 11:12:58 2016 +0100
summary:
  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.

files:
  Lib/test/test_support.py |  10 ++++++
  Lib/test/test_weakref.py |  44 ++++++++++++++++++++++++++++
  Lib/weakref.py           |  13 +++++---
  Misc/NEWS                |   4 ++
  4 files changed, 66 insertions(+), 5 deletions(-)


diff --git a/Lib/test/test_support.py b/Lib/test/test_support.py
--- a/Lib/test/test_support.py
+++ b/Lib/test/test_support.py
@@ -1710,3 +1710,13 @@
     # The sequence should be deallocated just after the end of iterating
     gc_collect()
     test.assertTrue(done[0])
+
+ at contextlib.contextmanager
+def disable_gc():
+    have_gc = gc.isenabled()
+    gc.disable()
+    try:
+        yield
+    finally:
+        if have_gc:
+            gc.enable()
diff --git a/Lib/test/test_weakref.py b/Lib/test/test_weakref.py
--- a/Lib/test/test_weakref.py
+++ b/Lib/test/test_weakref.py
@@ -6,6 +6,7 @@
 import operator
 import contextlib
 import copy
+import time
 
 from test import test_support
 
@@ -56,6 +57,32 @@
         self.cycle = self
 
 
+ at contextlib.contextmanager
+def collect_in_thread(period=0.0001):
+    """
+    Ensure GC collections happen in a different thread, at a high frequency.
+    """
+    threading = test_support.import_module('threading')
+    please_stop = False
+
+    def collect():
+        while not please_stop:
+            time.sleep(period)
+            gc.collect()
+
+    with test_support.disable_gc():
+        old_interval = sys.getcheckinterval()
+        sys.setcheckinterval(20)
+        t = threading.Thread(target=collect)
+        t.start()
+        try:
+            yield
+        finally:
+            please_stop = True
+            t.join()
+            sys.setcheckinterval(old_interval)
+
+
 class TestBase(unittest.TestCase):
 
     def setUp(self):
@@ -1394,6 +1421,23 @@
         self.assertEqual(len(d), 0)
         self.assertEqual(count, 2)
 
+    def test_threaded_weak_valued_setdefault(self):
+        d = weakref.WeakValueDictionary()
+        with collect_in_thread():
+            for i in range(50000):
+                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(50000):
+                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
--- a/Lib/weakref.py
+++ b/Lib/weakref.py
@@ -202,24 +202,27 @@
         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:
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -13,6 +13,10 @@
 Library
 -------
 
+- Issue #19542: Fix bugs in WeakValueDictionary.setdefault() and
+  WeakValueDictionary.pop() when a GC collection happens in another
+  thread.
+
 - Issue #28925: cPickle now correctly propagates errors when unpickle instances
   of old-style classes.
 

-- 
Repository URL: https://hg.python.org/cpython


More information about the Python-checkins mailing list