Skip to content

gh-135371: Fix asyncio task awaited_by semantics in remote debugging #135372

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 28 additions & 8 deletions Lib/asyncio/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def __init__(
# ─── indexing helpers ───────────────────────────────────────────
def _format_stack_entry(elem: tuple[str, str, int] | str) -> str:
if isinstance(elem, tuple):
fqname, path, line_no = elem
path, line_no, fqname = elem
return f"{fqname} {path}:{line_no}"

return elem
Expand All @@ -44,12 +44,12 @@ def _index(result):
awaits.append((parent_id, stack, tid))
return id2name, awaits


def _build_tree(id2name, awaits):
id2label = {(NodeType.TASK, tid): name for tid, name in id2name.items()}
children = defaultdict(list)
cor_names = defaultdict(dict) # (parent) -> {frame: node}
cor_id_seq = count(1)
task_innermost = {} # task_key -> innermost coroutine node

def _cor_node(parent_key, frame_name):
"""Return an existing or new (NodeType.COROUTINE, …) node under *parent_key*."""
Expand All @@ -62,18 +62,38 @@ def _cor_node(parent_key, frame_name):
bucket[frame_name] = node_key
return node_key

# lay down parent ➜ …frames… ➜ child paths
# First pass: build coroutine stacks under each task and track innermost frames
for parent_id, stack, child_id in awaits:
cur = (NodeType.TASK, parent_id)
child_key = (NodeType.TASK, child_id)
cur = child_key
for frame in reversed(stack): # outer-most → inner-most
cur = _cor_node(cur, frame)
child_key = (NodeType.TASK, child_id)
if child_key not in children[cur]:
children[cur].append(child_key)
# Track the innermost coroutine frame for this task
if stack: # Only if there's a stack
task_innermost[child_key] = cur

# Second pass: build parent -> child task relationships
for parent_id, stack, child_id in awaits:
if parent_id != 0: # Skip root tasks (no parent)
parent_key = (NodeType.TASK, parent_id)
child_key = (NodeType.TASK, child_id)

# Check if this is a task created from within a coroutine call
# by looking at the stack length - single frame suggests deep nesting
if len(stack) == 1 and parent_key in task_innermost:
# Single frame stack: child created from within innermost coroutine
innermost_parent = task_innermost[parent_key]
if child_key not in children[innermost_parent]:
children[innermost_parent].append(child_key)
else:
# Multi-frame stack: direct task-to-task relationship
if child_key not in children[parent_key]:
children[parent_key].append(child_key)

return id2label, children



def _roots(id2label, children):
all_children = {c for kids in children.values() for c in kids}
return [n for n in id2label if n not in all_children]
Expand Down Expand Up @@ -170,7 +190,7 @@ def build_task_table(result):
]
)
for stack, awaiter_id in awaited:
stack = [elem[0] if isinstance(elem, tuple) else elem for elem in stack]
stack = [elem[-1] if isinstance(elem, tuple) else elem for elem in stack]
coroutine_chain = " -> ".join(stack)
awaiter_name = id2name.get(awaiter_id, "Unknown")
table.append(
Expand Down
Loading
Loading