[pypy-commit] pypy py3.5: hg merge default
arigo
pypy.commits at gmail.com
Sun Feb 5 16:03:39 EST 2017
Author: Armin Rigo <arigo at 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)
+ at 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
More information about the pypy-commit
mailing list