Skip to content

Commit 11fae63

Browse files
committed
Maybe
1 parent bc87733 commit 11fae63

File tree

1 file changed

+95
-10
lines changed

1 file changed

+95
-10
lines changed

Lib/asyncio/tools.py

+95-10
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)