]> granicus.if.org Git - python/commitdiff
Issue #28427: old keys should not remove new values from
authorAntoine Pitrou <solipsis@pitrou.net>
Tue, 27 Dec 2016 13:34:54 +0000 (14:34 +0100)
committerAntoine Pitrou <solipsis@pitrou.net>
Tue, 27 Dec 2016 13:34:54 +0000 (14:34 +0100)
WeakValueDictionary when collecting from another thread.

1  2 
Include/dictobject.h
Misc/NEWS
Objects/dictobject.c

Simple merge
diff --cc Misc/NEWS
index a80a3db8d4701a7bba89e427317a1c6d23d215ae,767f3e3b83745049604b6bd4106f676deb7631c6..b5ec17e30d12730a98f14c7ef11b6cdd5fb300de
+++ b/Misc/NEWS
@@@ -208,8 -40,14 +208,11 @@@ Core and Builtin
  Library
  -------
  
+ - Issue #28427: old keys should not remove new values from
+   WeakValueDictionary when collecting from another thread.
  - Issue 28923: Remove editor artifacts from Tix.py.
  
 -- Issue #29055:  Neaten-up empty population error on random.choice()
 -  by suppressing the upstream exception.
 -
  - Issue #28871: Fixed a crash when deallocate deep ElementTree.
  
  - Issue #19542: Fix bugs in WeakValueDictionary.setdefault() and
index 6a7e8319440a756f6ef12e2ef68c9aaf913c9eee,64941937e748ad8beb76100d9cd744c11098acd9..baef58942798f231e34245709301a76049cf96a4
@@@ -1583,6 -1591,30 +1583,28 @@@ _PyDict_SetItem_KnownHash(PyObject *op
      return insertdict(mp, key, hash, value);
  }
  
 -               PyObject **value_addr)
+ static int
+ delitem_common(PyDictObject *mp, Py_ssize_t hashpos, Py_ssize_t ix,
 -    PyObject *old_key, *old_value;
++               PyObject *old_value)
+ {
 -    old_value = *value_addr;
 -    assert(old_value != NULL);
 -    *value_addr = NULL;
++    PyObject *old_key;
+     PyDictKeyEntry *ep;
+     mp->ma_used--;
+     mp->ma_version_tag = DICT_NEXT_VERSION();
+     ep = &DK_ENTRIES(mp->ma_keys)[ix];
+     dk_set_index(mp->ma_keys, hashpos, DKIX_DUMMY);
+     ENSURE_ALLOWS_DELETIONS(mp);
+     old_key = ep->me_key;
+     ep->me_key = NULL;
++    ep->me_value = NULL;
+     Py_DECREF(old_key);
+     Py_DECREF(old_value);
+     assert(_PyDict_CheckConsistency(mp));
+     return 0;
+ }
  int
  PyDict_DelItem(PyObject *op, PyObject *key)
  {
@@@ -1604,8 -1635,7 +1625,7 @@@ _PyDict_DelItem_KnownHash(PyObject *op
  {
      Py_ssize_t hashpos, ix;
      PyDictObject *mp;
-     PyDictKeyEntry *ep;
-     PyObject *old_key, *old_value;
 -    PyObject **value_addr;
++    PyObject *old_value;
  
      if (!PyDict_Check(op)) {
          PyErr_BadInternalCall();
          if (dictresize(mp, DK_SIZE(mp->ma_keys))) {
              return -1;
          }
 -        ix = (mp->ma_keys->dk_lookup)(mp, key, hash, &value_addr, &hashpos);
 +        ix = (mp->ma_keys->dk_lookup)(mp, key, hash, &old_value, &hashpos);
          assert(ix >= 0);
      }
 -    return delitem_common(mp, hashpos, ix, value_addr);
 +
-     assert(old_value != NULL);
-     mp->ma_used--;
-     mp->ma_version_tag = DICT_NEXT_VERSION();
-     ep = &DK_ENTRIES(mp->ma_keys)[ix];
-     dk_set_index(mp->ma_keys, hashpos, DKIX_DUMMY);
-     ENSURE_ALLOWS_DELETIONS(mp);
-     old_key = ep->me_key;
-     ep->me_key = NULL;
-     ep->me_value = NULL;
-     Py_DECREF(old_key);
-     Py_DECREF(old_value);
++    return delitem_common(mp, hashpos, ix, old_value);
+ }
  
-     assert(_PyDict_CheckConsistency(mp));
-     return 0;
+ /* This function promises that the predicate -> deletion sequence is atomic
+  * (i.e. protected by the GIL), assuming the predicate itself doesn't
+  * release the GIL.
+  */
+ int
+ _PyDict_DelItemIf(PyObject *op, PyObject *key,
+                   int (*predicate)(PyObject *value))
+ {
+     Py_ssize_t hashpos, ix;
+     PyDictObject *mp;
+     Py_hash_t hash;
 -    PyObject **value_addr;
++    PyObject *old_value;
+     int res;
+     if (!PyDict_Check(op)) {
+         PyErr_BadInternalCall();
+         return -1;
+     }
+     assert(key);
+     hash = PyObject_Hash(key);
+     if (hash == -1)
+         return -1;
+     mp = (PyDictObject *)op;
 -    ix = (mp->ma_keys->dk_lookup)(mp, key, hash, &value_addr, &hashpos);
++    ix = (mp->ma_keys->dk_lookup)(mp, key, hash, &old_value, &hashpos);
+     if (ix == DKIX_ERROR)
+         return -1;
 -    if (ix == DKIX_EMPTY || *value_addr == NULL) {
++    if (ix == DKIX_EMPTY || old_value == NULL) {
+         _PyErr_SetKeyError(key);
+         return -1;
+     }
+     assert(dk_get_index(mp->ma_keys, hashpos) == ix);
+     // Split table doesn't allow deletion.  Combine it.
+     if (_PyDict_HasSplitTable(mp)) {
+         if (dictresize(mp, DK_SIZE(mp->ma_keys))) {
+             return -1;
+         }
 -        ix = (mp->ma_keys->dk_lookup)(mp, key, hash, &value_addr, &hashpos);
++        ix = (mp->ma_keys->dk_lookup)(mp, key, hash, &old_value, &hashpos);
+         assert(ix >= 0);
+     }
 -    res = predicate(*value_addr);
++    res = predicate(old_value);
+     if (res == -1)
+         return -1;
+     if (res > 0)
 -        return delitem_common(mp, hashpos, ix, value_addr);
++        return delitem_common(mp, hashpos, ix, old_value);
+     else
+         return 0;
  }
  
  void
  PyDict_Clear(PyObject *op)
  {