summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAntoine Pitrou <solipsis@pitrou.net>2016-12-19 09:56:40 (GMT)
committerAntoine Pitrou <solipsis@pitrou.net>2016-12-19 09:56:40 (GMT)
commitc1ee488962f0a20c2814b14a4c29d6082dd38add (patch)
tree08884079e3c2da9de5e337ae9d78b6410fa683ef
parentca3263c50c3b3a3719d2ec3ee7b30b8c669dcb19 (diff)
downloadcpython-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.py41
-rw-r--r--Lib/weakref.py13
-rw-r--r--Misc/NEWS4
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:
diff --git a/Misc/NEWS b/Misc/NEWS
index f613d0d..a2c2881 100644
--- 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.