Skip to content

Commit 5cbca02

Browse files
authored
[3.6] Fix a c.f.as_completed() refleak previously introduced in bpo-27144 (GH-3270) (#3271)
(cherry picked from commit 2ef3760)
1 parent 98bbeb7 commit 5cbca02

File tree

2 files changed

+20
-8
lines changed

2 files changed

+20
-8
lines changed

Lib/concurrent/futures/_base.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -171,15 +171,24 @@ def _create_and_install_waiters(fs, return_when):
171171
return waiter
172172

173173

174-
def _yield_and_decref(fs, ref_collect):
174+
def _yield_finished_futures(fs, waiter, ref_collect):
175175
"""
176-
Iterate on the list *fs*, yielding objects one by one in reverse order.
177-
Before yielding an object, it is removed from each set in
178-
the collection of sets *ref_collect*.
176+
Iterate on the list *fs*, yielding finished futures one by one in
177+
reverse order.
178+
Before yielding a future, *waiter* is removed from its waiters
179+
and the future is removed from each set in the collection of sets
180+
*ref_collect*.
181+
182+
The aim of this function is to avoid keeping stale references after
183+
the future is yielded and before the iterator resumes.
179184
"""
180185
while fs:
186+
f = fs[-1]
181187
for futures_set in ref_collect:
182-
futures_set.remove(fs[-1])
188+
futures_set.remove(f)
189+
with f._condition:
190+
f._waiters.remove(waiter)
191+
del f
183192
# Careful not to keep a reference to the popped value
184193
yield fs.pop()
185194

@@ -216,7 +225,8 @@ def as_completed(fs, timeout=None):
216225
waiter = _create_and_install_waiters(fs, _AS_COMPLETED)
217226
finished = list(finished)
218227
try:
219-
yield from _yield_and_decref(finished, ref_collect=(fs,))
228+
yield from _yield_finished_futures(finished, waiter,
229+
ref_collect=(fs,))
220230

221231
while pending:
222232
if timeout is None:
@@ -237,9 +247,11 @@ def as_completed(fs, timeout=None):
237247

238248
# reverse to keep finishing order
239249
finished.reverse()
240-
yield from _yield_and_decref(finished, ref_collect=(fs, pending))
250+
yield from _yield_finished_futures(finished, waiter,
251+
ref_collect=(fs, pending))
241252

242253
finally:
254+
# Remove waiter from unfinished futures
243255
for f in fs:
244256
with f._condition:
245257
f._waiters.remove(waiter)

Lib/test/test_concurrent_futures.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,7 @@ def test_free_reference_yielded_future(self):
406406
# to finished futures.
407407
futures_list = [Future() for _ in range(8)]
408408
futures_list.append(create_future(state=CANCELLED_AND_NOTIFIED))
409-
futures_list.append(create_future(state=SUCCESSFUL_FUTURE))
409+
futures_list.append(create_future(state=FINISHED, result=42))
410410

411411
with self.assertRaises(futures.TimeoutError):
412412
for future in futures.as_completed(futures_list, timeout=0):

0 commit comments

Comments
 (0)