Skip to content

Commit c99aa15

Browse files
committed
Cleanup post_mortem implementation and interface a bit. Add as_uncaught flag which says whether to apply uncaught exception breakpoint filters, defaulting to True.
1 parent d407a4f commit c99aa15

5 files changed

Lines changed: 52 additions & 56 deletions

File tree

src/debugpy/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"is_client_connected",
2020
"listen",
2121
"log_to",
22-
"postmortem",
22+
"post_mortem",
2323
"trace_this_thread",
2424
"wait_for_client",
2525
]

src/debugpy/_vendored/pydevd/_pydevd_sys_monitoring/_pydevd_sys_monitoring.py

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1837,7 +1837,7 @@ def suspend_current_thread_tracing():
18371837
Returns the previous tracing state (True if tracing was enabled, False otherwise).
18381838
This is useful for temporarily disabling tracing to prevent recursive debugging.
18391839
1840-
Use resume_current_thread_tracing() or set_current_thread_tracing_state() to restore.
1840+
Use resume_current_thread_tracing() to restore.
18411841
"""
18421842
try:
18431843
thread_info = _thread_local_info.thread_info
@@ -1860,9 +1860,6 @@ def resume_current_thread_tracing():
18601860
# fmt: on
18611861
"""
18621862
Resumes tracing for the current thread.
1863-
1864-
This unconditionally enables tracing. For conditional restoration,
1865-
use set_current_thread_tracing_state().
18661863
"""
18671864
try:
18681865
thread_info = _thread_local_info.thread_info
@@ -1873,31 +1870,6 @@ def resume_current_thread_tracing():
18731870
thread_info.trace = True
18741871

18751872

1876-
# fmt: off
1877-
# IFDEF CYTHON
1878-
# cpdef set_current_thread_tracing_state(bint trace):
1879-
# cdef ThreadInfo thread_info
1880-
# ELSE
1881-
def set_current_thread_tracing_state(trace):
1882-
# ENDIF
1883-
# fmt: on
1884-
"""
1885-
Sets the tracing state for the current thread.
1886-
1887-
:param trace: True to enable tracing, False to disable.
1888-
1889-
This is typically used to restore a previously saved state from
1890-
suspend_current_thread_tracing().
1891-
"""
1892-
try:
1893-
thread_info = _thread_local_info.thread_info
1894-
except:
1895-
thread_info = _get_thread_info(False, 1)
1896-
if thread_info is None:
1897-
return
1898-
thread_info.trace = trace
1899-
1900-
19011873
def update_monitor_events(suspend_requested: Optional[bool]=None) -> None:
19021874
"""
19031875
This should be called when breakpoints change.

src/debugpy/_vendored/pydevd/pydevd.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2392,6 +2392,51 @@ def do_stop_on_unhandled_exception(self, thread, frame, frames_byid, arg):
23922392
remove_exception_from_frame(frame)
23932393
frame = None
23942394

2395+
def post_mortem(self, excinfo, as_uncaught=True):
2396+
"""
2397+
Triggers post-mortem debugging as if handling an uncaught exception.
2398+
2399+
If as_uncaught is True (default), respects exception breakpoint configuration and applies breakpoint filters.
2400+
2401+
:param excinfo: A tuple of (exc_type, exc_value, exc_traceback).
2402+
"""
2403+
if not as_uncaught:
2404+
exctype, value, tb = excinfo
2405+
2406+
# Walk traceback to build frames list and find user frame
2407+
frames = []
2408+
user_frame = None
2409+
while tb is not None:
2410+
frame = tb.tb_frame
2411+
# Skip debugger-internal frames, use last user frame
2412+
if self.get_file_type(frame) is None:
2413+
user_frame = frame
2414+
frames.append(frame)
2415+
tb = tb.tb_next
2416+
2417+
if user_frame is None:
2418+
pydev_log.debug("post_mortem: no user frame found in traceback")
2419+
return
2420+
2421+
frames_byid = dict([(id(frame), frame) for frame in frames])
2422+
2423+
if PYDEVD_USE_SYS_MONITORING:
2424+
saved_sys_monitoring_trace = pydevd_sys_monitoring.suspend_current_thread_tracing()
2425+
thread = threading.current_thread()
2426+
additional_info = self.set_additional_thread_info(thread)
2427+
additional_info.is_tracing += 1
2428+
2429+
try:
2430+
if as_uncaught:
2431+
self.stop_on_unhandled_exception(self, thread, additional_info, excinfo)
2432+
else:
2433+
self.do_stop_on_unhandled_exception(thread, user_frame, frames_byid, excinfo)
2434+
finally:
2435+
if PYDEVD_USE_SYS_MONITORING:
2436+
if saved_sys_monitoring_trace:
2437+
pydevd_sys_monitoring.resume_current_thread_tracing()
2438+
additional_info.is_tracing -= 1
2439+
23952440
def set_trace_for_frame_and_parents(self, thread_ident: Optional[int], frame, **kwargs):
23962441
disable = kwargs.pop("disable", False)
23972442
assert not kwargs

src/debugpy/public_api.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -213,8 +213,9 @@ def trace_this_thread(__should_trace: bool):
213213

214214

215215
@_api()
216-
def postmortem(
217-
__excinfo: typing.Tuple[type, BaseException, typing.Any] | None = None
216+
def post_mortem(
217+
__excinfo: typing.Tuple[type, BaseException, typing.Any] | None = None,
218+
as_uncaught: bool = True,
218219
) -> None:
219220
"""Stops the debugger on an unhandled exception.
220221

src/debugpy/server/api.py

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ def trace_this_thread(should_trace):
366366
pydb.disable_tracing()
367367

368368

369-
def postmortem(excinfo=None):
369+
def post_mortem(excinfo=None, as_uncaught=True):
370370
ensure_logging()
371371

372372
if excinfo is None:
@@ -388,27 +388,5 @@ def postmortem(excinfo=None):
388388
log.warning("postmortem() ignored - no global debugger")
389389
return
390390

391-
thread = threading.current_thread()
392-
additional_info = pydb.set_additional_thread_info(thread)
391+
pydb.post_mortem(excinfo, as_uncaught=as_uncaught)
393392

394-
# Save states for restoration
395-
saved_is_tracing = additional_info.is_tracing
396-
saved_sys_monitoring_trace = None
397-
398-
try:
399-
# Prevent recursive tracing (settrace protection)
400-
additional_info.is_tracing += 1
401-
402-
# For Python 3.12+, suspend sys.monitoring tracing
403-
if hasattr(sys, 'monitoring'):
404-
from _pydevd_sys_monitoring import pydevd_sys_monitoring
405-
saved_sys_monitoring_trace = pydevd_sys_monitoring.suspend_current_thread_tracing()
406-
407-
from _pydevd_bundle.pydevd_breakpoints import stop_on_unhandled_exception
408-
stop_on_unhandled_exception(pydb, thread, additional_info, excinfo)
409-
410-
finally:
411-
additional_info.is_tracing = saved_is_tracing
412-
if saved_sys_monitoring_trace is not None:
413-
from _pydevd_sys_monitoring import pydevd_sys_monitoring
414-
pydevd_sys_monitoring.set_current_thread_tracing_state(saved_sys_monitoring_trace)

0 commit comments

Comments
 (0)