summaryrefslogtreecommitdiff
path: root/Lib/_weakrefset.py
diff options
context:
space:
mode:
authorAntoine Pitrou <solipsis@pitrou.net>2010-01-08 17:54:23 +0000
committerAntoine Pitrou <solipsis@pitrou.net>2010-01-08 17:54:23 +0000
commit527de5406d09fd65ec3bb0e1af81de45597eae51 (patch)
tree219f720ccf8d868ef0d5a84e96e1c2bd2d57a694 /Lib/_weakrefset.py
parenta6ad34e934842f1802abbacb25c4878f204ea7a8 (diff)
downloadcpython-527de5406d09fd65ec3bb0e1af81de45597eae51.tar.gz
Issue #7105: Make WeakKeyDictionary and WeakValueDictionary robust against
the destruction of weakref'ed objects while iterating.
Diffstat (limited to 'Lib/_weakrefset.py')
-rw-r--r--Lib/_weakrefset.py74
1 files changed, 69 insertions, 5 deletions
diff --git a/Lib/_weakrefset.py b/Lib/_weakrefset.py
index addc7afeef..3de3bda695 100644
--- a/Lib/_weakrefset.py
+++ b/Lib/_weakrefset.py
@@ -6,22 +6,61 @@ from _weakref import ref
__all__ = ['WeakSet']
+
+class _IterationGuard:
+ # This context manager registers itself in the current iterators of the
+ # weak container, such as to delay all removals until the context manager
+ # exits.
+ # This technique should be relatively thread-safe (since sets are).
+
+ def __init__(self, weakcontainer):
+ # Don't create cycles
+ self.weakcontainer = ref(weakcontainer)
+
+ def __enter__(self):
+ w = self.weakcontainer()
+ if w is not None:
+ w._iterating.add(self)
+ return self
+
+ def __exit__(self, e, t, b):
+ w = self.weakcontainer()
+ if w is not None:
+ s = w._iterating
+ s.remove(self)
+ if not s:
+ w._commit_removals()
+
+
class WeakSet:
def __init__(self, data=None):
self.data = set()
def _remove(item, selfref=ref(self)):
self = selfref()
if self is not None:
- self.data.discard(item)
+ if self._iterating:
+ self._pending_removals.append(item)
+ else:
+ self.data.discard(item)
self._remove = _remove
+ # A list of keys to be removed
+ self._pending_removals = []
+ self._iterating = set()
if data is not None:
self.update(data)
+ def _commit_removals(self):
+ l = self._pending_removals
+ discard = self.data.discard
+ while l:
+ discard(l.pop())
+
def __iter__(self):
- for itemref in self.data:
- item = itemref()
- if item is not None:
- yield item
+ with _IterationGuard(self):
+ for itemref in self.data:
+ item = itemref()
+ if item is not None:
+ yield item
def __len__(self):
return sum(x() is not None for x in self.data)
@@ -34,15 +73,21 @@ class WeakSet:
getattr(self, '__dict__', None))
def add(self, item):
+ if self._pending_removals:
+ self._commit_removals()
self.data.add(ref(item, self._remove))
def clear(self):
+ if self._pending_removals:
+ self._commit_removals()
self.data.clear()
def copy(self):
return self.__class__(self)
def pop(self):
+ if self._pending_removals:
+ self._commit_removals()
while True:
try:
itemref = self.data.pop()
@@ -53,17 +98,24 @@ class WeakSet:
return item
def remove(self, item):
+ if self._pending_removals:
+ self._commit_removals()
self.data.remove(ref(item))
def discard(self, item):
+ if self._pending_removals:
+ self._commit_removals()
self.data.discard(ref(item))
def update(self, other):
+ if self._pending_removals:
+ self._commit_removals()
if isinstance(other, self.__class__):
self.data.update(other.data)
else:
for element in other:
self.add(element)
+
def __ior__(self, other):
self.update(other)
return self
@@ -82,11 +134,15 @@ class WeakSet:
__sub__ = difference
def difference_update(self, other):
+ if self._pending_removals:
+ self._commit_removals()
if self is other:
self.data.clear()
else:
self.data.difference_update(ref(item) for item in other)
def __isub__(self, other):
+ if self._pending_removals:
+ self._commit_removals()
if self is other:
self.data.clear()
else:
@@ -98,8 +154,12 @@ class WeakSet:
__and__ = intersection
def intersection_update(self, other):
+ if self._pending_removals:
+ self._commit_removals()
self.data.intersection_update(ref(item) for item in other)
def __iand__(self, other):
+ if self._pending_removals:
+ self._commit_removals()
self.data.intersection_update(ref(item) for item in other)
return self
@@ -127,11 +187,15 @@ class WeakSet:
__xor__ = symmetric_difference
def symmetric_difference_update(self, other):
+ if self._pending_removals:
+ self._commit_removals()
if self is other:
self.data.clear()
else:
self.data.symmetric_difference_update(ref(item) for item in other)
def __ixor__(self, other):
+ if self._pending_removals:
+ self._commit_removals()
if self is other:
self.data.clear()
else: