Skip to content

gh-121468: Show asyncio information in pdb #124367

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

Merged
merged 7 commits into from
Mar 14, 2025
Merged
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
6 changes: 5 additions & 1 deletion Doc/library/pdb.rst
Original file line number Diff line number Diff line change
Expand Up @@ -304,16 +304,20 @@ sets a global variable ``$foo`` which you can use in the debugger session. The
less likely to interfere with your program compared to using normal variables
like ``foo = 1``.

There are three preset *convenience variables*:
There are four preset *convenience variables*:

* ``$_frame``: the current frame you are debugging
* ``$_retval``: the return value if the frame is returning
* ``$_exception``: the exception if the frame is raising an exception
* ``$_asynctask``: the asyncio task if pdb stops in an async function

.. versionadded:: 3.12

Added the *convenience variable* feature.

.. versionadded:: 3.14
Added the ``$_asynctask`` convenience variable.

.. index::
pair: .pdbrc; file
triple: debugger; configuration; file
Expand Down
3 changes: 3 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -811,6 +811,9 @@ pdb
fill in a 4-space indentation now, instead of inserting a ``\t`` character.
(Contributed by Tian Gao in :gh:`130471`.)

* ``$_asynctask`` is added to access the current asyncio task if applicable.
(Contributed by Tian Gao in :gh:`124367`.)


pickle
------
Expand Down
15 changes: 14 additions & 1 deletion Lib/pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
import codeop
import pprint
import signal
import asyncio
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line 614 has deferred import of asyncio.base_futures, so we could consider adding a lazy import of asyncio at line 606 to improve import time?

#109653
#118761

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

asyncio will be used as long as pdb is used. pdb is not an module that is often imported explicitly or implicitly, I don't think import time of pdb is specifically critical.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. I didn't realize that at the time

import inspect
import textwrap
import tokenize
Expand Down Expand Up @@ -363,6 +364,8 @@ def __init__(self, completekey='tab', stdin=None, stdout=None, skip=None,
self._chained_exceptions = tuple()
self._chained_exception_index = 0

self._current_task = None

def set_trace(self, frame=None, *, commands=None):
Pdb._last_pdb_instance = self
if frame is None:
Expand Down Expand Up @@ -405,7 +408,8 @@ def setup(self, f, tb):
tb = tb.tb_next
self.curframe = self.stack[self.curindex][0]
self.set_convenience_variable(self.curframe, '_frame', self.curframe)

if self._current_task:
self.set_convenience_variable(self.curframe, '_asynctask', self._current_task)
self._save_initial_file_mtime(self.curframe)

if self._chained_exceptions:
Expand Down Expand Up @@ -616,6 +620,13 @@ def _hold_exceptions(self, exceptions):
self._chained_exceptions = tuple()
self._chained_exception_index = 0

def _get_asyncio_task(self):
try:
task = asyncio.current_task()
except RuntimeError:
task = None
return task

def interaction(self, frame, tb_or_exc):
# Restore the previous signal handler at the Pdb prompt.
if Pdb._previous_sigint_handler:
Expand All @@ -626,6 +637,8 @@ def interaction(self, frame, tb_or_exc):
else:
Pdb._previous_sigint_handler = None

self._current_task = self._get_asyncio_task()

_chained_exceptions, tb = self._get_tb_and_exceptions(tb_or_exc)
if isinstance(tb_or_exc, BaseException):
assert tb is not None, "main exception must have a traceback"
Expand Down
26 changes: 25 additions & 1 deletion Lib/test/test_pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from contextlib import ExitStack, redirect_stdout
from io import StringIO
from test import support
from test.support import force_not_colorized, os_helper
from test.support import force_not_colorized, has_socket_support, os_helper
from test.support.import_helper import import_module
from test.support.pty_helper import run_pty, FakeInput
from test.support.script_helper import kill_python
Expand Down Expand Up @@ -2059,6 +2059,30 @@ def test_pdb_next_command_for_generator():
"""

if not SKIP_CORO_TESTS:
if has_socket_support:
def test_pdb_asynctask():
"""Testing $_asynctask is accessible in async context

>>> import asyncio

>>> async def test():
... import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace()

>>> def test_function():
... asyncio.run(test())

>>> with PdbTestInput([ # doctest: +ELLIPSIS
... '$_asynctask',
... 'continue',
... ]):
... test_function()
> <doctest test.test_pdb.test_pdb_asynctask[1]>(2)test()
-> import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace()
(Pdb) $_asynctask
<Task pending name='Task-1' coro=<test() running at <doctest test.test_pdb.test_pdb_asynctask[1]>:2> ...
(Pdb) continue
"""

def test_pdb_next_command_for_coroutine():
"""Testing skip unwinding stack on yield for coroutines for "next" command

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
``$_asynctask`` is added as a :mod:`pdb` convenience variable to
access the current asyncio task if applicable.
Loading