[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