mirror of
https://github.com/python/cpython.git
synced 2025-09-21 16:10:33 +00:00
gh-123089: Make weakref.WeakSet safe against concurrent mutations while it is being iterated (#123279)
* Make `weakref.WeakSet` safe against concurrent mutations while it is being iterated. `_IterationGuard` is no longer used for `WeakSet`, it now relies on copying the underlying set which is an atomic operation while iterating so that it can be modified by other threads.
This commit is contained in:
parent
6754566a51
commit
03f5abf15a
2 changed files with 11 additions and 43 deletions
|
@ -36,33 +36,18 @@ class _IterationGuard:
|
||||||
class WeakSet:
|
class WeakSet:
|
||||||
def __init__(self, data=None):
|
def __init__(self, data=None):
|
||||||
self.data = set()
|
self.data = set()
|
||||||
|
|
||||||
def _remove(item, selfref=ref(self)):
|
def _remove(item, selfref=ref(self)):
|
||||||
self = selfref()
|
self = selfref()
|
||||||
if self is not None:
|
if self is not None:
|
||||||
if self._iterating:
|
|
||||||
self._pending_removals.append(item)
|
|
||||||
else:
|
|
||||||
self.data.discard(item)
|
self.data.discard(item)
|
||||||
|
|
||||||
self._remove = _remove
|
self._remove = _remove
|
||||||
# A list of keys to be removed
|
|
||||||
self._pending_removals = []
|
|
||||||
self._iterating = set()
|
|
||||||
if data is not None:
|
if data is not None:
|
||||||
self.update(data)
|
self.update(data)
|
||||||
|
|
||||||
def _commit_removals(self):
|
|
||||||
pop = self._pending_removals.pop
|
|
||||||
discard = self.data.discard
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
item = pop()
|
|
||||||
except IndexError:
|
|
||||||
return
|
|
||||||
discard(item)
|
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
with _IterationGuard(self):
|
for itemref in self.data.copy():
|
||||||
for itemref in self.data:
|
|
||||||
item = itemref()
|
item = itemref()
|
||||||
if item is not None:
|
if item is not None:
|
||||||
# Caveat: the iterator will keep a strong reference to
|
# Caveat: the iterator will keep a strong reference to
|
||||||
|
@ -70,7 +55,7 @@ class WeakSet:
|
||||||
yield item
|
yield item
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
return len(self.data) - len(self._pending_removals)
|
return len(self.data)
|
||||||
|
|
||||||
def __contains__(self, item):
|
def __contains__(self, item):
|
||||||
try:
|
try:
|
||||||
|
@ -83,21 +68,15 @@ class WeakSet:
|
||||||
return self.__class__, (list(self),), self.__getstate__()
|
return self.__class__, (list(self),), self.__getstate__()
|
||||||
|
|
||||||
def add(self, item):
|
def add(self, item):
|
||||||
if self._pending_removals:
|
|
||||||
self._commit_removals()
|
|
||||||
self.data.add(ref(item, self._remove))
|
self.data.add(ref(item, self._remove))
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
if self._pending_removals:
|
|
||||||
self._commit_removals()
|
|
||||||
self.data.clear()
|
self.data.clear()
|
||||||
|
|
||||||
def copy(self):
|
def copy(self):
|
||||||
return self.__class__(self)
|
return self.__class__(self)
|
||||||
|
|
||||||
def pop(self):
|
def pop(self):
|
||||||
if self._pending_removals:
|
|
||||||
self._commit_removals()
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
itemref = self.data.pop()
|
itemref = self.data.pop()
|
||||||
|
@ -108,18 +87,12 @@ class WeakSet:
|
||||||
return item
|
return item
|
||||||
|
|
||||||
def remove(self, item):
|
def remove(self, item):
|
||||||
if self._pending_removals:
|
|
||||||
self._commit_removals()
|
|
||||||
self.data.remove(ref(item))
|
self.data.remove(ref(item))
|
||||||
|
|
||||||
def discard(self, item):
|
def discard(self, item):
|
||||||
if self._pending_removals:
|
|
||||||
self._commit_removals()
|
|
||||||
self.data.discard(ref(item))
|
self.data.discard(ref(item))
|
||||||
|
|
||||||
def update(self, other):
|
def update(self, other):
|
||||||
if self._pending_removals:
|
|
||||||
self._commit_removals()
|
|
||||||
for element in other:
|
for element in other:
|
||||||
self.add(element)
|
self.add(element)
|
||||||
|
|
||||||
|
@ -136,8 +109,6 @@ class WeakSet:
|
||||||
def difference_update(self, other):
|
def difference_update(self, other):
|
||||||
self.__isub__(other)
|
self.__isub__(other)
|
||||||
def __isub__(self, other):
|
def __isub__(self, other):
|
||||||
if self._pending_removals:
|
|
||||||
self._commit_removals()
|
|
||||||
if self is other:
|
if self is other:
|
||||||
self.data.clear()
|
self.data.clear()
|
||||||
else:
|
else:
|
||||||
|
@ -151,8 +122,6 @@ class WeakSet:
|
||||||
def intersection_update(self, other):
|
def intersection_update(self, other):
|
||||||
self.__iand__(other)
|
self.__iand__(other)
|
||||||
def __iand__(self, other):
|
def __iand__(self, other):
|
||||||
if self._pending_removals:
|
|
||||||
self._commit_removals()
|
|
||||||
self.data.intersection_update(ref(item) for item in other)
|
self.data.intersection_update(ref(item) for item in other)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
@ -184,8 +153,6 @@ class WeakSet:
|
||||||
def symmetric_difference_update(self, other):
|
def symmetric_difference_update(self, other):
|
||||||
self.__ixor__(other)
|
self.__ixor__(other)
|
||||||
def __ixor__(self, other):
|
def __ixor__(self, other):
|
||||||
if self._pending_removals:
|
|
||||||
self._commit_removals()
|
|
||||||
if self is other:
|
if self is other:
|
||||||
self.data.clear()
|
self.data.clear()
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Make :class:`weakref.WeakSet` safe against concurrent mutations while it is being iterated. Patch by Kumar Aditya.
|
Loading…
Add table
Add a link
Reference in a new issue