Author: Armin Rigo <ar...@tunes.org> Branch: py3.5 Changeset: r89961:4bd2a3132a3f Date: 2017-02-05 22:01 +0100 http://bitbucket.org/pypy/pypy/changeset/4bd2a3132a3f/
Log: hg merge default 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 @@ -78,6 +78,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', 'move_to_end' : 'interp_dict.move_to_end', 'strategy' : 'interp_magic.strategy', # dict,set,list '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 @@ -59,3 +59,16 @@ if not isinstance(w_obj, W_DictMultiObject): raise OperationError(space.w_TypeError, space.w_None) return w_obj.nondescr_move_to_end(space, w_key, last) + +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 @@ -263,6 +263,14 @@ for i in range(len(keys_w)): self.setitem(keys_w[i], values_w[i]) + 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_clear(self, space): """D.clear() -> None. Remove all items from D.""" self.clear() @@ -314,11 +322,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 @@ -293,6 +293,20 @@ else: assert list(d) == [key] + other_keys + 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 = list(d.keys()) diff --git a/rpython/annotator/unaryop.py b/rpython/annotator/unaryop.py --- a/rpython/annotator/unaryop.py +++ b/rpython/annotator/unaryop.py @@ -575,6 +575,10 @@ pair(self, s_key).delitem() method_delitem_with_hash.can_only_throw = _dict_can_only_throw_keyerror + def method_delitem_if_value_is(self, s_key, s_value): + pair(self, s_key).setitem(s_value) + pair(self, s_key).delitem() + class __extend__(SomeOrderedDict): def method_move_to_end(self, s_key, s_last): diff --git a/rpython/rlib/objectmodel.py b/rpython/rlib/objectmodel.py --- a/rpython/rlib/objectmodel.py +++ b/rpython/rlib/objectmodel.py @@ -934,6 +934,20 @@ return d.delitem_with_hash(key, h) +@specialize.call_location() +def delitem_if_value_is(d, key, value): + """Same as 'if d.get(key) is value: del d[key]'. It is safe even in + case 'd' is an r_dict and the lookup involves callbacks that might + release the GIL.""" + if not we_are_translated(): + try: + if d[key] is value: + del d[key] + except KeyError: + pass + return + d.delitem_if_value_is(key, value) + def _untranslated_move_to_end(d, key, last): "NOT_RPYTHON" value = d.pop(key) diff --git a/rpython/rlib/test/test_objectmodel.py b/rpython/rlib/test/test_objectmodel.py --- a/rpython/rlib/test/test_objectmodel.py +++ b/rpython/rlib/test/test_objectmodel.py @@ -7,7 +7,7 @@ resizelist_hint, is_annotation_constant, always_inline, NOT_CONSTANT, iterkeys_with_hash, iteritems_with_hash, contains_with_hash, setitem_with_hash, getitem_with_hash, delitem_with_hash, import_from_mixin, - fetch_translated_config, try_inline, move_to_end) + fetch_translated_config, try_inline, delitem_if_value_is, move_to_end) from rpython.translator.translator import TranslationContext, graphof from rpython.rtyper.test.tool import BaseRtypingTest from rpython.rtyper.test.test_llinterp import interpret @@ -661,6 +661,24 @@ f(29) interpret(f, [27]) +def test_delitem_if_value_is(): + class X: + pass + def f(i): + x42 = X() + x612 = X() + d = {i + .5: x42, i + .6: x612} + delitem_if_value_is(d, i + .5, x612) + assert (i + .5) in d + delitem_if_value_is(d, i + .5, x42) + assert (i + .5) not in d + delitem_if_value_is(d, i + .5, x612) + assert (i + .5) not in d + return 0 + + f(29) + interpret(f, [27]) + def test_rdict_with_hash(): def f(i): d = r_dict(strange_key_eq, strange_key_hash) diff --git a/rpython/rtyper/lltypesystem/rordereddict.py b/rpython/rtyper/lltypesystem/rordereddict.py --- a/rpython/rtyper/lltypesystem/rordereddict.py +++ b/rpython/rtyper/lltypesystem/rordereddict.py @@ -407,6 +407,12 @@ hop.exception_is_here() hop.gendirectcall(ll_dict_delitem_with_hash, v_dict, v_key, v_hash) + def rtype_method_delitem_if_value_is(self, hop): + v_dict, v_key, v_value = hop.inputargs( + self, self.key_repr, self.value_repr) + hop.exception_cannot_occur() + hop.gendirectcall(ll_dict_delitem_if_value_is, v_dict, v_key, v_value) + def rtype_method_move_to_end(self, hop): v_dict, v_key, v_last = hop.inputargs( self, self.key_repr, lltype.Bool) @@ -821,6 +827,15 @@ raise KeyError _ll_dict_del(d, hash, index) +def ll_dict_delitem_if_value_is(d, key, value): + hash = d.keyhash(key) + index = d.lookup_function(d, key, hash, FLAG_LOOKUP) + if index < 0: + return + if d.entries[index].value != value: + return + _ll_dict_del(d, hash, index) + def _ll_dict_del_entry(d, index): d.entries.mark_deleted(index) d.num_live_items -= 1 _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit