Skip to content

Commit 5d1d4e3

Browse files
bpo-36607: Eliminate RuntimeError raised by asyncio.all_tasks() (GH-13971)
If internal tasks weak set is changed by another thread during iteration. https://bugs.python.org/issue36607 (cherry picked from commit 65aa64f) Co-authored-by: Andrew Svetlov <[email protected]>
1 parent 1b615b2 commit 5d1d4e3

File tree

2 files changed

+34
-6
lines changed

2 files changed

+34
-6
lines changed

Lib/asyncio/tasks.py

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,22 @@ def all_tasks(loop=None):
3535
"""Return a set of all tasks for the loop."""
3636
if loop is None:
3737
loop = events.get_running_loop()
38-
# NB: set(_all_tasks) is required to protect
39-
# from https://bugs.python.org/issue34970 bug
40-
return {t for t in list(_all_tasks)
38+
# Looping over a WeakSet (_all_tasks) isn't safe as it can be updated from another
39+
# thread while we do so. Therefore we cast it to list prior to filtering. The list
40+
# cast itself requires iteration, so we repeat it several times ignoring
41+
# RuntimeErrors (which are not very likely to occur). See issues 34970 and 36607 for
42+
# details.
43+
i = 0
44+
while True:
45+
try:
46+
tasks = list(_all_tasks)
47+
except RuntimeError:
48+
i += 1
49+
if i >= 1000:
50+
raise
51+
else:
52+
break
53+
return {t for t in tasks
4154
if futures._get_loop(t) is loop and not t.done()}
4255

4356

@@ -47,9 +60,22 @@ def _all_tasks_compat(loop=None):
4760
# method.
4861
if loop is None:
4962
loop = events.get_event_loop()
50-
# NB: set(_all_tasks) is required to protect
51-
# from https://bugs.python.org/issue34970 bug
52-
return {t for t in list(_all_tasks) if futures._get_loop(t) is loop}
63+
# Looping over a WeakSet (_all_tasks) isn't safe as it can be updated from another
64+
# thread while we do so. Therefore we cast it to list prior to filtering. The list
65+
# cast itself requires iteration, so we repeat it several times ignoring
66+
# RuntimeErrors (which are not very likely to occur). See issues 34970 and 36607 for
67+
# details.
68+
i = 0
69+
while True:
70+
try:
71+
tasks = list(_all_tasks)
72+
except RuntimeError:
73+
i += 1
74+
if i >= 1000:
75+
raise
76+
else:
77+
break
78+
return {t for t in tasks if futures._get_loop(t) is loop}
5379

5480

5581
class Task(futures._PyFuture): # Inherit Python Task implementation
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Eliminate :exc:`RuntimeError` raised by :func:`asyncio.all_tasks()` if
2+
internal tasks weak set is changed by another thread during iteration.

0 commit comments

Comments
 (0)