Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitcaee16f

Browse files
gh-121468: Support async breakpoint in pdb (#132576)
1 parent4265854 commitcaee16f

File tree

5 files changed

+314
-9
lines changed

5 files changed

+314
-9
lines changed

‎Doc/library/pdb.rst

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,21 @@ slightly different way:
188188
..versionadded::3.14
189189
The *commands* argument.
190190

191+
192+
..awaitablefunction::set_trace_async(*, header=None, commands=None)
193+
194+
async version of:func:`set_trace`. This function should be used inside an
195+
async function with:keyword:`await`.
196+
197+
..code-block::python
198+
199+
asyncdeff():
200+
await pdb.set_trace_async()
201+
202+
:keyword:`await` statements are supported if the debugger is invoked by this function.
203+
204+
..versionadded::3.14
205+
191206
..function::post_mortem(t=None)
192207

193208
Enter post-mortem debugging of the given exception or

‎Doc/whatsnew/3.14.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1168,6 +1168,11 @@ pdb
11681168
backend by default, which is configurable.
11691169
(Contributed by Tian Gao in:gh:`124533`.)
11701170

1171+
*:func:`pdb.set_trace_async` is added to support debugging asyncio
1172+
coroutines.:keyword:`await` statements are supported with this
1173+
function.
1174+
(Contributed by Tian Gao in:gh:`132576`.)
1175+
11711176

11721177
pickle
11731178
------

‎Lib/pdb.py

Lines changed: 115 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,9 @@ def __init__(self, completekey='tab', stdin=None, stdout=None, skip=None,
385385
self.commands_bnum=None# The breakpoint number for which we are
386386
# defining a list
387387

388+
self.async_shim_frame=None
389+
self.async_awaitable=None
390+
388391
self._chained_exceptions=tuple()
389392
self._chained_exception_index=0
390393

@@ -400,6 +403,57 @@ def set_trace(self, frame=None, *, commands=None):
400403

401404
super().set_trace(frame)
402405

406+
asyncdefset_trace_async(self,frame=None,*,commands=None):
407+
ifself.async_awaitableisnotNone:
408+
# We are already in a set_trace_async call, do not mess with it
409+
return
410+
411+
ifframeisNone:
412+
frame=sys._getframe().f_back
413+
414+
# We need set_trace to set up the basics, however, this will call
415+
# set_stepinstr() will we need to compensate for, because we don't
416+
# want to trigger on calls
417+
self.set_trace(frame,commands=commands)
418+
# Changing the stopframe will disable trace dispatch on calls
419+
self.stopframe=frame
420+
# We need to stop tracing because we don't have the privilege to avoid
421+
# triggering tracing functions as normal, as we are not already in
422+
# tracing functions
423+
self.stop_trace()
424+
425+
self.async_shim_frame=sys._getframe()
426+
self.async_awaitable=None
427+
428+
whileTrue:
429+
self.async_awaitable=None
430+
# Simulate a trace event
431+
# This should bring up pdb and make pdb believe it's debugging the
432+
# caller frame
433+
self.trace_dispatch(frame,"opcode",None)
434+
ifself.async_awaitableisnotNone:
435+
try:
436+
ifself.breaks:
437+
withself.set_enterframe(frame):
438+
# set_continue requires enterframe to work
439+
self.set_continue()
440+
self.start_trace()
441+
awaitself.async_awaitable
442+
exceptException:
443+
self._error_exc()
444+
else:
445+
break
446+
447+
self.async_shim_frame=None
448+
449+
# start the trace (the actual command is already set by set_* calls)
450+
ifself.returnframeisNoneandself.stoplineno==-1andnotself.breaks:
451+
# This means we did a continue without any breakpoints, we should not
452+
# start the trace
453+
return
454+
455+
self.start_trace()
456+
403457
defsigint_handler(self,signum,frame):
404458
ifself.allow_kbdint:
405459
raiseKeyboardInterrupt
@@ -782,12 +836,25 @@ def _exec_in_closure(self, source, globals, locals):
782836

783837
returnTrue
784838

785-
defdefault(self,line):
786-
ifline[:1]=='!':line=line[1:].strip()
787-
locals=self.curframe.f_locals
788-
globals=self.curframe.f_globals
839+
def_exec_await(self,source,globals,locals):
840+
""" Run source code that contains await by playing with async shim frame"""
841+
# Put the source in an async function
842+
source_async= (
843+
"async def __pdb_await():\n"+
844+
textwrap.indent(source," ")+'\n'+
845+
" __pdb_locals.update(locals())"
846+
)
847+
ns=globals|locals
848+
# We use __pdb_locals to do write back
849+
ns["__pdb_locals"]=locals
850+
exec(source_async,ns)
851+
self.async_awaitable=ns["__pdb_await"]()
852+
853+
def_read_code(self,line):
854+
buffer=line
855+
is_await_code=False
856+
code=None
789857
try:
790-
buffer=line
791858
if (code:=codeop.compile_command(line+'\n','<stdin>','single'))isNone:
792859
# Multi-line mode
793860
withself._enable_multiline_completion():
@@ -800,7 +867,7 @@ def default(self, line):
800867
except (EOFError,KeyboardInterrupt):
801868
self.lastcmd=""
802869
print('\n')
803-
return
870+
returnNone,None,False
804871
else:
805872
self.stdout.write(continue_prompt)
806873
self.stdout.flush()
@@ -809,20 +876,44 @@ def default(self, line):
809876
self.lastcmd=""
810877
self.stdout.write('\n')
811878
self.stdout.flush()
812-
return
879+
returnNone,None,False
813880
else:
814881
line=line.rstrip('\r\n')
815882
buffer+='\n'+line
816883
self.lastcmd=buffer
884+
exceptSyntaxErrorase:
885+
# Maybe it's an await expression/statement
886+
if (
887+
self.async_shim_frameisnotNone
888+
ande.msg=="'await' outside function"
889+
):
890+
is_await_code=True
891+
else:
892+
raise
893+
894+
returncode,buffer,is_await_code
895+
896+
defdefault(self,line):
897+
ifline[:1]=='!':line=line[1:].strip()
898+
locals=self.curframe.f_locals
899+
globals=self.curframe.f_globals
900+
try:
901+
code,buffer,is_await_code=self._read_code(line)
902+
ifbufferisNone:
903+
return
817904
save_stdout=sys.stdout
818905
save_stdin=sys.stdin
819906
save_displayhook=sys.displayhook
820907
try:
821908
sys.stdin=self.stdin
822909
sys.stdout=self.stdout
823910
sys.displayhook=self.displayhook
824-
ifnotself._exec_in_closure(buffer,globals,locals):
825-
exec(code,globals,locals)
911+
ifis_await_code:
912+
self._exec_await(buffer,globals,locals)
913+
returnTrue
914+
else:
915+
ifnotself._exec_in_closure(buffer,globals,locals):
916+
exec(code,globals,locals)
826917
finally:
827918
sys.stdout=save_stdout
828919
sys.stdin=save_stdin
@@ -2501,6 +2592,21 @@ def set_trace(*, header=None, commands=None):
25012592
pdb.message(header)
25022593
pdb.set_trace(sys._getframe().f_back,commands=commands)
25032594

2595+
asyncdefset_trace_async(*,header=None,commands=None):
2596+
"""Enter the debugger at the calling stack frame, but in async mode.
2597+
2598+
This should be used as await pdb.set_trace_async(). Users can do await
2599+
if they enter the debugger with this function. Otherwise it's the same
2600+
as set_trace().
2601+
"""
2602+
ifPdb._last_pdb_instanceisnotNone:
2603+
pdb=Pdb._last_pdb_instance
2604+
else:
2605+
pdb=Pdb(mode='inline',backend='monitoring')
2606+
ifheaderisnotNone:
2607+
pdb.message(header)
2608+
awaitpdb.set_trace_async(sys._getframe().f_back,commands=commands)
2609+
25042610
# Remote PDB
25052611

25062612
class_PdbServer(Pdb):

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp