From 3aa51a0afd04bb8dbdc3483ce5b47ab244518570 Mon Sep 17 00:00:00 2001 From: Fabio Zadrozny Date: Fri, 10 Nov 2023 08:43:39 -0300 Subject: [PATCH] wip --- .../_pydevd_bundle/pydevd_thread_lifecycle.py | 9 +- .../pydevd_sys_monitoring.py | 10 ++ plugins/org.python.pydev.core/pysrc/pydevd.py | 38 +++++-- .../pysrc/tests_python/test_sys_monitoring.py | 104 ++++++++++++++---- 4 files changed, 131 insertions(+), 30 deletions(-) diff --git a/plugins/org.python.pydev.core/pysrc/_pydevd_bundle/pydevd_thread_lifecycle.py b/plugins/org.python.pydev.core/pysrc/_pydevd_bundle/pydevd_thread_lifecycle.py index 069b6b6a9a..1426004c8e 100644 --- a/plugins/org.python.pydev.core/pysrc/_pydevd_bundle/pydevd_thread_lifecycle.py +++ b/plugins/org.python.pydev.core/pysrc/_pydevd_bundle/pydevd_thread_lifecycle.py @@ -1,9 +1,12 @@ from _pydevd_bundle import pydevd_utils from _pydevd_bundle.pydevd_additional_thread_info import set_additional_thread_info from _pydevd_bundle.pydevd_comm_constants import CMD_STEP_INTO, CMD_THREAD_SUSPEND -from _pydevd_bundle.pydevd_constants import PYTHON_SUSPEND, STATE_SUSPEND, get_thread_id, STATE_RUN +from _pydevd_bundle.pydevd_constants import PYTHON_SUSPEND, STATE_SUSPEND, get_thread_id, STATE_RUN, \ + USE_SYS_MONITORING from _pydev_bundle._pydev_saved_modules import threading from _pydev_bundle import pydev_log +import sys +from _pydevd_sys_monitoring import pydevd_sys_monitoring def pydevd_find_thread_by_id(thread_id): @@ -94,3 +97,7 @@ def suspend_all_threads(py_db, except_thread): py_db.set_trace_for_frame_and_parents(frame) finally: frame = None + + if USE_SYS_MONITORING: + # After suspending the frames we need the monitoring to be reset. + pydevd_sys_monitoring.restart_events() diff --git a/plugins/org.python.pydev.core/pysrc/_pydevd_sys_monitoring/pydevd_sys_monitoring.py b/plugins/org.python.pydev.core/pysrc/_pydevd_sys_monitoring/pydevd_sys_monitoring.py index b83314a787..a2dc078fdd 100644 --- a/plugins/org.python.pydev.core/pysrc/_pydevd_sys_monitoring/pydevd_sys_monitoring.py +++ b/plugins/org.python.pydev.core/pysrc/_pydevd_sys_monitoring/pydevd_sys_monitoring.py @@ -281,6 +281,10 @@ def enable_line_tracing(code): monitor.set_local_events(DEBUGGER_ID, code, events | monitor.events.LINE) +def enable_code_tracing(code): + enable_line_tracing(code) + + def _line_event(code, line): print('line event', code, line) @@ -456,3 +460,9 @@ def stop_monitoring(all_threads=False): if thread_info is None: return thread_info.trace = False + + +def restart_events(): + print('restart events') + sys.monitoring.restart_events() + diff --git a/plugins/org.python.pydev.core/pysrc/pydevd.py b/plugins/org.python.pydev.core/pysrc/pydevd.py index 32c225c96d..ee499f743e 100644 --- a/plugins/org.python.pydev.core/pysrc/pydevd.py +++ b/plugins/org.python.pydev.core/pysrc/pydevd.py @@ -1207,6 +1207,9 @@ def set_tracing_for_untraced_contexts(self): threads = None additional_info = None + if USE_SYS_MONITORING: + pydevd_sys_monitoring.restart_events() + @property def multi_threads_single_notification(self): return self._threads_suspended_single_notification.multi_threads_single_notification @@ -1935,6 +1938,10 @@ def set_suspend(self, thread, stop_reason, suspend_other_threads=False, is_pause # Suspend all except the current one (which we're currently suspending already). suspend_all_threads(self, except_thread=thread) + if is_pause and not suspend_other_threads: + # When suspending other threads this is already called. + pydevd_sys_monitoring.restart_events() + def _send_breakpoint_condition_exception(self, thread, conditional_breakpoint_exception_tuple): """If conditional breakpoint raises an exception during evaluation send exception details to java @@ -2278,18 +2285,29 @@ def set_trace_for_frame_and_parents(self, frame, **kwargs): while frame is not None: # Don't change the tracing on debugger-related files file_type = self.get_file_type(frame) + if USE_SYS_MONITORING: + if file_type is None: + if disable: + pydev_log.debug('Disable tracing of frame: %s - %s', frame.f_code.co_filename, frame.f_code.co_name) - if file_type is None: - if disable: - pydev_log.debug('Disable tracing of frame: %s - %s', frame.f_code.co_filename, frame.f_code.co_name) - if frame.f_trace is not None and frame.f_trace is not NO_FTRACE: - frame.f_trace = NO_FTRACE - - elif frame.f_trace is not self.trace_dispatch: - pydev_log.debug('Set tracing of frame: %s - %s', frame.f_code.co_filename, frame.f_code.co_name) - frame.f_trace = self.trace_dispatch + else: + pydev_log.debug('Set tracing of frame: %s - %s', frame.f_code.co_filename, frame.f_code.co_name) + pydevd_sys_monitoring.enable_code_tracing(frame.f_code) + else: + pydev_log.debug('SKIP set tracing of frame: %s - %s', frame.f_code.co_filename, frame.f_code.co_name) else: - pydev_log.debug('SKIP set tracing of frame: %s - %s', frame.f_code.co_filename, frame.f_code.co_name) + # Not using sys.monitoring. + if file_type is None: + if disable: + pydev_log.debug('Disable tracing of frame: %s - %s', frame.f_code.co_filename, frame.f_code.co_name) + if frame.f_trace is not None and frame.f_trace is not NO_FTRACE: + frame.f_trace = NO_FTRACE + + elif frame.f_trace is not self.trace_dispatch: + pydev_log.debug('Set tracing of frame: %s - %s', frame.f_code.co_filename, frame.f_code.co_name) + frame.f_trace = self.trace_dispatch + else: + pydev_log.debug('SKIP set tracing of frame: %s - %s', frame.f_code.co_filename, frame.f_code.co_name) frame = frame.f_back diff --git a/plugins/org.python.pydev.core/pysrc/tests_python/test_sys_monitoring.py b/plugins/org.python.pydev.core/pysrc/tests_python/test_sys_monitoring.py index 25e94c734a..c1646ac41e 100644 --- a/plugins/org.python.pydev.core/pysrc/tests_python/test_sys_monitoring.py +++ b/plugins/org.python.pydev.core/pysrc/tests_python/test_sys_monitoring.py @@ -1,10 +1,71 @@ import sys +import pytest +import threading DEBUGGER_ID = sys.monitoring.DEBUGGER_ID monitor = sys.monitoring -def test_change_line_during_trace(): +def _disable_monitoring(): + if monitor.get_tool(DEBUGGER_ID) == 'pydevd': + sys.monitoring.set_events(sys.monitoring.DEBUGGER_ID, 0) + monitor.register_callback(DEBUGGER_ID, monitor.events.PY_START , None) + monitor.register_callback(DEBUGGER_ID, monitor.events.PY_RESUME, None) + monitor.register_callback(DEBUGGER_ID, monitor.events.LINE, None) + sys.monitoring.free_tool_id(DEBUGGER_ID) + + +@pytest.fixture +def with_monitoring(): + monitor.use_tool_id(DEBUGGER_ID, 'pydevd') + yield + _disable_monitoring() + + +def test_disabling_code(with_monitoring): + + executed = [] + + def _start_method(code, offset): + if code.co_name == 'method': + executed.append(('start', code.co_name, offset)) + monitor.set_local_events(DEBUGGER_ID, code, monitor.events.LINE) + return monitor.DISABLE + + def _on_line(code, offset): + if code.co_name == 'method': + executed.append(('line', code.co_name, offset)) + return monitor.DISABLE + + monitor.set_events(DEBUGGER_ID, monitor.events.PY_START | monitor.events.PY_RESUME) + + monitor.register_callback(DEBUGGER_ID, monitor.events.PY_START , _start_method) + monitor.register_callback(DEBUGGER_ID, monitor.events.PY_RESUME, _start_method) + monitor.register_callback(DEBUGGER_ID, monitor.events.LINE, _on_line) + + def method(): + a = 1 + + method() + method() + assert executed == [('start', 'method', 0), ('line', 'method', method.__code__.co_firstlineno + 1)] + + del executed[:] + t = threading.Thread(target=method) + t.start() + t.join() + + # If disable once, even on a new thread we won't get notifications + assert not executed + + monitor.restart_events() + t = threading.Thread(target=method) + t.start() + t.join() + assert executed == [('start', 'method', 0), ('line', 'method', method.__code__.co_firstlineno + 1)] + + +def test_change_line_during_trace(with_monitoring): code_to_break_at_line = {} do_change_line = [0] lines_traced = [] @@ -23,25 +84,30 @@ def _on_line(code, line): print(frame.f_lineno) frame.f_lineno = line - 2 - monitor.use_tool_id(DEBUGGER_ID, 'pydevd') - try: - monitor.set_events(DEBUGGER_ID, monitor.events.PY_START | monitor.events.PY_RESUME) + monitor.set_events(DEBUGGER_ID, monitor.events.PY_START | monitor.events.PY_RESUME) - monitor.register_callback(DEBUGGER_ID, monitor.events.PY_START , _start_method) - monitor.register_callback(DEBUGGER_ID, monitor.events.PY_RESUME, _start_method) - monitor.register_callback(DEBUGGER_ID, monitor.events.LINE, _on_line) + monitor.register_callback(DEBUGGER_ID, monitor.events.PY_START , _start_method) + monitor.register_callback(DEBUGGER_ID, monitor.events.PY_RESUME, _start_method) + monitor.register_callback(DEBUGGER_ID, monitor.events.LINE, _on_line) - def method1(): # code.co_firstlineno - a = 1 # code.co_firstlineno + 1 - lines_traced.append('before a=2') # code.co_firstlineno + 2 - a = 2 # code.co_firstlineno + 3 - lines_traced.append('before a=3') # code.co_firstlineno + 4 - # a = 3 # code.co_firstlineno + 5 + def method1(): # code.co_firstlineno + a = 1 # code.co_firstlineno + 1 + lines_traced.append('before a=2') # code.co_firstlineno + 2 + a = 2 # code.co_firstlineno + 3 + lines_traced.append('before a=3') # code.co_firstlineno + 4 + # a = 3 # code.co_firstlineno + 5 - for _i in range(3): - method1() + for _i in range(3): + method1() - assert lines_traced == [] - sys.monitoring.set_events(sys.monitoring.DEBUGGER_ID, 0) - finally: - sys.monitoring.free_tool_id(DEBUGGER_ID) + assert lines_traced == [ + 'before a=2', + 'before a=3', + + 'before a=2', + 'before a=2', + + 'before a=3', + 'before a=2', + 'before a=3', + ]