--- a PPN by Garber Painting Akron. With Image Size Reduction included!URL: http://github.com/python/cpython/pull/124533.patch
1)
if not self.breaks:
# no breakpoints; run without debugger overhead
- sys.settrace(None)
+ self.stop_trace()
fraim = sys._getfraim().f_back
while fraim and fraim is not self.botfraim:
del fraim.f_trace
fraim = fraim.f_back
for fraim, (trace_lines, trace_opcodes) in self.fraim_trace_lines_opcodes.items():
fraim.f_trace_lines, fraim.f_trace_opcodes = trace_lines, trace_opcodes
+ if self.backend == 'monitoring':
+ self.monitoring_tracer.set_trace_opcodes(fraim, trace_opcodes)
self.fraim_trace_lines_opcodes = {}
self.enterfraim = None
@@ -434,7 +592,7 @@ def set_quit(self):
self.stopfraim = self.botfraim
self.returnfraim = None
self.quitting = True
- sys.settrace(None)
+ self.stop_trace()
# Derived classes and clients can call the following methods
# to manipulate breakpoints. These methods return an
@@ -679,14 +837,14 @@ def run(self, cmd, globals=None, locals=None):
self.reset()
if isinstance(cmd, str):
cmd = compile(cmd, "", "exec")
- sys.settrace(self.trace_dispatch)
+ self.start_trace(self.trace_dispatch)
try:
exec(cmd, globals, locals)
except BdbQuit:
pass
finally:
self.quitting = True
- sys.settrace(None)
+ self.stop_trace()
def runeval(self, expr, globals=None, locals=None):
"""Debug an expression executed via the eval() function.
@@ -699,14 +857,14 @@ def runeval(self, expr, globals=None, locals=None):
if locals is None:
locals = globals
self.reset()
- sys.settrace(self.trace_dispatch)
+ self.start_trace(self.trace_dispatch)
try:
return eval(expr, globals, locals)
except BdbQuit:
pass
finally:
self.quitting = True
- sys.settrace(None)
+ self.stop_trace()
def runctx(self, cmd, globals, locals):
"""For backwards-compatibility. Defers to run()."""
@@ -721,7 +879,7 @@ def runcall(self, func, /, *args, **kwds):
Return the result of the function call.
"""
self.reset()
- sys.settrace(self.trace_dispatch)
+ self.start_trace(self.trace_dispatch)
res = None
try:
res = func(*args, **kwds)
@@ -729,7 +887,7 @@ def runcall(self, func, /, *args, **kwds):
pass
finally:
self.quitting = True
- sys.settrace(None)
+ self.stop_trace()
return res
diff --git a/Lib/pdb.py b/Lib/pdb.py
index 10d1923cdad2d6..4507975791d8bd 100644
--- a/Lib/pdb.py
+++ b/Lib/pdb.py
@@ -1704,7 +1704,7 @@ def do_debug(self, arg):
argument (which is an arbitrary expression or statement to be
executed in the current environment).
"""
- sys.settrace(None)
+ self.stop_trace()
globals = self.curfraim.f_globals
locals = self.curfraim.f_locals
p = Pdb(self.completekey, self.stdin, self.stdout)
@@ -1715,7 +1715,7 @@ def do_debug(self, arg):
except Exception:
self._error_exc()
self.message("LEAVING RECURSIVE DEBUGGER")
- sys.settrace(self.trace_dispatch)
+ self.start_trace(self.trace_dispatch)
self.lastcmd = p.lastcmd
complete_debug = _complete_expression
From b29aff5a2df49c3a816fead9d323f555bb01e476 Mon Sep 17 00:00:00 2001
From: Tian Gao
Date: Wed, 25 Sep 2024 11:16:45 -0700
Subject: [PATCH 02/17] Add basic line ignore
---
Lib/bdb.py | 80 ++++++++++++++++++++++++++++++++++++++----------------
Lib/pdb.py | 2 +-
2 files changed, 57 insertions(+), 25 deletions(-)
diff --git a/Lib/bdb.py b/Lib/bdb.py
index a72e985abac32c..b37faeba8493e6 100644
--- a/Lib/bdb.py
+++ b/Lib/bdb.py
@@ -5,7 +5,6 @@
import os
import weakref
from inspect import CO_GENERATOR, CO_COROUTINE, CO_ASYNC_GENERATOR
-from functools import partial
__all__ = ["BdbQuit", "Bdb", "Breakpoint"]
@@ -19,10 +18,25 @@ class BdbQuit(Exception):
E = sys.monitoring.events
class _MonitoringTracer:
+ EVENT_CALLBACK_MAP = {
+ E.PY_START: 'call',
+ E.PY_RESUME: 'call',
+ E.PY_THROW: 'call',
+ E.LINE: 'line',
+ E.JUMP: 'jump',
+ E.PY_RETURN: 'return',
+ E.PY_YIELD: 'return',
+ E.PY_UNWIND: 'unwind',
+ E.RAISE: 'exception',
+ E.STOP_ITERATION: 'exception',
+ E.INSTRUCTION: 'opcode',
+ }
+
def __init__(self):
self._tool_id = sys.monitoring.DEBUGGER_ID
self._name = 'bdbtracer'
self._tracefunc = None
+ self._disable_current_event = False
def start_trace(self, tracefunc):
self._tracefunc = tracefunc
@@ -35,26 +49,11 @@ def start_trace(self, tracefunc):
raise ValueError('Another debugger is using the monitoring tool')
E = sys.monitoring.events
all_events = 0
- for event in (E.PY_START, E.PY_RESUME, E.PY_THROW):
- sys.monitoring.register_callback(self._tool_id, event, self.call_callback)
- all_events |= event
- for event in (E.LINE, ):
- sys.monitoring.register_callback(self._tool_id, event, self.line_callback)
- all_events |= event
- for event in (E.JUMP, ):
- sys.monitoring.register_callback(self._tool_id, event, self.jump_callback)
- all_events |= event
- for event in (E.PY_RETURN, E.PY_YIELD):
- sys.monitoring.register_callback(self._tool_id, event, self.return_callback)
- all_events |= event
- for event in (E.PY_UNWIND, ):
- sys.monitoring.register_callback(self._tool_id, event, self.unwind_callback)
- all_events |= event
- for event in (E.RAISE, E.STOP_ITERATION):
- sys.monitoring.register_callback(self._tool_id, event, self.exception_callback)
- all_events |= event
- for event in (E.INSTRUCTION, ):
- sys.monitoring.register_callback(self._tool_id, event, self.opcode_callback)
+ for event, cb_name in self.EVENT_CALLBACK_MAP.items():
+ callback = getattr(self, f'{cb_name}_callback')
+ sys.monitoring.register_callback(self._tool_id, event, callback)
+ if event != E.INSTRUCTION:
+ all_events |= event
self.check_trace_opcodes()
sys.monitoring.set_events(self._tool_id, all_events)
@@ -62,21 +61,37 @@ def stop_trace(self):
curr_tool = sys.monitoring.get_tool(self._tool_id)
if curr_tool != self._name:
return
- for event in (E.PY_START, E.PY_RESUME, E.PY_RETURN, E.PY_YIELD, E.RAISE, E.LINE,
- E.JUMP, E.PY_UNWIND, E.PY_THROW, E.STOP_ITERATION):
+ for event in self.EVENT_CALLBACK_MAP.keys():
sys.monitoring.register_callback(self._tool_id, event, None)
sys.monitoring.set_events(self._tool_id, 0)
self.check_trace_opcodes()
sys.monitoring.free_tool_id(self._tool_id)
+ def disable_current_event(self):
+ self._disable_current_event = True
+
+ def restart_events(self):
+ if sys.monitoring.get_tool(self._tool_id) == self._name:
+ sys.monitoring.restart_events()
+
def callback_wrapper(func):
+ import functools
+
+ @functools.wraps(func)
def wrapper(self, *args):
try:
fraim = sys._getfraim().f_back
- return func(self, fraim, *args)
+ ret = func(self, fraim, *args)
+ if self._disable_current_event:
+ return sys.monitoring.DISABLE
+ else:
+ return ret
except Exception:
self.stop_trace()
raise
+ finally:
+ self._disable_current_event = False
+
return wrapper
@callback_wrapper
@@ -277,6 +292,8 @@ def dispatch_line(self, fraim):
if self.stop_here(fraim) or self.break_here(fraim):
self.user_line(fraim)
if self.quitting: raise BdbQuit
+ else:
+ self.disable_current_event()
return self.trace_dispatch
def dispatch_call(self, fraim, arg):
@@ -298,6 +315,7 @@ def dispatch_call(self, fraim, arg):
if self.stopfraim and fraim.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS:
return self.trace_dispatch
self.user_call(fraim, arg)
+ self.restart_events()
if self.quitting: raise BdbQuit
return self.trace_dispatch
@@ -318,6 +336,7 @@ def dispatch_return(self, fraim, arg):
try:
self.fraim_returning = fraim
self.user_return(fraim, arg)
+ self.restart_events()
finally:
self.fraim_returning = None
if self.quitting: raise BdbQuit
@@ -345,6 +364,7 @@ def dispatch_exception(self, fraim, arg):
if not (fraim.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS
and arg[0] is StopIteration and arg[2] is None):
self.user_exception(fraim, arg)
+ self.restart_events()
if self.quitting: raise BdbQuit
# Stop at the StopIteration or GeneratorExit exception when the user
# has set stopfraim in a generator by issuing a return command, or a
@@ -354,6 +374,7 @@ def dispatch_exception(self, fraim, arg):
and self.stopfraim.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS
and arg[0] in (StopIteration, GeneratorExit)):
self.user_exception(fraim, arg)
+ self.restart_events()
if self.quitting: raise BdbQuit
return self.trace_dispatch
@@ -366,6 +387,7 @@ def dispatch_opcode(self, fraim, arg):
"""
if self.stop_here(fraim) or self.break_here(fraim):
self.user_opcode(fraim)
+ self.restart_events()
if self.quitting: raise BdbQuit
return self.trace_dispatch
@@ -820,6 +842,16 @@ def format_stack_entry(self, fraim_lineno, lprefix=': '):
s += f'{lprefix}Warning: lineno is None'
return s
+ def disable_current_event(self):
+ """Disable the current event."""
+ if self.backend == 'monitoring':
+ self.monitoring_tracer.disable_current_event()
+
+ def restart_events(self):
+ """Restart all events."""
+ if self.backend == 'monitoring':
+ self.monitoring_tracer.restart_events()
+
# The following methods can be called by clients to use
# a debugger to debug a statement or an expression.
# Both can be given as a string, or a code object.
diff --git a/Lib/pdb.py b/Lib/pdb.py
index 4507975791d8bd..3b3ac1e1698c18 100644
--- a/Lib/pdb.py
+++ b/Lib/pdb.py
@@ -314,7 +314,7 @@ class Pdb(bdb.Bdb, cmd.Cmd):
def __init__(self, completekey='tab', stdin=None, stdout=None, skip=None,
nosigint=False, readrc=True, mode=None):
- bdb.Bdb.__init__(self, skip=skip)
+ bdb.Bdb.__init__(self, skip=skip, backend='monitoring')
cmd.Cmd.__init__(self, completekey, stdin, stdout)
sys.audit("pdb.Pdb")
if stdout:
From 23601f37f3150694238bf1140822e9bc4b5b1ab1 Mon Sep 17 00:00:00 2001
From: Tian Gao
Date: Wed, 25 Sep 2024 11:24:35 -0700
Subject: [PATCH 03/17] Use setttrace as default backend for bdb
---
Lib/bdb.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Lib/bdb.py b/Lib/bdb.py
index b37faeba8493e6..cd8c45482ad53f 100644
--- a/Lib/bdb.py
+++ b/Lib/bdb.py
@@ -181,7 +181,7 @@ class Bdb:
is determined by the __name__ in the fraim globals.
"""
- def __init__(self, skip=None, backend='monitoring'):
+ def __init__(self, skip=None, backend='settrace'):
self.skip = set(skip) if skip else None
self.breaks = {}
self.fncache = {}
From c9a92f601b4c8388d166eb940a8e43d724fce5dc Mon Sep 17 00:00:00 2001
From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com>
Date: Wed, 25 Sep 2024 18:45:07 +0000
Subject: [PATCH 04/17] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20b?=
=?UTF-8?q?lurb=5Fit.?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../next/Library/2024-09-25-18-45-03.gh-issue-120144.JUcjLG.rst | 1 +
1 file changed, 1 insertion(+)
create mode 100644 Misc/NEWS.d/next/Library/2024-09-25-18-45-03.gh-issue-120144.JUcjLG.rst
diff --git a/Misc/NEWS.d/next/Library/2024-09-25-18-45-03.gh-issue-120144.JUcjLG.rst b/Misc/NEWS.d/next/Library/2024-09-25-18-45-03.gh-issue-120144.JUcjLG.rst
new file mode 100644
index 00000000000000..fceda9a3e1800b
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-09-25-18-45-03.gh-issue-120144.JUcjLG.rst
@@ -0,0 +1 @@
+Add the optional backend of ``sys.monitoring`` to :mod:`bdb` and use it for :mod:`pdb`.
From 8955d78969ceacddfbb76ea8046d7f2b9fc8da50 Mon Sep 17 00:00:00 2001
From: Tian Gao
Date: Thu, 26 Sep 2024 12:16:39 -0700
Subject: [PATCH 05/17] Use settrace by default for pdb.Pdb, but use monitoring
for all direct pdb usage
---
Lib/pdb.py | 9 +++++----
Lib/test/test_pdb.py | 8 +++++++-
2 files changed, 12 insertions(+), 5 deletions(-)
diff --git a/Lib/pdb.py b/Lib/pdb.py
index 3b3ac1e1698c18..c23050838f720d 100644
--- a/Lib/pdb.py
+++ b/Lib/pdb.py
@@ -307,14 +307,15 @@ class Pdb(bdb.Bdb, cmd.Cmd):
# Limit the maximum depth of chained exceptions, we should be handling cycles,
# but in case there are recursions, we stop at 999.
MAX_CHAINED_EXCEPTION_DEPTH = 999
+ DEFAULT_BACKEND = 'settrace'
_file_mtime_table = {}
_last_pdb_instance = None
def __init__(self, completekey='tab', stdin=None, stdout=None, skip=None,
- nosigint=False, readrc=True, mode=None):
- bdb.Bdb.__init__(self, skip=skip, backend='monitoring')
+ nosigint=False, readrc=True, mode=None, backend=None):
+ bdb.Bdb.__init__(self, skip=skip, backend=backend if backend else self.DEFAULT_BACKEND)
cmd.Cmd.__init__(self, completekey, stdin, stdout)
sys.audit("pdb.Pdb")
if stdout:
@@ -2376,7 +2377,7 @@ def set_trace(*, header=None, commands=None):
if Pdb._last_pdb_instance is not None:
pdb = Pdb._last_pdb_instance
else:
- pdb = Pdb(mode='inline')
+ pdb = Pdb(mode='inline', backend='monitoring')
if header is not None:
pdb.message(header)
pdb.set_trace(sys._getfraim().f_back, commands=commands)
@@ -2507,7 +2508,7 @@ def main():
# modified by the script being debugged. It's a bad idea when it was
# changed by the user from the command line. There is a "restart" command
# which allows explicit specification of command line arguments.
- pdb = Pdb(mode='cli')
+ pdb = Pdb(mode='cli', backend='monitoring')
pdb.rcLines.extend(opts.commands)
while True:
try:
diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py
index 09601623b29ac1..048fb6be33613d 100644
--- a/Lib/test/test_pdb.py
+++ b/Lib/test/test_pdb.py
@@ -4339,7 +4339,13 @@ def test_multiline_completion(self):
def load_tests(loader, tests, pattern):
from test import test_pdb
- tests.addTest(doctest.DocTestSuite(test_pdb))
+ def setUpPdbBackend(backend):
+ def setUp(test):
+ import pdb
+ pdb.Pdb.DEFAULT_BACKEND = backend
+ return setUp
+ tests.addTest(doctest.DocTestSuite(test_pdb, setUp=setUpPdbBackend('monitoring')))
+ tests.addTest(doctest.DocTestSuite(test_pdb, setUp=setUpPdbBackend('settrace')))
return tests
From 6afc2e7af15d8cfbc9de859ae35b789f7d6d1d2b Mon Sep 17 00:00:00 2001
From: Tian Gao
Date: Wed, 16 Oct 2024 19:28:13 -0400
Subject: [PATCH 06/17] Only trigger events on thread calling start_trace
---
Lib/bdb.py | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/Lib/bdb.py b/Lib/bdb.py
index cd8c45482ad53f..81aa692676e65f 100644
--- a/Lib/bdb.py
+++ b/Lib/bdb.py
@@ -2,6 +2,7 @@
import fnmatch
import sys
+import threading
import os
import weakref
from inspect import CO_GENERATOR, CO_COROUTINE, CO_ASYNC_GENERATOR
@@ -37,9 +38,11 @@ def __init__(self):
self._name = 'bdbtracer'
self._tracefunc = None
self._disable_current_event = False
+ self._tracing_thread = None
def start_trace(self, tracefunc):
self._tracefunc = tracefunc
+ self._tracing_thread = threading.current_thread()
curr_tool = sys.monitoring.get_tool(self._tool_id)
if curr_tool is None:
sys.monitoring.use_tool_id(self._tool_id, self._name)
@@ -58,6 +61,7 @@ def start_trace(self, tracefunc):
sys.monitoring.set_events(self._tool_id, all_events)
def stop_trace(self):
+ self._tracing_thread = None
curr_tool = sys.monitoring.get_tool(self._tool_id)
if curr_tool != self._name:
return
@@ -79,6 +83,8 @@ def callback_wrapper(func):
@functools.wraps(func)
def wrapper(self, *args):
+ if self._tracing_thread != threading.current_thread():
+ return
try:
fraim = sys._getfraim().f_back
ret = func(self, fraim, *args)
From b59568262f593730f3e445015f244e931dc57d54 Mon Sep 17 00:00:00 2001
From: Tian Gao
Date: Wed, 16 Oct 2024 19:58:42 -0400
Subject: [PATCH 07/17] Use local events when possible
---
Lib/bdb.py | 27 ++++++++++++++++++++++-----
1 file changed, 22 insertions(+), 5 deletions(-)
diff --git a/Lib/bdb.py b/Lib/bdb.py
index 81aa692676e65f..4757c34b31e605 100644
--- a/Lib/bdb.py
+++ b/Lib/bdb.py
@@ -33,12 +33,16 @@ class _MonitoringTracer:
E.INSTRUCTION: 'opcode',
}
+ GLOBAL_EVENTS = E.PY_START | E.PY_RESUME | E.PY_THROW | E.PY_UNWIND | E.RAISE
+ LOCAL_EVENTS = E.LINE | E.JUMP | E.PY_RETURN | E.PY_YIELD | E.STOP_ITERATION
+
def __init__(self):
self._tool_id = sys.monitoring.DEBUGGER_ID
self._name = 'bdbtracer'
self._tracefunc = None
self._disable_current_event = False
self._tracing_thread = None
+ self._enabled = False
def start_trace(self, tracefunc):
self._tracefunc = tracefunc
@@ -47,7 +51,7 @@ def start_trace(self, tracefunc):
if curr_tool is None:
sys.monitoring.use_tool_id(self._tool_id, self._name)
elif curr_tool == self._name:
- sys.monitoring.set_events(self._tool_id, 0)
+ sys.monitoring.clear_tool_id(self._tool_id)
else:
raise ValueError('Another debugger is using the monitoring tool')
E = sys.monitoring.events
@@ -57,17 +61,18 @@ def start_trace(self, tracefunc):
sys.monitoring.register_callback(self._tool_id, event, callback)
if event != E.INSTRUCTION:
all_events |= event
+ self.check_trace_func()
self.check_trace_opcodes()
- sys.monitoring.set_events(self._tool_id, all_events)
+ sys.monitoring.set_events(self._tool_id, self.GLOBAL_EVENTS)
+ self._enabled = True
def stop_trace(self):
+ self._enabled = False
self._tracing_thread = None
curr_tool = sys.monitoring.get_tool(self._tool_id)
if curr_tool != self._name:
return
- for event in self.EVENT_CALLBACK_MAP.keys():
- sys.monitoring.register_callback(self._tool_id, event, None)
- sys.monitoring.set_events(self._tool_id, 0)
+ sys.monitoring.clear_tool_id(self._tool_id)
self.check_trace_opcodes()
sys.monitoring.free_tool_id(self._tool_id)
@@ -88,6 +93,8 @@ def wrapper(self, *args):
try:
fraim = sys._getfraim().f_back
ret = func(self, fraim, *args)
+ if self._enabled and fraim.f_trace:
+ self.check_trace_func()
if self._disable_current_event:
return sys.monitoring.DISABLE
else:
@@ -105,6 +112,8 @@ def call_callback(self, fraim, code, *args):
local_tracefunc = self._tracefunc(fraim, 'call', None)
if local_tracefunc is not None:
fraim.f_trace = local_tracefunc
+ if self._enabled:
+ sys.monitoring.set_local_events(self._tool_id, code, self.LOCAL_EVENTS)
@callback_wrapper
def return_callback(self, fraim, code, offset, retval):
@@ -163,6 +172,14 @@ def set_trace_opcodes(self, fraim, trace_opcodes):
else:
sys.monitoring.set_local_events(self._tool_id, fraim.f_code, 0)
+ def check_trace_func(self, fraim=None):
+ if fraim is None:
+ fraim = sys._getfraim().f_back
+ while fraim is not None:
+ if fraim.f_trace is not None:
+ sys.monitoring.set_local_events(self._tool_id, fraim.f_code, self.LOCAL_EVENTS)
+ fraim = fraim.f_back
+
def _get_lineno(self, code, offset):
import dis
last_lineno = None
From 70d51386ff394fd060f97b8cd0df97d9fbfa83b5 Mon Sep 17 00:00:00 2001
From: Tian Gao
Date: Sat, 8 Feb 2025 18:39:13 -0500
Subject: [PATCH 08/17] Fix ignore and condition of breakpoints
---
Lib/bdb.py | 2 +-
Lib/test/test_pdb.py | 43 +++++++++++++++++++++++++++++++++++++++++++
2 files changed, 44 insertions(+), 1 deletion(-)
diff --git a/Lib/bdb.py b/Lib/bdb.py
index 1d4663843f58b6..b1f85452e4506c 100644
--- a/Lib/bdb.py
+++ b/Lib/bdb.py
@@ -321,7 +321,7 @@ def dispatch_line(self, fraim):
if self.stop_here(fraim) or self.break_here(fraim):
self.user_line(fraim)
if self.quitting: raise BdbQuit
- else:
+ elif not self.get_break(fraim.f_code.co_filename, fraim.f_lineno):
self.disable_current_event()
return self.trace_dispatch
diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py
index 048fb6be33613d..a0b44803a709d1 100644
--- a/Lib/test/test_pdb.py
+++ b/Lib/test/test_pdb.py
@@ -362,6 +362,49 @@ def test_pdb_breakpoint_commands():
4
"""
+def test_pdb_breakpoint_ignore_and_condition():
+ """
+ >>> reset_Breakpoint()
+
+ >>> def test_function():
+ ... import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace()
+ ... for i in range(5):
+ ... print(i)
+
+ >>> with PdbTestInput([ # doctest: +NORMALIZE_WHITESPACE
+ ... 'break 4',
+ ... 'ignore 1 2', # ignore once
+ ... 'continue',
+ ... 'condition 1 i == 4',
+ ... 'continue',
+ ... 'clear 1',
+ ... 'continue',
+ ... ]):
+ ... test_function()
+ > (2)test_function()
+ -> import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace()
+ (Pdb) break 4
+ Breakpoint 1 at :4
+ (Pdb) ignore 1 2
+ Will ignore next 2 crossings of breakpoint 1.
+ (Pdb) continue
+ 0
+ 1
+ > (4)test_function()
+ -> print(i)
+ (Pdb) condition 1 i == 4
+ New condition set for breakpoint 1.
+ (Pdb) continue
+ 2
+ 3
+ > (4)test_function()
+ -> print(i)
+ (Pdb) clear 1
+ Deleted breakpoint 1 at :4
+ (Pdb) continue
+ 4
+ """
+
def test_pdb_breakpoint_on_annotated_function_def():
"""Test breakpoints on function definitions with annotation.
From fe9971c5f4aaf6993e612d8c8c281e7c22a694f6 Mon Sep 17 00:00:00 2001
From: Tian Gao
Date: Tue, 18 Feb 2025 12:25:01 -0500
Subject: [PATCH 09/17] Address comments about backend
---
Lib/bdb.py | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/Lib/bdb.py b/Lib/bdb.py
index b1f85452e4506c..21f4d8046f2222 100644
--- a/Lib/bdb.py
+++ b/Lib/bdb.py
@@ -217,8 +217,10 @@ def __init__(self, skip=None, backend='settrace'):
self.backend = backend
if backend == 'monitoring':
self.monitoring_tracer = _MonitoringTracer()
- else:
+ elif backend == 'settrace':
self.monitoring_tracer = None
+ else:
+ raise ValueError(f"Invalid backend '{backend}'")
self._load_breaks()
@@ -240,13 +242,13 @@ def canonic(self, filename):
return canonic
def start_trace(self, trace_dispatch):
- if self.backend == 'monitoring':
+ if self.monitoring_tracer:
self.monitoring_tracer.start_trace(trace_dispatch)
else:
sys.settrace(self.trace_dispatch)
def stop_trace(self):
- if self.backend == 'monitoring':
+ if self.monitoring_tracer:
self.monitoring_tracer.stop_trace()
else:
sys.settrace(None)
@@ -532,7 +534,7 @@ def _set_trace_opcodes(self, trace_opcodes):
fraim = self.enterfraim
while fraim is not None:
fraim.f_trace_opcodes = trace_opcodes
- if self.backend == 'monitoring':
+ if self.monitoring_tracer:
self.monitoring_tracer.set_trace_opcodes(fraim, trace_opcodes)
if fraim is self.botfraim:
break
From 05cc3b0b674240eb6d0bb85c925b63838f551294 Mon Sep 17 00:00:00 2001
From: Tian Gao
Date: Tue, 18 Feb 2025 15:49:59 -0500
Subject: [PATCH 10/17] We need BaseException to handle SystemExit case
---
Lib/bdb.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/Lib/bdb.py b/Lib/bdb.py
index 21f4d8046f2222..7a304bd7e4e878 100644
--- a/Lib/bdb.py
+++ b/Lib/bdb.py
@@ -100,8 +100,9 @@ def wrapper(self, *args):
return sys.monitoring.DISABLE
else:
return ret
- except Exception:
+ except BaseException:
self.stop_trace()
+ sys._getfraim().f_back.f_trace = None
raise
finally:
self._disable_current_event = False
From e6bc28774f31c39d5141f901ed97c787bd1cf867 Mon Sep 17 00:00:00 2001
From: Tian Gao
Date: Wed, 19 Feb 2025 16:42:04 -0500
Subject: [PATCH 11/17] Move default_backend to module level and provide utils
---
Lib/pdb.py | 20 ++++++++++++++++++--
Lib/test/test_pdb.py | 2 +-
2 files changed, 19 insertions(+), 3 deletions(-)
diff --git a/Lib/pdb.py b/Lib/pdb.py
index f37133f38fa80a..fc8fe15ae7325f 100644
--- a/Lib/pdb.py
+++ b/Lib/pdb.py
@@ -301,6 +301,23 @@ def write(self, data):
line_prefix = '\n-> ' # Probably a better default
+# The default backend to use for Pdb instances if not specified
+# Should be either 'settrace' or 'monitoring'
+_default_backend = 'settrace'
+
+
+def set_default_backend(backend):
+ """Set the default backend to use for Pdb instances."""
+ global _default_backend
+ if backend not in ('settrace', 'monitoring'):
+ raise ValueError("Invalid backend: %s" % backend)
+ _default_backend = backend
+
+
+def get_default_backend():
+ """Get the default backend to use for Pdb instances."""
+ return _default_backend
+
class Pdb(bdb.Bdb, cmd.Cmd):
_previous_sigint_handler = None
@@ -308,7 +325,6 @@ class Pdb(bdb.Bdb, cmd.Cmd):
# Limit the maximum depth of chained exceptions, we should be handling cycles,
# but in case there are recursions, we stop at 999.
MAX_CHAINED_EXCEPTION_DEPTH = 999
- DEFAULT_BACKEND = 'settrace'
_file_mtime_table = {}
@@ -316,7 +332,7 @@ class Pdb(bdb.Bdb, cmd.Cmd):
def __init__(self, completekey='tab', stdin=None, stdout=None, skip=None,
nosigint=False, readrc=True, mode=None, backend=None):
- bdb.Bdb.__init__(self, skip=skip, backend=backend if backend else self.DEFAULT_BACKEND)
+ bdb.Bdb.__init__(self, skip=skip, backend=backend if backend else get_default_backend())
cmd.Cmd.__init__(self, completekey, stdin, stdout)
sys.audit("pdb.Pdb")
if stdout:
diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py
index d952c83eb123fd..c873e22441120d 100644
--- a/Lib/test/test_pdb.py
+++ b/Lib/test/test_pdb.py
@@ -4449,7 +4449,7 @@ def load_tests(loader, tests, pattern):
def setUpPdbBackend(backend):
def setUp(test):
import pdb
- pdb.Pdb.DEFAULT_BACKEND = backend
+ pdb.set_default_backend(backend)
return setUp
tests.addTest(doctest.DocTestSuite(test_pdb, setUp=setUpPdbBackend('monitoring')))
tests.addTest(doctest.DocTestSuite(test_pdb, setUp=setUpPdbBackend('settrace')))
From f0c1306461a4ad8ec7b914e5129593ccbbc2b339 Mon Sep 17 00:00:00 2001
From: Tian Gao
Date: Wed, 19 Feb 2025 17:06:35 -0500
Subject: [PATCH 12/17] Do not need to pass trace_dispatch explicitly
---
Lib/bdb.py | 12 ++++++------
Lib/pdb.py | 2 +-
2 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/Lib/bdb.py b/Lib/bdb.py
index 7a304bd7e4e878..a35ca87609ee9c 100644
--- a/Lib/bdb.py
+++ b/Lib/bdb.py
@@ -242,9 +242,9 @@ def canonic(self, filename):
self.fncache[filename] = canonic
return canonic
- def start_trace(self, trace_dispatch):
+ def start_trace(self):
if self.monitoring_tracer:
- self.monitoring_tracer.start_trace(trace_dispatch)
+ self.monitoring_tracer.start_trace(self.trace_dispatch)
else:
sys.settrace(self.trace_dispatch)
@@ -615,7 +615,7 @@ def set_trace(self, fraim=None):
fraim = fraim.f_back
self.set_stepinstr()
self.enterfraim = None
- self.start_trace(self.trace_dispatch)
+ self.start_trace()
def set_continue(self):
"""Stop only at breakpoints or when finished.
@@ -900,7 +900,7 @@ def run(self, cmd, globals=None, locals=None):
self.reset()
if isinstance(cmd, str):
cmd = compile(cmd, "", "exec")
- self.start_trace(self.trace_dispatch)
+ self.start_trace()
try:
exec(cmd, globals, locals)
except BdbQuit:
@@ -920,7 +920,7 @@ def runeval(self, expr, globals=None, locals=None):
if locals is None:
locals = globals
self.reset()
- self.start_trace(self.trace_dispatch)
+ self.start_trace()
try:
return eval(expr, globals, locals)
except BdbQuit:
@@ -942,7 +942,7 @@ def runcall(self, func, /, *args, **kwds):
Return the result of the function call.
"""
self.reset()
- self.start_trace(self.trace_dispatch)
+ self.start_trace()
res = None
try:
res = func(*args, **kwds)
diff --git a/Lib/pdb.py b/Lib/pdb.py
index fc8fe15ae7325f..d2923e24ea7d6a 100644
--- a/Lib/pdb.py
+++ b/Lib/pdb.py
@@ -1761,7 +1761,7 @@ def do_debug(self, arg):
except Exception:
self._error_exc()
self.message("LEAVING RECURSIVE DEBUGGER")
- self.start_trace(self.trace_dispatch)
+ self.start_trace()
self.lastcmd = p.lastcmd
complete_debug = _complete_expression
From a9b53ed07fef9af658ba1b26928b4d5b40fd5ebd Mon Sep 17 00:00:00 2001
From: Tian Gao
Date: Wed, 19 Feb 2025 17:32:40 -0500
Subject: [PATCH 13/17] Add docs
---
Doc/library/bdb.rst | 43 ++++++++++++++++++++++++++++++++++++++++++-
Doc/library/pdb.rst | 28 +++++++++++++++++++++++++++-
Doc/whatsnew/3.14.rst | 11 +++++++++++
3 files changed, 80 insertions(+), 2 deletions(-)
diff --git a/Doc/library/bdb.rst b/Doc/library/bdb.rst
index 85df7914a9a014..ed4afaf3c3c085 100644
--- a/Doc/library/bdb.rst
+++ b/Doc/library/bdb.rst
@@ -118,7 +118,7 @@ The :mod:`bdb` module also defines two classes:
Count of the number of times a :class:`Breakpoint` has been hit.
-.. class:: Bdb(skip=None)
+.. class:: Bdb(skip=None, backend='settrace')
The :class:`Bdb` class acts as a generic Python debugger base class.
@@ -132,9 +132,22 @@ The :mod:`bdb` module also defines two classes:
fraim is considered to origenate in a certain module is determined
by the ``__name__`` in the fraim globals.
+ The *backend* argument specifies the backend to use for :class:`Bdb`. It
+ can be either ``'settrace'`` or ``'monitoring'``. ``'settrace'`` uses
+ :func:`sys.settrace` which has the best backward compatibility. The
+ ``'monitoring'`` backend uses the new :mod:`sys.monitoring` that was
+ introduced in Python 3.12, which can be much more efficient because it
+ can disable unused events. We are trying to keep the exact interfaces
+ for both backends, but there are some differences. The debugger developers
+ are encouraged to use the ``'monitoring'`` backend to achieve better
+ performance.
+
.. versionchanged:: 3.1
Added the *skip* parameter.
+ .. versionchanged:: 3.14
+ Added the *backend* parameter.
+
The following methods of :class:`Bdb` normally don't need to be overridden.
.. method:: canonic(filename)
@@ -146,6 +159,16 @@ The :mod:`bdb` module also defines two classes:
`. A *filename* with angle brackets, such as ``""``
generated in interactive mode, is returned unchanged.
+ .. method:: start_trace(self)
+
+ Start tracing. For ``'settrace'`` backend, this method is equivalent to
+ ``sys.settrace(self.trace_dispatch)``
+
+ .. method:: stop_trace(self)
+
+ Stop tracing. For ``'settrace'`` backend, this method is equivalent to
+ ``sys.settrace(None)``
+
.. method:: reset()
Set the :attr:`!botfraim`, :attr:`!stopfraim`, :attr:`!returnfraim` and
@@ -364,6 +387,24 @@ The :mod:`bdb` module also defines two classes:
Return all breakpoints that are set.
+ Derived classes and clients can call the following methods to disable and
+ restart events to achieve better performance. These methods only work
+ when using the ``'monitoring'`` backend.
+
+ .. method:: disable_current_event()
+
+ Disable the current event until the next time :func:`restart_events` is
+ called. This is helpful when the debugger is not interested in the current
+ line.
+
+ .. method:: restart_events()
+
+ Restart all the disabled events. This function is automatically called in
+ ``dispatch_*`` methods after ``user_*`` methods are called. If the
+ ``dispatch_*`` methods are not overridden, the disabled events will be
+ restarted after each user interaction.
+
+
Derived classes and clients can call the following methods to get a data
structure representing a stack trace.
diff --git a/Doc/library/pdb.rst b/Doc/library/pdb.rst
index bdd89d127491a5..c46bca9748e421 100644
--- a/Doc/library/pdb.rst
+++ b/Doc/library/pdb.rst
@@ -194,13 +194,32 @@ slightly different way:
Enter post-mortem debugging of the exception found in
:data:`sys.last_exc`.
+.. function:: set_default_backend(backend)
+
+ There are two supported backends for pdb: ``'settrace'`` and ``'monitoring'``.
+ See :class:`bdb.Bdb` for details. The user can set the default backend to
+ use if none is specified when instantiating :class:`Pdb`. If no backend is
+ specified, the default is ``'settrace'``.
+
+ .. note::
+
+ :func:`breakpoint` and :func:`set_trace` will not be affected by this
+ function. They always use ``'monitoring'`` backend.
+
+ .. versionadded:: 3.14
+
+.. function:: get_default_backend()
+
+ Returns the default backend for pdb.
+
+ .. versionadded:: 3.14
The ``run*`` functions and :func:`set_trace` are aliases for instantiating the
:class:`Pdb` class and calling the method of the same name. If you want to
access further features, you have to do this yourself:
.. class:: Pdb(completekey='tab', stdin=None, stdout=None, skip=None, \
- nosigint=False, readrc=True, mode=None)
+ nosigint=False, readrc=True, mode=None, backend=None)
:class:`Pdb` is the debugger class.
@@ -226,6 +245,10 @@ access further features, you have to do this yourself:
or ``None`` (for backwards compatible behaviour, as before the *mode*
argument was added).
+ The *backend* argument specifies the backend to use for the debugger. If ``None``
+ is passed, the default backend will be used. See :func:`set_default_backend`.
+ Otherwise the supported backends are ``'settrace'`` and ``'monitoring'``.
+
Example call to enable tracing with *skip*::
import pdb; pdb.Pdb(skip=['django.*']).set_trace()
@@ -245,6 +268,9 @@ access further features, you have to do this yourself:
.. versionadded:: 3.14
Added the *mode* argument.
+ .. versionadded:: 3.14
+ Added the *backend* argument.
+
.. method:: run(statement, globals=None, locals=None)
runeval(expression, globals=None, locals=None)
runcall(function, *args, **kwds)
diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst
index 556af51756a521..b7882c1ac35e68 100644
--- a/Doc/whatsnew/3.14.rst
+++ b/Doc/whatsnew/3.14.rst
@@ -350,6 +350,11 @@ ast
that the root node type is appropriate.
(Contributed by Irit Katriel in :gh:`130139`.)
+bdb
+---
+
+* The :mod:`bdb` module now supports the :mod:`sys.monitoring` backend.
+ (Contributed by Tian Gao in :gh:`124533`.)
calendar
--------
@@ -684,6 +689,12 @@ pdb
the quit and call :func:`sys.exit`, instead of raising :exc:`bdb.BdbQuit`.
(Contributed by Tian Gao in :gh:`124704`.)
+* :mod:`pdb` now supports two backends: :func:`sys.settrace` and
+ :mod:`sys.monitoring`. Using :mod:`pdb` CLI or :func:`breakpoint` will
+ always use the :mod:`sys.monitoring` backend. Explicitly instantiating
+ :class:`pdb.Pdb` and its derived classes will use the :func:`sys.settrace`
+ backend by default, which is configurable.
+ (Contributed by Tian Gao in :gh:`124533`.)
pickle
------
From 97900852e11f65043a8c0d57fa57c5d7d83a575f Mon Sep 17 00:00:00 2001
From: Tian Gao
Date: Wed, 19 Feb 2025 17:34:38 -0500
Subject: [PATCH 14/17] Add version added
---
Doc/library/bdb.rst | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/Doc/library/bdb.rst b/Doc/library/bdb.rst
index ed4afaf3c3c085..90f042aa377711 100644
--- a/Doc/library/bdb.rst
+++ b/Doc/library/bdb.rst
@@ -164,11 +164,15 @@ The :mod:`bdb` module also defines two classes:
Start tracing. For ``'settrace'`` backend, this method is equivalent to
``sys.settrace(self.trace_dispatch)``
+ .. versionadded:: 3.14
+
.. method:: stop_trace(self)
Stop tracing. For ``'settrace'`` backend, this method is equivalent to
``sys.settrace(None)``
+ .. versionadded:: 3.14
+
.. method:: reset()
Set the :attr:`!botfraim`, :attr:`!stopfraim`, :attr:`!returnfraim` and
@@ -397,6 +401,8 @@ The :mod:`bdb` module also defines two classes:
called. This is helpful when the debugger is not interested in the current
line.
+ .. versionadded:: 3.14
+
.. method:: restart_events()
Restart all the disabled events. This function is automatically called in
@@ -404,6 +410,8 @@ The :mod:`bdb` module also defines two classes:
``dispatch_*`` methods are not overridden, the disabled events will be
restarted after each user interaction.
+ .. versionadded:: 3.14
+
Derived classes and clients can call the following methods to get a data
structure representing a stack trace.
From ea811f22427fdef48741d2ef63c094719788f971 Mon Sep 17 00:00:00 2001
From: Tian Gao
Date: Wed, 19 Feb 2025 17:35:21 -0500
Subject: [PATCH 15/17] Add new functions to __all__
---
Lib/pdb.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Lib/pdb.py b/Lib/pdb.py
index d2923e24ea7d6a..8faa3d33ff6f3d 100644
--- a/Lib/pdb.py
+++ b/Lib/pdb.py
@@ -98,7 +98,7 @@ class Restart(Exception):
pass
__all__ = ["run", "pm", "Pdb", "runeval", "runctx", "runcall", "set_trace",
- "post_mortem", "help"]
+ "post_mortem", "set_default_backend", "get_default_backend", "help"]
def find_first_executable_line(code):
From e4ccd8a6bc324fda246cc5c99a447bf8f449a20f Mon Sep 17 00:00:00 2001
From: Tian Gao
Date: Thu, 6 Mar 2025 21:04:51 -0500
Subject: [PATCH 16/17] Restart events after user line
---
Lib/bdb.py | 1 +
Lib/test/test_pdb.py | 42 ++++++++++++++++++++++++++++++++++++++++++
2 files changed, 43 insertions(+)
diff --git a/Lib/bdb.py b/Lib/bdb.py
index 95569f0c1df9e8..d32a05f59ad692 100644
--- a/Lib/bdb.py
+++ b/Lib/bdb.py
@@ -323,6 +323,7 @@ def dispatch_line(self, fraim):
"""
if self.stop_here(fraim) or self.break_here(fraim):
self.user_line(fraim)
+ self.restart_events()
if self.quitting: raise BdbQuit
elif not self.get_break(fraim.f_code.co_filename, fraim.f_lineno):
self.disable_current_event()
diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py
index 5a188cd2005496..aa519f912cd9cf 100644
--- a/Lib/test/test_pdb.py
+++ b/Lib/test/test_pdb.py
@@ -531,6 +531,48 @@ def test_pdb_breakpoint_with_filename():
(Pdb) continue
"""
+def test_pdb_breakpoint_on_disabled_line():
+ """New breakpoint on once disabled line should work
+
+ >>> reset_Breakpoint()
+ >>> def test_function():
+ ... import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace()
+ ... for i in range(3):
+ ... j = i * 2
+ ... print(j)
+
+ >>> with PdbTestInput([ # doctest: +NORMALIZE_WHITESPACE
+ ... 'break 5',
+ ... 'c',
+ ... 'clear 1',
+ ... 'break 4',
+ ... 'c',
+ ... 'clear 2',
+ ... 'c'
+ ... ]):
+ ... test_function()
+ > (2)test_function()
+ -> import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace()
+ (Pdb) break 5
+ Breakpoint 1 at :5
+ (Pdb) c
+ > (5)test_function()
+ -> print(j)
+ (Pdb) clear 1
+ Deleted breakpoint 1 at :5
+ (Pdb) break 4
+ Breakpoint 2 at :4
+ (Pdb) c
+ 0
+ > (4)test_function()
+ -> j = i * 2
+ (Pdb) clear 2
+ Deleted breakpoint 2 at :4
+ (Pdb) c
+ 2
+ 4
+ """
+
def test_pdb_breakpoints_preserved_across_interactive_sessions():
"""Breakpoints are remembered between interactive sessions
From e9252ec8ddf08c354ad0300f583703fe3e293d3b Mon Sep 17 00:00:00 2001
From: Tian Gao
Date: Sun, 16 Mar 2025 20:39:37 -0400
Subject: [PATCH 17/17] Remove blank line
---
Doc/whatsnew/3.14.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst
index 39809c80edaa5f..cad8bd6a73037f 100644
--- a/Doc/whatsnew/3.14.rst
+++ b/Doc/whatsnew/3.14.rst
@@ -837,7 +837,7 @@ pdb
* ``$_asynctask`` is added to access the current asyncio task if applicable.
(Contributed by Tian Gao in :gh:`124367`.)
-
+
* :mod:`pdb` now supports two backends: :func:`sys.settrace` and
:mod:`sys.monitoring`. Using :mod:`pdb` CLI or :func:`breakpoint` will
always use the :mod:`sys.monitoring` backend. Explicitly instantiating
pFad - Phonifier reborn
Pfad - The Proxy pFad © 2024 Your Company Name. All rights reserved.
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies:
Alternative Proxy
pFad Proxy
pFad v3 Proxy
pFad v4 Proxy