mirror of
https://github.com/python/cpython.git
synced 2025-11-01 06:01:29 +00:00
SF 742860: WeakKeyDictionary __delitem__ uses iterkeys
Someone review this, please! Final releases are getting close, Fred (the weakref guy) won't be around until Tuesday, and the pre-patch code can indeed raise spurious RuntimeErrors in the presence of threads or mutating comparison functions. See the bug report for my confusions: I can't see any reason for why __delitem__ iterated over the keys. The new one-liner implementation is much faster, can't raise RuntimeError, and should be better-behaved in all respects wrt threads. New tests test_weak_keyed_bad_delitem and test_weak_keyed_cascading_deletes fail before this patch. Bugfix candidate for 2.2.3 too, if someone else agrees with this patch.
This commit is contained in:
parent
6f80594229
commit
886128f4f8
3 changed files with 58 additions and 5 deletions
|
|
@ -516,6 +516,57 @@ def test_weak_valued_delitem(self):
|
||||||
self.assert_(len(d) == 1)
|
self.assert_(len(d) == 1)
|
||||||
self.assert_(d.items() == [('something else', o2)])
|
self.assert_(d.items() == [('something else', o2)])
|
||||||
|
|
||||||
|
def test_weak_keyed_bad_delitem(self):
|
||||||
|
d = weakref.WeakKeyDictionary()
|
||||||
|
o = Object('1')
|
||||||
|
# An attempt to delete an object that isn't there should raise
|
||||||
|
# KetError. It didn't before 2.3.
|
||||||
|
self.assertRaises(KeyError, d.__delitem__, o)
|
||||||
|
|
||||||
|
def test_weak_keyed_cascading_deletes(self):
|
||||||
|
# SF bug 742860. For some reason, before 2.3 __delitem__ iterated
|
||||||
|
# over the keys via self.data.iterkeys(). If things vanished from
|
||||||
|
# the dict during this (or got added), that caused a RuntimeError.
|
||||||
|
|
||||||
|
d = weakref.WeakKeyDictionary()
|
||||||
|
mutate = False
|
||||||
|
|
||||||
|
class C(object):
|
||||||
|
def __init__(self, i):
|
||||||
|
self.value = i
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(self.value)
|
||||||
|
def __eq__(self, other):
|
||||||
|
if mutate:
|
||||||
|
# Side effect that mutates the dict, by removing the
|
||||||
|
# last strong reference to a key.
|
||||||
|
del objs[-1]
|
||||||
|
return self.value == other.value
|
||||||
|
|
||||||
|
objs = [C(i) for i in range(4)]
|
||||||
|
for o in objs:
|
||||||
|
d[o] = o.value
|
||||||
|
del o # now the only strong references to keys are in objs
|
||||||
|
# Find the order in which iterkeys sees the keys.
|
||||||
|
objs = d.keys()
|
||||||
|
# Reverse it, so that the iteration implementation of __delitem__
|
||||||
|
# has to keep looping to find the first object we delete.
|
||||||
|
objs.reverse()
|
||||||
|
# Turn on mutation in C.__eq__. The first time thru the loop,
|
||||||
|
# under the iterkeys() business the first comparison will delete
|
||||||
|
# the last item iterkeys() would see, and that causes a
|
||||||
|
# RuntimeError: dictionary changed size during iteration
|
||||||
|
# when the iterkeys() loop goes around to try comparing the next
|
||||||
|
# key. After ths was fixed, it just deletes the last object *our*
|
||||||
|
# "for o in obj" loop would have gotten to.
|
||||||
|
mutate = True
|
||||||
|
count = 0
|
||||||
|
for o in objs:
|
||||||
|
count += 1
|
||||||
|
del d[o]
|
||||||
|
self.assertEqual(len(d), 0)
|
||||||
|
self.assertEqual(count, 2)
|
||||||
|
|
||||||
from test_userdict import TestMappingProtocol
|
from test_userdict import TestMappingProtocol
|
||||||
|
|
||||||
class WeakValueDictionaryTestCase(TestMappingProtocol):
|
class WeakValueDictionaryTestCase(TestMappingProtocol):
|
||||||
|
|
|
||||||
|
|
@ -164,11 +164,7 @@ def remove(k, selfref=ref(self)):
|
||||||
if dict is not None: self.update(dict)
|
if dict is not None: self.update(dict)
|
||||||
|
|
||||||
def __delitem__(self, key):
|
def __delitem__(self, key):
|
||||||
for ref in self.data.iterkeys():
|
del self.data[ref(key)]
|
||||||
o = ref()
|
|
||||||
if o == key:
|
|
||||||
del self.data[ref]
|
|
||||||
return
|
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
return self.data[ref(key)]
|
return self.data[ref(key)]
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,12 @@ What's New in Python 2.3 beta 2?
|
||||||
Core and builtins
|
Core and builtins
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
|
- SF bug 742860: WeakKeyDictionary __delitem__ uses iterkeys. This
|
||||||
|
wasn't as threadsafe as it should be, was very inefficient, and could
|
||||||
|
raise RuntimeError if another thread mutated the dict during
|
||||||
|
__delitem__, or if a comparison function mutated it. A new
|
||||||
|
implementation of WeakKeyDictionary.__delitem__ repairs all that.
|
||||||
|
|
||||||
- SF bug 705231: builtin pow() no longer lets the platform C pow()
|
- SF bug 705231: builtin pow() no longer lets the platform C pow()
|
||||||
raise -1.0 to integer powers, because (at least) glibc gets it wrong
|
raise -1.0 to integer powers, because (at least) glibc gets it wrong
|
||||||
in some cases. The result should be -1.0 if the power is odd and 1.0
|
in some cases. The result should be -1.0 if the power is odd and 1.0
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue