-
-
Notifications
You must be signed in to change notification settings - Fork 32k
gh-121468: Support async breakpoint in pdb #132576
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
Changes from 9 commits
1b77cdb
285efe2
677c952
15290d5
a3401e9
50c3c9c
a6c580d
090715b
ebdc161
109d8b1
e953532
968472d
ff09ab2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -378,6 +378,9 @@ def __init__(self, completekey='tab', stdin=None, stdout=None, skip=None, | |
self.commands_bnum = None # The breakpoint number for which we are | ||
# defining a list | ||
|
||
self.async_shim_frame = None | ||
self.async_awaitable = None | ||
|
||
self._chained_exceptions = tuple() | ||
self._chained_exception_index = 0 | ||
|
||
|
@@ -393,6 +396,57 @@ def set_trace(self, frame=None, *, commands=None): | |
|
||
super().set_trace(frame) | ||
|
||
async def set_trace_async(self, frame=None, *, commands=None): | ||
if self.async_awaitable is not None: | ||
# We are already in a set_trace_async call, do not mess with it | ||
return | ||
|
||
if frame is None: | ||
frame = sys._getframe().f_back | ||
|
||
# We need set_trace to set up the basics, however, this will call | ||
# set_stepinstr() will we need to compensate for, because we don't | ||
# want to trigger on calls | ||
self.set_trace(frame, commands=commands) | ||
# Changing the stopframe will disable trace dispatch on calls | ||
self.stopframe = frame | ||
# We need to stop tracing because we don't have the privilege to avoid | ||
# triggering tracing functions as normal, as we are not already in | ||
# tracing functions | ||
self.stop_trace() | ||
|
||
self.async_shim_frame = sys._getframe() | ||
self.async_awaitable = None | ||
|
||
while True: | ||
self.async_awaitable = None | ||
# Simulate a trace event | ||
# This should bring up pdb and make pdb believe it's debugging the | ||
# caller frame | ||
self.trace_dispatch(frame, "opcode", None) | ||
if self.async_awaitable is not None: | ||
try: | ||
if self.breaks: | ||
with self.set_enterframe(frame): | ||
# set_continue requires enterframe to work | ||
self.set_continue() | ||
self.start_trace() | ||
await self.async_awaitable | ||
except Exception: | ||
self._error_exc() | ||
else: | ||
break | ||
|
||
self.async_shim_frame = None | ||
|
||
# start the trace (the actual command is already set by set_* calls) | ||
if self.returnframe is None and self.stoplineno == -1 and not self.breaks: | ||
# This means we did a continue without any breakpoints, we should not | ||
# start the trace | ||
return | ||
|
||
self.start_trace() | ||
|
||
def sigint_handler(self, signum, frame): | ||
if self.allow_kbdint: | ||
raise KeyboardInterrupt | ||
|
@@ -775,6 +829,20 @@ def _exec_in_closure(self, source, globals, locals): | |
|
||
return True | ||
|
||
def _exec_await(self, source, globals, locals): | ||
""" Run source code that contains await by playing with async shim frame""" | ||
# Put the source in an async function | ||
source_async = ( | ||
"async def __pdb_await():\n" + | ||
textwrap.indent(source, " ") + '\n' + | ||
" __pdb_locals.update(locals())" | ||
) | ||
ns = globals | locals | ||
# We use __pdb_locals to do write back | ||
ns["__pdb_locals"] = locals | ||
exec(source_async, ns) | ||
self.async_awaitable = ns["__pdb_await"]() | ||
|
||
def default(self, line): | ||
if line[:1] == '!': line = line[1:].strip() | ||
locals = self.curframe.f_locals | ||
|
@@ -820,8 +888,20 @@ def default(self, line): | |
sys.stdout = save_stdout | ||
sys.stdin = save_stdin | ||
sys.displayhook = save_displayhook | ||
except: | ||
self._error_exc() | ||
except Exception as e: | ||
# Maybe it's an await expression/statement | ||
if ( | ||
self.async_shim_frame is not None | ||
and isinstance(e, SyntaxError) | ||
and e.msg == "'await' outside function" | ||
): | ||
try: | ||
self._exec_await(buffer, globals, locals) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this should be run without the exception from line 891 in the context There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I restructured the code as we know whether it is an await code during compile. The nice side effect is that we don't need to execute the code under the exception context. |
||
return True | ||
except: | ||
self._error_exc() | ||
else: | ||
self._error_exc() | ||
|
||
def _replace_convenience_variables(self, line): | ||
"""Replace the convenience variables in 'line' with their values. | ||
|
@@ -2491,6 +2571,21 @@ def set_trace(*, header=None, commands=None): | |
pdb.message(header) | ||
pdb.set_trace(sys._getframe().f_back, commands=commands) | ||
|
||
async def set_trace_async(*, header=None, commands=None): | ||
"""Enter the debugger at the calling stack frame, but in async mode. | ||
|
||
This should be used as await pdb.set_trace_async(). Users can do await | ||
if they enter the debugger with this function. Otherwise it's the same | ||
as set_trace(). | ||
""" | ||
if Pdb._last_pdb_instance is not None: | ||
pdb = Pdb._last_pdb_instance | ||
else: | ||
pdb = Pdb(mode='inline', backend='monitoring') | ||
if header is not None: | ||
pdb.message(header) | ||
await pdb.set_trace_async(sys._getframe().f_back, commands=commands) | ||
|
||
# Post-Mortem interface | ||
|
||
def post_mortem(t=None): | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Add :func:`pdb.set_trace_async` function to support :keyword:`await` statements in :mod:`pdb`. |
Uh oh!
There was an error while loading. Please reload this page.