11from abc import ABC , abstractmethod
2+ from collections import deque
23
34from _remote_debugging import FrameInfo
45
@@ -71,11 +72,15 @@ def _find_leaf_tasks(self, child_to_parents, all_task_ids):
7172
7273 def _build_linear_stacks (self , leaf_task_ids , task_map , child_to_parents ):
7374 for leaf_id in leaf_task_ids :
74- # BFS queue: (current_task_id, frames_so_far, path_for_cycle_detection)
75- queue = [(leaf_id , [], frozenset ())]
75+ # Track yielded paths to avoid duplicates from multiple parent paths
76+ yielded_paths = set ()
77+
78+ # BFS queue: (current_task_id, frames_so_far, path_for_cycle_detection, thread_id)
79+ # Use deque for O(1) popleft instead of O(n) list.pop(0)
80+ queue = deque ([(leaf_id , [], frozenset (), None )])
7681
7782 while queue :
78- current_id , frames , path = queue .pop ( 0 )
83+ current_id , frames , path , thread_id = queue .popleft ( )
7984
8085 # Cycle detection
8186 if current_id in path :
@@ -84,12 +89,20 @@ def _build_linear_stacks(self, leaf_task_ids, task_map, child_to_parents):
8489 # End of path (parent ID not in task_map)
8590 if current_id not in task_map :
8691 if frames :
87- _ , thread_id = task_map [leaf_id ]
88- yield frames , thread_id , leaf_id
92+ # Deduplicate yields based on path taken
93+ path_sig = frozenset (path )
94+ if path_sig not in yielded_paths :
95+ yielded_paths .add (path_sig )
96+ yield frames , thread_id , leaf_id
8997 continue
9098
9199 # Process current task
92100 task_info , tid = task_map [current_id ]
101+
102+ # Set thread_id from first task if not already set
103+ if thread_id is None :
104+ thread_id = tid
105+
93106 new_frames = list (frames )
94107 new_path = path | {current_id }
95108
@@ -107,9 +120,12 @@ def _build_linear_stacks(self, leaf_task_ids, task_map, child_to_parents):
107120 parent_ids = child_to_parents .get (current_id , [])
108121
109122 if not parent_ids :
110- # Root task - yield complete stack
111- yield new_frames , tid , leaf_id
123+ # Root task - yield complete stack (deduplicate)
124+ path_sig = frozenset (new_path )
125+ if path_sig not in yielded_paths :
126+ yielded_paths .add (path_sig )
127+ yield new_frames , thread_id , leaf_id
112128 else :
113129 # Continue to each parent (creates multiple paths if >1 parent)
114130 for parent_id in parent_ids :
115- queue .append ((parent_id , new_frames , new_path ))
131+ queue .append ((parent_id , new_frames , new_path , thread_id ))
0 commit comments