Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 11fae63

Browse filesBrowse files
committed
Maybe
1 parent bc87733 commit 11fae63
Copy full SHA for 11fae63

File tree

1 file changed

+95
-10
lines changed
Filter options

1 file changed

+95
-10
lines changed

‎Lib/asyncio/tools.py

Copy file name to clipboardExpand all lines: Lib/asyncio/tools.py
+95-10Lines changed: 95 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -56,18 +56,93 @@ def _cor_node(parent_key, frame_name):
5656

5757

5858
def _roots(id2label, children):
59-
roots = [n for n, lbl in id2label.items() if lbl == "Task-1"]
60-
if roots:
61-
return roots
59+
"""Return every task that is *not awaited by anybody*."""
6260
all_children = {c for kids in children.values() for c in kids}
6361
return [n for n in id2label if n not in all_children]
6462

63+
# ─── helpers for _roots() ─────────────────────────────────────────
64+
from collections import defaultdict
65+
66+
def _roots(id2label, children):
67+
"""
68+
Return one root per *source* strongly-connected component (SCC).
69+
70+
• Build the graph that contains **only tasks** as nodes and edges
71+
parent-task ─▶ child-task (ignore coroutine frames).
72+
73+
• Collapse it into SCCs with Tarjan (linear time).
74+
75+
• For every component whose condensation-DAG in-degree is 0, choose a
76+
stable representative (lexicographically-smallest label, fallback to
77+
smallest object-id) and return that list.
78+
"""
79+
TASK = NodeType.TASK
80+
task_nodes = [n for n in id2label if n[0] == TASK]
81+
82+
# ------------ adjacency list among *tasks only* -----------------
83+
adj = defaultdict(list)
84+
for p in task_nodes:
85+
adj[p] = [c for c in children.get(p, []) if c[0] == TASK]
86+
87+
# ------------ Tarjan’s algorithm --------------------------------
88+
index = 0
89+
stack, on_stack = [], set()
90+
node_index, low = {}, {}
91+
comp_of = {} # node -> comp-id
92+
comps = defaultdict(list) # comp-id -> [members]
93+
94+
def strong(v):
95+
nonlocal index
96+
node_index[v] = low[v] = index
97+
index += 1
98+
stack.append(v)
99+
on_stack.add(v)
100+
101+
for w in adj[v]:
102+
if w not in node_index:
103+
strong(w)
104+
low[v] = min(low[v], low[w])
105+
elif w in on_stack:
106+
low[v] = min(low[v], node_index[w])
107+
108+
if low[v] == node_index[v]: # root of an SCC
109+
while True:
110+
w = stack.pop()
111+
on_stack.remove(w)
112+
comp_of[w] = v # use root-node as comp-id
113+
comps[v].append(w)
114+
if w == v:
115+
break
116+
117+
for v in task_nodes:
118+
if v not in node_index:
119+
strong(v)
120+
121+
# ------------ condensation DAG in-degrees -----------------------
122+
indeg = defaultdict(int)
123+
for p in task_nodes:
124+
cp = comp_of[p]
125+
for q in adj[p]:
126+
cq = comp_of[q]
127+
if cp != cq:
128+
indeg[cq] += 1
129+
130+
# ------------ choose one representative per source-SCC ----------
131+
roots = []
132+
for cid, members in comps.items():
133+
if indeg[cid] == 0: # source component
134+
roots.append(min(
135+
members,
136+
key=lambda n: (id2label[n], n[1]) # stable pick
137+
))
138+
return roots
139+
65140

66141
# ─── PRINT TREE FUNCTION ───────────────────────────────────────
67142
def print_async_tree(result, task_emoji="(T)", cor_emoji="", printer=print):
68143
"""
69144
Pretty-print the async call tree produced by `get_all_async_stacks()`,
70-
prefixing tasks with *task_emoji* and coroutine frames with *cor_emoji*.
145+
coping safely with cycles.
71146
"""
72147
id2name, awaits = _index(result)
73148
labels, children = _build_tree(id2name, awaits)
@@ -76,20 +151,29 @@ def pretty(node):
76151
flag = task_emoji if node[0] == NodeType.TASK else cor_emoji
77152
return f"{flag} {labels[node]}"
78153

79-
def render(node, prefix="", last=True, buf=None):
154+
def render(node, prefix="", last=True, buf=None, ancestry=frozenset()):
155+
"""
156+
DFS renderer that stops if *node* already occurs in *ancestry*
157+
(i.e. we just found a cycle).
158+
"""
80159
if buf is None:
81160
buf = []
161+
162+
if node in ancestry:
163+
buf.append(f"{prefix}{'└── ' if last else '├── '}{pretty(node)} (cycle)")
164+
return buf
165+
82166
buf.append(f"{prefix}{'└── ' if last else '├── '}{pretty(node)}")
83167
new_pref = prefix + (" " if last else "│ ")
84168
kids = children.get(node, [])
85169
for i, kid in enumerate(kids):
86-
render(kid, new_pref, i == len(kids) - 1, buf)
170+
render(kid, new_pref, i == len(kids) - 1, buf, ancestry | {node})
87171
return buf
88172

89-
result = []
90-
for r, root in enumerate(_roots(labels, children)):
91-
result.append(render(root))
92-
return result
173+
forest = []
174+
for root in _roots(labels, children):
175+
forest.append(render(root))
176+
return forest
93177

94178

95179
def build_task_table(result):
@@ -124,6 +208,7 @@ def build_task_table(result):
124208

125209
try:
126210
tasks = get_all_awaited_by(args.pid)
211+
print(tasks)
127212
except RuntimeError as e:
128213
print(f"Error retrieving tasks: {e}")
129214
sys.exit(1)

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.