[FIX] unsafe listification of weakref in Python < 2.7.4

Fixes #966

* As a preallocation optimization, ``list()`` calls ``__len__`` on its
  parameter if it's available
* Before Python 2.7.4, WeakSet has a bug[0] where ``len()`` is unsafe: it is
  done by iteration and weakrefs may be removed from the underlying set during
  the iteration

As a result, the safety feature of listifying a WeakSet to ensure we have
strong refs on all items during iteration may blow up.

Wrapping the weakset in a ``iter()`` makes ``__len__()`` invisible and ensures
we're within the IterationGuard[1].

Which now that I think about it means we *should* be able to safely iterate
weaksets in the first place and may not have needed to listify them...

[0] http://bugs.python.org/issue14159
[1] http://hg.python.org/cpython/file/b6acfbe2bdbe/Lib/_weakrefset.py#l58
This commit is contained in:
Xavier Morel 2014-07-07 13:51:53 +02:00
parent cd7e9a98d4
commit ac282e0294
2 changed files with 4 additions and 4 deletions

View File

@ -756,7 +756,7 @@ class Environment(object):
"""
if not spec:
return
for env in list(self.all):
for env in list(iter(self.all)):
c = env.cache
for field, ids in spec:
if ids is None:
@ -769,7 +769,7 @@ class Environment(object):
def invalidate_all(self):
""" Clear the cache of all environments. """
for env in list(self.all):
for env in list(iter(self.all)):
env.cache.clear()
env.prefetch.clear()
env.computed.clear()

View File

@ -5415,7 +5415,7 @@ class BaseModel(object):
""" If `field` must be recomputed on some record in `self`, return the
corresponding records that must be recomputed.
"""
for env in [self.env] + list(self.env.all):
for env in [self.env] + list(iter(self.env.all)):
if env.todo.get(field) and env.todo[field] & self:
return env.todo[field]
@ -5437,7 +5437,7 @@ class BaseModel(object):
""" Recompute stored function fields. The fields and records to
recompute have been determined by method :meth:`modified`.
"""
for env in list(self.env.all):
for env in list(iter(self.env.all)):
while env.todo:
field, recs = next(env.todo.iteritems())
# evaluate the fields to recompute, and save them to database