mirror of
https://github.com/python/cpython.git
synced 2025-08-31 22:18:28 +00:00
Closes #21173: Fix len() on a WeakKeyDictionary when .clear() was called with an iterator alive.
This commit is contained in:
parent
bed04a77ee
commit
1bf974dc6e
3 changed files with 52 additions and 0 deletions
|
@ -1298,6 +1298,36 @@ class MappingTestCase(TestBase):
|
||||||
dict.clear()
|
dict.clear()
|
||||||
self.assertEqual(len(dict), 0)
|
self.assertEqual(len(dict), 0)
|
||||||
|
|
||||||
|
def check_weak_del_and_len_while_iterating(self, dict, testcontext):
|
||||||
|
# Check that len() works when both iterating and removing keys
|
||||||
|
# explicitly through various means (.pop(), .clear()...), while
|
||||||
|
# implicit mutation is deferred because an iterator is alive.
|
||||||
|
# (each call to testcontext() should schedule one item for removal
|
||||||
|
# for this test to work properly)
|
||||||
|
o = Object(123456)
|
||||||
|
with testcontext():
|
||||||
|
n = len(dict)
|
||||||
|
dict.popitem()
|
||||||
|
self.assertEqual(len(dict), n - 1)
|
||||||
|
dict[o] = o
|
||||||
|
self.assertEqual(len(dict), n)
|
||||||
|
with testcontext():
|
||||||
|
self.assertEqual(len(dict), n - 1)
|
||||||
|
dict.pop(next(dict.keys()))
|
||||||
|
self.assertEqual(len(dict), n - 2)
|
||||||
|
with testcontext():
|
||||||
|
self.assertEqual(len(dict), n - 3)
|
||||||
|
del dict[next(dict.keys())]
|
||||||
|
self.assertEqual(len(dict), n - 4)
|
||||||
|
with testcontext():
|
||||||
|
self.assertEqual(len(dict), n - 5)
|
||||||
|
dict.popitem()
|
||||||
|
self.assertEqual(len(dict), n - 6)
|
||||||
|
with testcontext():
|
||||||
|
dict.clear()
|
||||||
|
self.assertEqual(len(dict), 0)
|
||||||
|
self.assertEqual(len(dict), 0)
|
||||||
|
|
||||||
def test_weak_keys_destroy_while_iterating(self):
|
def test_weak_keys_destroy_while_iterating(self):
|
||||||
# Issue #7105: iterators shouldn't crash when a key is implicitly removed
|
# Issue #7105: iterators shouldn't crash when a key is implicitly removed
|
||||||
dict, objects = self.make_weak_keyed_dict()
|
dict, objects = self.make_weak_keyed_dict()
|
||||||
|
@ -1319,6 +1349,10 @@ class MappingTestCase(TestBase):
|
||||||
it = None # should commit all removals
|
it = None # should commit all removals
|
||||||
gc.collect()
|
gc.collect()
|
||||||
self.check_weak_destroy_and_mutate_while_iterating(dict, testcontext)
|
self.check_weak_destroy_and_mutate_while_iterating(dict, testcontext)
|
||||||
|
# Issue #21173: len() fragile when keys are both implicitly and
|
||||||
|
# explicitly removed.
|
||||||
|
dict, objects = self.make_weak_keyed_dict()
|
||||||
|
self.check_weak_del_and_len_while_iterating(dict, testcontext)
|
||||||
|
|
||||||
def test_weak_values_destroy_while_iterating(self):
|
def test_weak_values_destroy_while_iterating(self):
|
||||||
# Issue #7105: iterators shouldn't crash when a key is implicitly removed
|
# Issue #7105: iterators shouldn't crash when a key is implicitly removed
|
||||||
|
@ -1342,6 +1376,8 @@ class MappingTestCase(TestBase):
|
||||||
it = None # should commit all removals
|
it = None # should commit all removals
|
||||||
gc.collect()
|
gc.collect()
|
||||||
self.check_weak_destroy_and_mutate_while_iterating(dict, testcontext)
|
self.check_weak_destroy_and_mutate_while_iterating(dict, testcontext)
|
||||||
|
dict, objects = self.make_weak_valued_dict()
|
||||||
|
self.check_weak_del_and_len_while_iterating(dict, testcontext)
|
||||||
|
|
||||||
def test_make_weak_keyed_dict_from_dict(self):
|
def test_make_weak_keyed_dict_from_dict(self):
|
||||||
o = Object(3)
|
o = Object(3)
|
||||||
|
|
|
@ -322,6 +322,7 @@ class WeakKeyDictionary(collections.MutableMapping):
|
||||||
# A list of dead weakrefs (keys to be removed)
|
# A list of dead weakrefs (keys to be removed)
|
||||||
self._pending_removals = []
|
self._pending_removals = []
|
||||||
self._iterating = set()
|
self._iterating = set()
|
||||||
|
self._dirty_len = False
|
||||||
if dict is not None:
|
if dict is not None:
|
||||||
self.update(dict)
|
self.update(dict)
|
||||||
|
|
||||||
|
@ -338,13 +339,23 @@ class WeakKeyDictionary(collections.MutableMapping):
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def _scrub_removals(self):
|
||||||
|
d = self.data
|
||||||
|
self._pending_removals = [k for k in self._pending_removals if k in d]
|
||||||
|
self._dirty_len = False
|
||||||
|
|
||||||
def __delitem__(self, key):
|
def __delitem__(self, key):
|
||||||
|
self._dirty_len = True
|
||||||
del self.data[ref(key)]
|
del self.data[ref(key)]
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
return self.data[ref(key)]
|
return self.data[ref(key)]
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
|
if self._dirty_len and self._pending_removals:
|
||||||
|
# self._pending_removals may still contain keys which were
|
||||||
|
# explicitly removed, we have to scrub them (see issue #21173).
|
||||||
|
self._scrub_removals()
|
||||||
return len(self.data) - len(self._pending_removals)
|
return len(self.data) - len(self._pending_removals)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
@ -417,6 +428,7 @@ class WeakKeyDictionary(collections.MutableMapping):
|
||||||
return list(self.data)
|
return list(self.data)
|
||||||
|
|
||||||
def popitem(self):
|
def popitem(self):
|
||||||
|
self._dirty_len = True
|
||||||
while True:
|
while True:
|
||||||
key, value = self.data.popitem()
|
key, value = self.data.popitem()
|
||||||
o = key()
|
o = key()
|
||||||
|
@ -424,6 +436,7 @@ class WeakKeyDictionary(collections.MutableMapping):
|
||||||
return o, value
|
return o, value
|
||||||
|
|
||||||
def pop(self, key, *args):
|
def pop(self, key, *args):
|
||||||
|
self._dirty_len = True
|
||||||
return self.data.pop(ref(key), *args)
|
return self.data.pop(ref(key), *args)
|
||||||
|
|
||||||
def setdefault(self, key, default=None):
|
def setdefault(self, key, default=None):
|
||||||
|
|
|
@ -22,6 +22,9 @@ Core and Builtins
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- Issue #21173: Fix len() on a WeakKeyDictionary when .clear() was called
|
||||||
|
with an iterator alive.
|
||||||
|
|
||||||
- Issue #11866: Eliminated race condition in the computation of names
|
- Issue #11866: Eliminated race condition in the computation of names
|
||||||
for new threads.
|
for new threads.
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue