[pypy-commit] pypy default: Use objectmodel.delitem_if_value_is() to fix lib-python/2.7/weakref.py
arigo
pypy.commits at gmail.com
Sun Feb 5 15:51:38 EST 2017
Author: Armin Rigo <arigo at tunes.org>
Branch:
Changeset: r89958:cba1fc5e5dcf
Date: 2017-02-05 20:54 +0100
http://bitbucket.org/pypy/pypy/changeset/cba1fc5e5dcf/
Log: Use objectmodel.delitem_if_value_is() to fix lib-
python/2.7/weakref.py
diff --git a/lib-python/2.7/weakref.py b/lib-python/2.7/weakref.py
--- a/lib-python/2.7/weakref.py
+++ b/lib-python/2.7/weakref.py
@@ -31,6 +31,26 @@
"WeakKeyDictionary", "ReferenceError", "ReferenceType", "ProxyType",
"CallableProxyType", "ProxyTypes", "WeakValueDictionary", 'WeakSet']
+try:
+ from __pypy__ import delitem_if_value_is as _delitem_if_value_is
+except ImportError:
+ def _delitem_if_value_is(d, key, value):
+ try:
+ if self.data[key] is value: # fall-back: there is a potential
+ # race condition in multithreaded programs HERE
+ del self.data[key]
+ except KeyError:
+ pass
+
+def _remove_dead_weakref(d, key):
+ try:
+ wr = d[key]
+ except KeyError:
+ pass
+ else:
+ if wr() is None:
+ _delitem_if_value_is(d, key, wr)
+
class WeakValueDictionary(UserDict.UserDict):
"""Mapping class that references values weakly.
@@ -58,14 +78,9 @@
if self._iterating:
self._pending_removals.append(wr.key)
else:
- # Changed this for PyPy: made more resistent. The
- # issue is that in some corner cases, self.data
- # might already be changed or removed by the time
- # this weakref's callback is called. If that is
- # the case, we don't want to randomly kill an
- # unrelated entry.
- if self.data.get(wr.key) is wr:
- del self.data[wr.key]
+ # Atomic removal is necessary since this function
+ # can be called asynchronously by the GC
+ _delitem_if_value_is(self.data, wr.key, wr)
self._remove = remove
# A list of keys to be removed
self._pending_removals = []
@@ -78,7 +93,8 @@
# 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):
o = self.data[key]()
diff --git a/pypy/module/__pypy__/__init__.py b/pypy/module/__pypy__/__init__.py
--- a/pypy/module/__pypy__/__init__.py
+++ b/pypy/module/__pypy__/__init__.py
@@ -79,6 +79,7 @@
'add_memory_pressure' : 'interp_magic.add_memory_pressure',
'newdict' : 'interp_dict.newdict',
'reversed_dict' : 'interp_dict.reversed_dict',
+ 'delitem_if_value_is' : 'interp_dict.delitem_if_value_is',
'strategy' : 'interp_magic.strategy', # dict,set,list
'specialized_zip_2_lists' : 'interp_magic.specialized_zip_2_lists',
'set_debug' : 'interp_magic.set_debug',
diff --git a/pypy/module/__pypy__/interp_dict.py b/pypy/module/__pypy__/interp_dict.py
--- a/pypy/module/__pypy__/interp_dict.py
+++ b/pypy/module/__pypy__/interp_dict.py
@@ -44,3 +44,16 @@
if not isinstance(w_obj, W_DictMultiObject):
raise OperationError(space.w_TypeError, space.w_None)
return w_obj.nondescr_reversed_dict(space)
+
+def delitem_if_value_is(space, w_obj, w_key, w_value):
+ """Atomic equivalent to: 'if dict.get(key) is value: del dict[key]'.
+
+ SPECIAL USE CASES ONLY! Avoid using on dicts which are specialized,
+ e.g. to int or str keys, because it switches to the object strategy.
+ Also, the 'is' operation is really pointer equality, so avoid using
+ it if 'value' is an immutable object like int or str.
+ """
+ from pypy.objspace.std.dictmultiobject import W_DictMultiObject
+ if not isinstance(w_obj, W_DictMultiObject):
+ raise OperationError(space.w_TypeError, space.w_None)
+ return w_obj.nondescr_delitem_if_value_is(space, w_key, w_value)
diff --git a/pypy/objspace/std/dictmultiobject.py b/pypy/objspace/std/dictmultiobject.py
--- a/pypy/objspace/std/dictmultiobject.py
+++ b/pypy/objspace/std/dictmultiobject.py
@@ -283,6 +283,14 @@
w_keys = self.w_keys()
return space.call_method(w_keys, '__reversed__')
+ def nondescr_delitem_if_value_is(self, space, w_key, w_value):
+ """Not exposed directly to app-level, but used by
+ _weakref._remove_dead_weakref and via __pypy__.delitem_if_value_is().
+ """
+ strategy = self.ensure_object_strategy()
+ d = strategy.unerase(self.dstorage)
+ objectmodel.delitem_if_value_is(d, w_key, w_value)
+
def descr_viewitems(self, space):
"""D.viewitems() -> a set-like object providing a view on D's items"""
return W_DictViewItemsObject(space, self)
@@ -350,11 +358,12 @@
F: D[k] = F[k]"""
init_or_update(space, self, __args__, 'dict.update')
- def ensure_object_strategy(self): # for cpyext
+ def ensure_object_strategy(self): # also called by cpyext
object_strategy = self.space.fromcache(ObjectDictStrategy)
strategy = self.get_strategy()
if strategy is not object_strategy:
strategy.switch_to_object_strategy(self)
+ return object_strategy
class W_DictObject(W_DictMultiObject):
diff --git a/pypy/objspace/std/test/test_dictmultiobject.py b/pypy/objspace/std/test/test_dictmultiobject.py
--- a/pypy/objspace/std/test/test_dictmultiobject.py
+++ b/pypy/objspace/std/test/test_dictmultiobject.py
@@ -271,6 +271,20 @@
del d[key]
raises(RuntimeError, it.next)
+ def test_delitem_if_value_is(self):
+ import __pypy__
+ class X:
+ pass
+ x2 = X()
+ x3 = X()
+ d = {2: x2, 3: x3}
+ __pypy__.delitem_if_value_is(d, 2, x3)
+ assert d == {2: x2, 3: x3}
+ __pypy__.delitem_if_value_is(d, 2, x2)
+ assert d == {3: x3}
+ __pypy__.delitem_if_value_is(d, 2, x3)
+ assert d == {3: x3}
+
def test_keys(self):
d = {1: 2, 3: 4}
kys = d.keys()
More information about the pypy-commit
mailing list