summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
authorAntoine Pitrou <solipsis@pitrou.net>2016-12-27 13:19:20 (GMT)
committerAntoine Pitrou <solipsis@pitrou.net>2016-12-27 13:19:20 (GMT)
commite10ca3a0fe10d825689179e9958c70aef01f4230 (patch)
treef7dc9b56ba188d143a616062ec9e47f434aa32a3 /Lib
parent1fee5151f72e4e141eb37e7ab0da901d1b610f45 (diff)
downloadcpython-e10ca3a0fe10d825689179e9958c70aef01f4230.zip
cpython-e10ca3a0fe10d825689179e9958c70aef01f4230.tar.gz
cpython-e10ca3a0fe10d825689179e9958c70aef01f4230.tar.bz2
Issue #28427: old keys should not remove new values from
WeakValueDictionary when collecting from another thread.
Diffstat (limited to 'Lib')
-rw-r--r--Lib/test/test_weakref.py12
-rw-r--r--Lib/weakref.py34
2 files changed, 42 insertions, 4 deletions
diff --git a/Lib/test/test_weakref.py b/Lib/test/test_weakref.py
index 28a1cc2..1aa3540 100644
--- a/Lib/test/test_weakref.py
+++ b/Lib/test/test_weakref.py
@@ -1673,6 +1673,18 @@ class MappingTestCase(TestBase):
x = d.pop(10, 10)
self.assertIsNot(x, None) # we never put None in there!
+ def test_threaded_weak_valued_consistency(self):
+ # Issue #28427: old keys should not remove new values from
+ # WeakValueDictionary when collecting from another thread.
+ d = weakref.WeakValueDictionary()
+ with collect_in_thread():
+ for i in range(200000):
+ o = RefCycle()
+ d[10] = o
+ # o is still alive, so the dict can't be empty
+ self.assertEqual(len(d), 1)
+ o = None # lose ref
+
from test import mapping_tests
diff --git a/Lib/weakref.py b/Lib/weakref.py
index 9f8ef3e..aaebd0c 100644
--- a/Lib/weakref.py
+++ b/Lib/weakref.py
@@ -16,7 +16,8 @@ from _weakref import (
proxy,
CallableProxyType,
ProxyType,
- ReferenceType)
+ ReferenceType,
+ _remove_dead_weakref)
from _weakrefset import WeakSet, _IterationGuard
@@ -111,7 +112,9 @@ class WeakValueDictionary(collections.MutableMapping):
if self._iterating:
self._pending_removals.append(wr.key)
else:
- del self.data[wr.key]
+ # Atomic removal is necessary since this function
+ # can be called asynchronously by the GC
+ _remove_dead_weakref(d, wr.key)
self._remove = remove
# A list of keys to be removed
self._pending_removals = []
@@ -125,9 +128,12 @@ class WeakValueDictionary(collections.MutableMapping):
# We shouldn't encounter any KeyError, because this method should
# always be called *before* mutating the dict.
while l:
- del d[l.pop()]
+ key = l.pop()
+ _remove_dead_weakref(d, key)
def __getitem__(self, key):
+ if self._pending_removals:
+ self._commit_removals()
o = self.data[key]()
if o is None:
raise KeyError(key)
@@ -140,9 +146,13 @@ class WeakValueDictionary(collections.MutableMapping):
del self.data[key]
def __len__(self):
- return len(self.data) - len(self._pending_removals)
+ if self._pending_removals:
+ self._commit_removals()
+ return len(self.data)
def __contains__(self, key):
+ if self._pending_removals:
+ self._commit_removals()
try:
o = self.data[key]()
except KeyError:
@@ -158,6 +168,8 @@ class WeakValueDictionary(collections.MutableMapping):
self.data[key] = KeyedRef(value, self._remove, key)
def copy(self):
+ if self._pending_removals:
+ self._commit_removals()
new = WeakValueDictionary()
for key, wr in self.data.items():
o = wr()
@@ -169,6 +181,8 @@ class WeakValueDictionary(collections.MutableMapping):
def __deepcopy__(self, memo):
from copy import deepcopy
+ if self._pending_removals:
+ self._commit_removals()
new = self.__class__()
for key, wr in self.data.items():
o = wr()
@@ -177,6 +191,8 @@ class WeakValueDictionary(collections.MutableMapping):
return new
def get(self, key, default=None):
+ if self._pending_removals:
+ self._commit_removals()
try:
wr = self.data[key]
except KeyError:
@@ -190,6 +206,8 @@ class WeakValueDictionary(collections.MutableMapping):
return o
def items(self):
+ if self._pending_removals:
+ self._commit_removals()
with _IterationGuard(self):
for k, wr in self.data.items():
v = wr()
@@ -197,6 +215,8 @@ class WeakValueDictionary(collections.MutableMapping):
yield k, v
def keys(self):
+ if self._pending_removals:
+ self._commit_removals()
with _IterationGuard(self):
for k, wr in self.data.items():
if wr() is not None:
@@ -214,10 +234,14 @@ class WeakValueDictionary(collections.MutableMapping):
keep the values around longer than needed.
"""
+ if self._pending_removals:
+ self._commit_removals()
with _IterationGuard(self):
yield from self.data.values()
def values(self):
+ if self._pending_removals:
+ self._commit_removals()
with _IterationGuard(self):
for wr in self.data.values():
obj = wr()
@@ -290,6 +314,8 @@ class WeakValueDictionary(collections.MutableMapping):
keep the values around longer than needed.
"""
+ if self._pending_removals:
+ self._commit_removals()
return list(self.data.values())