diff --git a/Lib/weakref.py b/Lib/weakref.py --- a/Lib/weakref.py +++ b/Lib/weakref.py @@ -108,13 +108,13 @@ class WeakValueDictionary(collections.Mu def remove(wr, selfref=ref(self)): self = selfref() if self is not None: - if self._iterating: - self._pending_removals.append(wr.key) - else: - del self.data[wr.key] + # This callback can be called at any point by the GC, + # so just queue the key for removal to avoid race conditions + # (issue #28427) + self._pending_removals.add(wr.key) self._remove = remove - # A list of keys to be removed - self._pending_removals = [] + # A set of keys to be removed + self._pending_removals = set() self._iterating = set() self.data = d = {} self.update(*args, **kw) @@ -125,9 +125,14 @@ class WeakValueDictionary(collections.Mu # 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() + # An other value with the same key may have been set in the meantime + if d[key]() is None: + del d[key] def __getitem__(self, key): + if self._pending_removals: + self._commit_removals() o = self.data[key]() if o is None: raise KeyError(key) @@ -140,9 +145,13 @@ class WeakValueDictionary(collections.Mu del self.data[key] def __len__(self): - return len(self.data) - len(self._pending_removals) + if self._pending_removals: + self._commit_removals() + return len(self.data) def __contains__(self, key): + if self._pending_removals: + self._commit_removals() try: o = self.data[key]() except KeyError: @@ -158,6 +167,8 @@ class WeakValueDictionary(collections.Mu self.data[key] = KeyedRef(value, self._remove, key) def copy(self): + if self._pending_removals: + self._commit_removals() new = WeakValueDictionary() for key, wr in self.data.items(): o = wr() @@ -169,6 +180,8 @@ class WeakValueDictionary(collections.Mu def __deepcopy__(self, memo): from copy import deepcopy + if self._pending_removals: + self._commit_removals() new = self.__class__() for key, wr in self.data.items(): o = wr() @@ -177,6 +190,8 @@ class WeakValueDictionary(collections.Mu return new def get(self, key, default=None): + if self._pending_removals: + self._commit_removals() try: wr = self.data[key] except KeyError: @@ -190,6 +205,8 @@ class WeakValueDictionary(collections.Mu return o def items(self): + if self._pending_removals: + self._commit_removals() with _IterationGuard(self): for k, wr in self.data.items(): v = wr() @@ -197,6 +214,8 @@ class WeakValueDictionary(collections.Mu yield k, v def keys(self): + if self._pending_removals: + self._commit_removals() with _IterationGuard(self): for k, wr in self.data.items(): if wr() is not None: @@ -214,10 +233,14 @@ class WeakValueDictionary(collections.Mu keep the values around longer than needed. """ + if self._pending_removals: + self._commit_removals() with _IterationGuard(self): yield from self.data.values() def values(self): + if self._pending_removals: + self._commit_removals() with _IterationGuard(self): for wr in self.data.values(): obj = wr() @@ -248,11 +271,11 @@ class WeakValueDictionary(collections.Mu return o def setdefault(self, key, default=None): + if self._pending_removals: + self._commit_removals() try: wr = self.data[key] except KeyError: - if self._pending_removals: - self._commit_removals() self.data[key] = KeyedRef(default, self._remove, key) return default else: @@ -287,6 +310,8 @@ class WeakValueDictionary(collections.Mu keep the values around longer than needed. """ + if self._pending_removals: + self._commit_removals() return list(self.data.values())