Skip to content

Commit 77cf769

Browse files
committed
gh-144881: Add retry logic to asyncio debugging tools
Transient errors can occur when attaching to a process that is actively using thread delegation (e.g. asyncio.to_thread). Add a retry loop to _get_awaited_by_tasks for RuntimeError, OSError, UnicodeDecodeError, and MemoryError, and expose --retries CLI flag on both `ps` and `pstree` subcommands (default: 3).
1 parent e11315d commit 77cf769

2 files changed

Lines changed: 43 additions & 16 deletions

File tree

Lib/asyncio/__main__.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,17 +162,29 @@ def interrupt(self) -> None:
162162
"ps", help="Display a table of all pending tasks in a process"
163163
)
164164
ps.add_argument("pid", type=int, help="Process ID to inspect")
165+
ps.add_argument(
166+
"--retries",
167+
type=int,
168+
default=3,
169+
help="Number of retries on transient attach errors (default: 3)",
170+
)
165171
pstree = subparsers.add_parser(
166172
"pstree", help="Display a tree of all pending tasks in a process"
167173
)
168174
pstree.add_argument("pid", type=int, help="Process ID to inspect")
175+
pstree.add_argument(
176+
"--retries",
177+
type=int,
178+
default=3,
179+
help="Number of retries on transient attach errors (default: 3)",
180+
)
169181
args = parser.parse_args()
170182
match args.command:
171183
case "ps":
172-
asyncio.tools.display_awaited_by_tasks_table(args.pid)
184+
asyncio.tools.display_awaited_by_tasks_table(args.pid, retries=args.retries)
173185
sys.exit(0)
174186
case "pstree":
175-
asyncio.tools.display_awaited_by_tasks_tree(args.pid)
187+
asyncio.tools.display_awaited_by_tasks_tree(args.pid, retries=args.retries)
176188
sys.exit(0)
177189
case None:
178190
pass # continue to the interactive shell

Lib/asyncio/tools.py

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -236,22 +236,37 @@ def exit_with_permission_help_text():
236236
sys.exit(1)
237237

238238

239-
def _get_awaited_by_tasks(pid: int) -> list:
240-
try:
241-
return get_all_awaited_by(pid)
242-
except RuntimeError as e:
243-
while e.__context__ is not None:
244-
e = e.__context__
245-
print(f"Error retrieving tasks: {e}")
246-
sys.exit(1)
247-
except PermissionError:
248-
exit_with_permission_help_text()
239+
_TRANSIENT_ERRORS = (RuntimeError, OSError, UnicodeDecodeError, MemoryError)
240+
241+
242+
def _get_awaited_by_tasks(pid: int, retries: int = 3) -> list:
243+
for attempt in range(retries + 1):
244+
try:
245+
return get_all_awaited_by(pid)
246+
except PermissionError:
247+
exit_with_permission_help_text()
248+
except ProcessLookupError:
249+
print(f"Error: process {pid} not found.", file=sys.stderr)
250+
sys.exit(1)
251+
except _TRANSIENT_ERRORS as e:
252+
if attempt < retries:
253+
print(
254+
f"Transient error while reading process state "
255+
f"(attempt {attempt + 1}/{retries + 1}), retrying...",
256+
file=sys.stderr,
257+
)
258+
continue
259+
if isinstance(e, RuntimeError):
260+
while e.__context__ is not None:
261+
e = e.__context__
262+
print(f"Error retrieving tasks: {e}", file=sys.stderr)
263+
sys.exit(1)
249264

250265

251-
def display_awaited_by_tasks_table(pid: int) -> None:
266+
def display_awaited_by_tasks_table(pid: int, retries: int = 3) -> None:
252267
"""Build and print a table of all pending tasks under `pid`."""
253268

254-
tasks = _get_awaited_by_tasks(pid)
269+
tasks = _get_awaited_by_tasks(pid, retries=retries)
255270
table = build_task_table(tasks)
256271
# Print the table in a simple tabular format
257272
print(
@@ -262,10 +277,10 @@ def display_awaited_by_tasks_table(pid: int) -> None:
262277
print(f"{row[0]:<10} {row[1]:<20} {row[2]:<20} {row[3]:<50} {row[4]:<50} {row[5]:<15} {row[6]:<15}")
263278

264279

265-
def display_awaited_by_tasks_tree(pid: int) -> None:
280+
def display_awaited_by_tasks_tree(pid: int, retries: int = 3) -> None:
266281
"""Build and print a tree of all pending tasks under `pid`."""
267282

268-
tasks = _get_awaited_by_tasks(pid)
283+
tasks = _get_awaited_by_tasks(pid, retries=retries)
269284
try:
270285
result = build_async_tree(tasks)
271286
except CycleFoundException as e:

0 commit comments

Comments
 (0)