From 7a9c4711c976fa860cb0995683fbc9187393e1be Mon Sep 17 00:00:00 2001 From: Fabio Zadrozny Date: Thu, 9 Nov 2023 16:13:10 -0300 Subject: [PATCH] wip --- .../pysrc/_pydevd_bundle/pydevd_constants.py | 4 +- .../_pydevd_bundle/pydevd_daemon_thread.py | 6 +- .../pysrc/_pydevd_bundle/pydevd_frame.py | 11 +- .../pydevd_trace_dispatch_regular.py | 7 +- .../pydevd_sys_monitoring.py | 269 +++++++++++++----- .../pydevd_sys_monitoring_manager.py | 25 ++ plugins/org.python.pydev.core/pysrc/pydevd.py | 31 +- .../pysrc/pydevd_tracing.py | 12 +- .../pysrc/tests_python/test_sys_monitoring.py | 44 +-- .../package_manager/CondaPackageManager.java | 4 + 10 files changed, 311 insertions(+), 102 deletions(-) create mode 100644 plugins/org.python.pydev.core/pysrc/_pydevd_sys_monitoring/pydevd_sys_monitoring_manager.py diff --git a/plugins/org.python.pydev.core/pysrc/_pydevd_bundle/pydevd_constants.py b/plugins/org.python.pydev.core/pysrc/_pydevd_bundle/pydevd_constants.py index 310f27bbcb..8cfeec4ae2 100644 --- a/plugins/org.python.pydev.core/pysrc/_pydevd_bundle/pydevd_constants.py +++ b/plugins/org.python.pydev.core/pysrc/_pydevd_bundle/pydevd_constants.py @@ -171,6 +171,8 @@ def _current_frames(): IS_PY311_OR_GREATER = sys.version_info >= (3, 11) IS_PY312_OR_GREATER = sys.version_info >= (3, 12) +USE_SYS_MONITORING = IS_PY312_OR_GREATER and hasattr(sys, 'monitoring') + def version_str(v): return '.'.join((str(x) for x in v[:3])) + ''.join((str(x) for x in v[3:])) @@ -507,7 +509,7 @@ def iter_chars(b): return iter(b) -if IS_JYTHON: +if IS_JYTHON or USE_SYS_MONITORING: def NO_FTRACE(frame, event, arg): return None diff --git a/plugins/org.python.pydev.core/pysrc/_pydevd_bundle/pydevd_daemon_thread.py b/plugins/org.python.pydev.core/pysrc/_pydevd_bundle/pydevd_daemon_thread.py index d221b5a7f2..80149acba3 100644 --- a/plugins/org.python.pydev.core/pysrc/_pydevd_bundle/pydevd_daemon_thread.py +++ b/plugins/org.python.pydev.core/pysrc/_pydevd_bundle/pydevd_daemon_thread.py @@ -3,12 +3,13 @@ from _pydevd_bundle.pydevd_utils import notify_about_gevent_if_needed import weakref from _pydevd_bundle.pydevd_constants import IS_JYTHON, IS_IRONPYTHON, \ - PYDEVD_APPLY_PATCHING_TO_HIDE_PYDEVD_THREADS + PYDEVD_APPLY_PATCHING_TO_HIDE_PYDEVD_THREADS, USE_SYS_MONITORING from _pydev_bundle.pydev_log import exception as pydev_log_exception import sys from _pydev_bundle import pydev_log import pydevd_tracing from _pydevd_bundle.pydevd_collect_bytecode_info import iter_instructions +from _pydevd_sys_monitoring import pydevd_sys_monitoring if IS_JYTHON: import org.python.core as JyCore # @UnresolvedImport @@ -67,6 +68,9 @@ def do_kill_pydev_thread(self): def _stop_trace(self): if self.pydev_do_not_trace: + if USE_SYS_MONITORING: + pydevd_sys_monitoring.stop_monitoring(all_threads=False) + return pydevd_tracing.SetTrace(None) # no debugging on this thread diff --git a/plugins/org.python.pydev.core/pysrc/_pydevd_bundle/pydevd_frame.py b/plugins/org.python.pydev.core/pysrc/_pydevd_bundle/pydevd_frame.py index 1460551f8c..387516b66f 100644 --- a/plugins/org.python.pydev.core/pysrc/_pydevd_bundle/pydevd_frame.py +++ b/plugins/org.python.pydev.core/pysrc/_pydevd_bundle/pydevd_frame.py @@ -5,7 +5,8 @@ from _pydev_bundle import pydev_log from _pydevd_bundle import pydevd_dont_trace from _pydevd_bundle.pydevd_constants import (RETURN_VALUES_DICT, NO_FTRACE, - EXCEPTION_TYPE_HANDLED, EXCEPTION_TYPE_USER_UNHANDLED, PYDEVD_IPYTHON_CONTEXT) + EXCEPTION_TYPE_HANDLED, EXCEPTION_TYPE_USER_UNHANDLED, PYDEVD_IPYTHON_CONTEXT, + USE_SYS_MONITORING) from _pydevd_bundle.pydevd_frame_utils import add_exception_to_frame, just_raised, remove_exception_from_frame, ignore_exception_trace from _pydevd_bundle.pydevd_utils import get_clsname_for_code from pydevd_file_utils import get_abs_path_real_path_and_base_from_frame @@ -1251,3 +1252,11 @@ def trace_dispatch(self, frame, event, arg): info.is_tracing -= 1 # end trace_dispatch + + +if USE_SYS_MONITORING: + + class PyDBFrame: + + def __init__(self, *args, **kwargs): + raise RuntimeError("Not expected to be used in sys.monitoring.") diff --git a/plugins/org.python.pydev.core/pysrc/_pydevd_bundle/pydevd_trace_dispatch_regular.py b/plugins/org.python.pydev.core/pysrc/_pydevd_bundle/pydevd_trace_dispatch_regular.py index 88a3f0832c..de20787cc2 100644 --- a/plugins/org.python.pydev.core/pysrc/_pydevd_bundle/pydevd_trace_dispatch_regular.py +++ b/plugins/org.python.pydev.core/pysrc/_pydevd_bundle/pydevd_trace_dispatch_regular.py @@ -2,7 +2,7 @@ from _pydev_bundle.pydev_log import exception as pydev_log_exception from _pydev_bundle._pydev_saved_modules import threading from _pydevd_bundle.pydevd_constants import (get_current_thread_id, NO_FTRACE, - USE_CUSTOM_SYS_CURRENT_FRAMES_MAP, ForkSafeLock) + USE_CUSTOM_SYS_CURRENT_FRAMES_MAP, ForkSafeLock, USE_SYS_MONITORING) from pydevd_file_utils import get_abs_path_real_path_and_base_from_frame, NORM_PATHS_AND_BASE_CONTAINER # IFDEF CYTHON @@ -488,3 +488,8 @@ def __call__(self, frame, event, arg): return _original_call(self, frame, event, arg) ThreadTracer.__call__ = __call__ + +if USE_SYS_MONITORING: + + def fix_top_level_trace_and_get_trace_func(*args, **kwargs): + raise RuntimeError('Not used in sys.monitoring mode.') 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 7d0c1e0ed6..93a0d0565b 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 @@ -1,5 +1,4 @@ -from _pydevd_bundle.pydevd_constants import IS_PY312_OR_GREATER, \ - GlobalDebuggerHolder +from _pydevd_bundle.pydevd_constants import GlobalDebuggerHolder, ForkSafeLock import threading from _pydevd_bundle.pydevd_additional_thread_info import _set_additional_thread_info_lock, PyDBAdditionalThreadInfo from pydevd_file_utils import NORM_PATHS_AND_BASE_CONTAINER, \ @@ -9,30 +8,63 @@ import sys from types import CodeType from typing import Dict, Optional, Set +import traceback DEBUGGER_ID = sys.monitoring.DEBUGGER_ID monitor = sys.monitoring -USING_FRAME_MONITORING = False -if IS_PY312_OR_GREATER: - import sys - USING_FRAME_MONITORING = hasattr(sys, 'monitoring') - _thread_local_info = threading.local() _get_ident = threading.get_ident -_thread_active = threading._active +_thread_active = threading._active # noqa + +STATE_SUSPEND: int = 2 +CMD_STEP_INTO: int = 107 +CMD_STEP_OVER: int = 108 +CMD_STEP_OVER_MY_CODE: int = 159 +CMD_STEP_INTO_MY_CODE: int = 144 +CMD_STEP_INTO_COROUTINE: int = 206 +CMD_SMART_STEP_INTO: int = 128 +can_skip: bool = True +CMD_STEP_RETURN: int = 109 +CMD_STEP_OVER_MY_CODE: int = 159 +CMD_STEP_RETURN_MY_CODE: int = 160 + +# Cache where we should keep that we completely skipped entering some context. +# It needs to be invalidated when: +# - Breakpoints are changed +# It can be used when running regularly (without step over/step in/step return) +global_cache_skips = {} +global_cache_frame_skips = {} + +_global_notify_skipped_step_in = False +_global_notify_skipped_step_in_lock = ForkSafeLock() + + +def notify_skipped_step_in_because_of_filters(py_db, frame): + global _global_notify_skipped_step_in + + with _global_notify_skipped_step_in_lock: + if _global_notify_skipped_step_in: + # Check with lock in place (callers should actually have checked + # before without the lock in place due to performance). + return + _global_notify_skipped_step_in = True + py_db.notify_skipped_step_in_because_of_filters(frame) class ThreadInfo: additional_info: PyDBAdditionalThreadInfo - is_pydevd_thread: bool fully_initialized: bool can_create_dummy_thread: bool + thread: threading.Thread + trace: bool def initialize_can_create_dummy_thread(self, code): + if code is None: + self.can_create_dummy_thread = True + return self.additional_info = None - self.is_pydevd_thread = False self.fully_initialized = False self.thread_trace_func = None @@ -73,10 +105,11 @@ def initialize_if_possible(self): # Initialize the dummy thread and set the tracing (both are needed to # actually stop on breakpoints). t = threading.current_thread() + self.thread = t if getattr(t, 'is_pydev_daemon_thread', False): - self.is_pydevd_thread = True self.fully_initialized = True + self.trace = False else: try: additional_info = t.additional_info @@ -91,6 +124,7 @@ def initialize_if_possible(self): additional_info = PyDBAdditionalThreadInfo() t.additional_info = additional_info self.additional_info = additional_info + self.trace = True self.fully_initialized = True @@ -99,6 +133,7 @@ class FuncCodeInfo: co_filename: str co_name: str canonical_normalized_filename: str + abs_path_filename: str always_skip_code: bool breakpoint_found: bool breakpoints_hit_at_lines: Set[int] @@ -108,10 +143,12 @@ class FuncCodeInfo: # to be re-evaluated (if invalid a new FuncCodeInfo must be created and # tracing can't be disabled for the related frames). breakpoints_mtime: int + filtered_out: Optional[bool] def __init__(self): self.co_filename = '' self.canonical_normalized_filename = '' + self.abs_path_filename = '' self.always_skip_code = False self.breakpoint_found = False @@ -119,9 +156,10 @@ def __init__(self): self.breakpoints_hit_at_lines = set() self.function_breakpoint = False + self.filtered_out = None -def get_thread_info(code) -> Optional[ThreadInfo]: +def get_thread_info(code: Optional[CodeType], create: bool) -> Optional[ThreadInfo]: ''' Provides thread-related info. @@ -132,6 +170,8 @@ def get_thread_info(code) -> Optional[ThreadInfo]: # effect in the performance. return _thread_local_info.thread_info except: + if not create: + return None thread_info = ThreadInfo() thread_info.initialize_can_create_dummy_thread(code) if not thread_info.can_create_dummy_thread: @@ -170,11 +210,11 @@ def get_func_code_info(thread_info: ThreadInfo, code_obj, code_to_func_code_info Note that it contains informations on the breakpoints for a given function. If breakpoints change a new FuncCodeInfo instance will be created. ''' - main_debugger = GlobalDebuggerHolder.global_dbg + py_db = GlobalDebuggerHolder.global_dbg func_code_info_obj = code_to_func_code_info.get(code_obj) if func_code_info_obj is not None: - if func_code_info_obj.breakpoints_mtime == main_debugger.mtime: + if func_code_info_obj.breakpoints_mtime == py_db.mtime: # if DEBUG: # print('get_func_code_info: matched mtime', f_code.co_name, f_code.co_filename) return func_code_info_obj @@ -185,7 +225,7 @@ def get_func_code_info(thread_info: ThreadInfo, code_obj, code_to_func_code_info cache_file_type_key: tuple func_code_info = FuncCodeInfo() - func_code_info.breakpoints_mtime = main_debugger.mtime + func_code_info.breakpoints_mtime = py_db.mtime func_code_info.co_filename = co_filename func_code_info.co_name = co_name @@ -196,29 +236,30 @@ def get_func_code_info(thread_info: ThreadInfo, code_obj, code_to_func_code_info except: abs_path_real_path_and_base = get_abs_path_real_path_and_base_from_file(co_filename) + func_code_info.abs_path_filename = abs_path_real_path_and_base[0] func_code_info.canonical_normalized_filename = abs_path_real_path_and_base[1] - cache_file_type = main_debugger.get_cache_file_type() + cache_file_type = py_db.get_cache_file_type() # Note: this cache key must be the same from PyDB.get_file_type() -- see it for comments # on the cache. cache_file_type_key = (code_obj.co_firstlineno, abs_path_real_path_and_base[0], code_obj) try: file_type = cache_file_type[cache_file_type_key] # Make it faster except: - file_type = main_debugger.get_file_type_from_code(code_obj, abs_path_real_path_and_base) # we don't want to debug anything related to pydevd + file_type = py_db.get_file_type_from_code(code_obj, abs_path_real_path_and_base) # we don't want to debug anything related to pydevd if file_type is not None: func_code_info.always_skip_code = True if not func_code_info.always_skip_code: - if main_debugger is not None: + if py_db is not None: - breakpoints: dict = main_debugger.breakpoints.get(func_code_info.canonical_normalized_filename) - function_breakpoint: object = main_debugger.function_breakpoint_name_to_breakpoint.get(func_code_info.co_name) + breakpoints: dict = py_db.breakpoints.get(func_code_info.canonical_normalized_filename) + function_breakpoint: object = py_db.function_breakpoint_name_to_breakpoint.get(func_code_info.co_name) # print('\n---') - # print(main_debugger.breakpoints) + # print(py_db.breakpoints) # print(func_code_info.canonical_normalized_filename) - # print(main_debugger.breakpoints.get(func_code_info.canonical_normalized_filename)) + # print(py_db.breakpoints.get(func_code_info.canonical_normalized_filename)) if function_breakpoint: # Go directly into tracing mode func_code_info.breakpoint_found = True @@ -244,86 +285,174 @@ def get_func_code_info(thread_info: ThreadInfo, code_obj, code_to_func_code_info return func_code_info -def _start_method(code, instruction_offset): +def enable_line_tracing(code): + print('enable line tracing', code) + events = monitor.get_local_events(DEBUGGER_ID, code) + monitor.set_local_events(DEBUGGER_ID, code, events | monitor.events.LINE) + + +def _line_event(code, line): + print('line event', code, line) + + # A bunch of things have to be repeated especially because in the sys.monitoring + # everything is global, yet, when we start tracing something for stepping that + # needs to be per-thread. try: thread_info = _thread_local_info.thread_info except: - thread_info = get_thread_info(code) + thread_info = get_thread_info(code, True) + if thread_info is None: + return - additional_info = thread_info.additional_info - if thread_info.is_pydevd_thread: - func_code_info: FuncCodeInfo = get_func_code_info(thread_info, code) - if func_code_info.always_skip_code: - return monitor.DISABLE - return # We can't disable it here as another thread could use it too. + py_db: object = GlobalDebuggerHolder.global_dbg + if py_db is None or py_db.pydb_disposed: + return monitor.DISABLE + + if not thread_info.trace or thread_info.thread._is_stopped: + # For thread-related stuff we can't disable the code tracing because other + # threads may still want it... + return - STATE_SUSPEND: int = 2 - CMD_STEP_INTO: int = 107 - CMD_STEP_OVER: int = 108 - CMD_STEP_OVER_MY_CODE: int = 159 - CMD_STEP_INTO_MY_CODE: int = 144 - CMD_STEP_INTO_COROUTINE: int = 206 - CMD_SMART_STEP_INTO: int = 128 - can_skip: bool = True + func_code_info: FuncCodeInfo = get_func_code_info(thread_info, code) + if func_code_info.always_skip_code: + return monitor.DISABLE + + additional_info = thread_info.additional_info # We know the frame depth. frame = sys._getframe(1) - main_debugger: object = GlobalDebuggerHolder.global_dbg - if main_debugger is None: + pydev_step_cmd = additional_info.pydev_step_cmd + is_stepping = pydev_step_cmd != -1 + + if py_db.is_files_filter_enabled: + if func_code_info.filtered_out is None: + if py_db.apply_files_filter(frame, func_code_info.abs_path_filename, False): + if is_stepping and additional_info.pydev_original_step_cmd in (CMD_STEP_INTO, CMD_STEP_INTO_MY_CODE) and not _global_notify_skipped_step_in: + notify_skipped_step_in_because_of_filters(py_db, frame) + + # A little gotcha, sometimes when we're stepping in we have to stop in a + # return event showing the back frame as the current frame, so, we need + # to check not only the current frame but the back frame too. + back_frame = frame.f_back + if back_frame is not None and pydev_step_cmd in (CMD_STEP_INTO, CMD_STEP_INTO_MY_CODE, CMD_STEP_RETURN, CMD_STEP_RETURN_MY_CODE): + if py_db.apply_files_filter(back_frame, back_frame.f_code.co_filename, False): + func_code_info.filtered_out = True + return monitor.DISABLE + else: + # if DEBUG: print('skipped: trace_dispatch (filtered out: 2)', frame_cache_key, frame.f_lineno, event, frame.f_code.co_name) + func_code_info.filtered_out = True + + if func_code_info.filtered_out: + return monitor.DISABLE + else: + func_code_info.filtered_out = False + + # If we reached here, it was not filtered out. + if line in func_code_info.breakpoints_hit_at_lines: + print('suspend...') + + +def _start_method(code, instruction_offset): + try: + thread_info = _thread_local_info.thread_info + except: + thread_info = get_thread_info(code, True) + if thread_info is None: + return + + py_db: object = GlobalDebuggerHolder.global_dbg + if py_db is None or py_db.pydb_disposed: + return monitor.DISABLE + + if not thread_info.trace or thread_info.thread._is_stopped: + # For thread-related stuff we can't disable the code tracing because other + # threads may still want it... + return + + func_code_info: FuncCodeInfo = get_func_code_info(thread_info, code) + if func_code_info.always_skip_code: return monitor.DISABLE + additional_info = thread_info.additional_info + # We know the frame depth. + frame = sys._getframe(1) + if additional_info.pydev_step_cmd in (CMD_STEP_INTO, CMD_STEP_INTO_MY_CODE, CMD_STEP_INTO_COROUTINE, CMD_SMART_STEP_INTO): # Stepping (must have line tracing enabled). enable_line_tracing(code) - if additional_info.pydev_step_cmd in (CMD_STEP_OVER, CMD_STEP_OVER_MY_CODE) and main_debugger.show_return_values and frame.f_back is additional_info.pydev_step_stop: + if additional_info.pydev_step_cmd in (CMD_STEP_OVER, CMD_STEP_OVER_MY_CODE) and py_db.show_return_values and frame.f_back is additional_info.pydev_step_stop: # Show return values on step over. enable_return_tracing(code) - if main_debugger.break_on_caught_exceptions or main_debugger.break_on_user_uncaught_exceptions or main_debugger.has_plugin_exception_breaks: + if py_db.break_on_caught_exceptions or py_db.break_on_user_uncaught_exceptions or py_db.has_plugin_exception_breaks: enable_exception_tracing(code) - if main_debugger.signature_factory: + if py_db.signature_factory: pass - func_code_info: FuncCodeInfo = get_func_code_info(thread_info, frame, code) + func_code_info: FuncCodeInfo = get_func_code_info(thread_info, code) # if DEBUG: # print('get_bytecode_while_frame_eval always skip', func_code_info.always_skip_code) if not func_code_info.always_skip_code: - if main_debugger.has_plugin_line_breaks or main_debugger.has_plugin_exception_breaks: - can_skip = main_debugger.plugin.can_skip(main_debugger, frame) + if py_db.has_plugin_line_breaks or py_db.has_plugin_exception_breaks: + can_skip = py_db.plugin.can_skip(py_db, frame) if not can_skip: - if main_debugger.has_plugin_line_breaks: + if py_db.has_plugin_line_breaks: enable_line_tracing(code) - if main_debugger.has_plugin_exception_breaks: + if py_db.has_plugin_exception_breaks: enable_exception_tracing(code) if func_code_info.breakpoint_found: enable_line_tracing(code) -def start_monitoring(): - DEBUGGER_ID = sys.monitoring.DEBUGGER_ID - if not sys.monitoring.get_tool(DEBUGGER_ID): - sys.monitoring.use_tool_id(DEBUGGER_ID, 'pydevd') - sys.monitoring.set_events(DEBUGGER_ID, sys.monitoring.events.PY_START | sys.monitoring.events.PY_RESUME) - - sys.monitoring.register_callback(DEBUGGER_ID, sys.monitoring.events.PY_START, _start_method) - sys.monitoring.register_callback(DEBUGGER_ID, sys.monitoring.events.PY_RESUME, _start_method) - - # sys.monitoring.register_callback(DEBUGGER_ID, sys.monitoring.events.LINE, self._line_callback) - # - # sys.monitoring.register_callback(DEBUGGER_ID, sys.monitoring.events.PY_RETURN, self._return_callback) - # - # sys.monitoring.register_callback(DEBUGGER_ID, sys.monitoring.events.RAISE, self._raise_callback) - - # Activate exception raise callback if exception breakpoints are registered. - current_events = sys.monitoring.get_events(DEBUGGER_ID) - sys.monitoring.set_events(DEBUGGER_ID, current_events | sys.monitoring.events.RAISE) - - -def stop_monitoring(): - sys.monitoring.set_events(sys.monitoring.DEBUGGER_ID, 0) +def start_monitoring(all_threads=False): + print('start monitoring, all_threads=', all_threads) + if all_threads: + DEBUGGER_ID = monitor.DEBUGGER_ID + if not monitor.get_tool(DEBUGGER_ID): + monitor.use_tool_id(DEBUGGER_ID, 'pydevd') + 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, _line_event) + + # monitor.register_callback(DEBUGGER_ID, monitor.events.LINE, self._line_callback) + # + # monitor.register_callback(DEBUGGER_ID, monitor.events.PY_RETURN, self._return_callback) + # + # monitor.register_callback(DEBUGGER_ID, monitor.events.RAISE, self._raise_callback) + + # Activate exception raise callback if exception breakpoints are registered. + current_events = monitor.get_events(DEBUGGER_ID) + monitor.set_events(DEBUGGER_ID, current_events | monitor.events.RAISE) + else: + try: + thread_info = _thread_local_info.thread_info + except: + # code=None means we can already get the threading.current_thread. + thread_info = get_thread_info(code=None, create=True) + if thread_info is None: + return + thread_info.trace = True + + +def stop_monitoring(all_threads=False): + print('stop monitoring, all_threads=', all_threads) + if all_threads: + if monitor.get_tool(monitor.DEBUGGER_ID) == 'pydevd': + monitor.free_tool_id(monitor.DEBUGGER_ID) + else: + try: + thread_info = _thread_local_info.thread_info + except: + # code=None means we can already get the threading.current_thread. + thread_info = get_thread_info(code=None, create=False) + if thread_info is None: + return + thread_info.trace = False diff --git a/plugins/org.python.pydev.core/pysrc/_pydevd_sys_monitoring/pydevd_sys_monitoring_manager.py b/plugins/org.python.pydev.core/pysrc/_pydevd_sys_monitoring/pydevd_sys_monitoring_manager.py new file mode 100644 index 0000000000..2b0819428c --- /dev/null +++ b/plugins/org.python.pydev.core/pysrc/_pydevd_sys_monitoring/pydevd_sys_monitoring_manager.py @@ -0,0 +1,25 @@ +import sys +DEBUGGER_ID = sys.monitoring.DEBUGGER_ID +monitor = sys.monitoring + + +class SysMonitoringManager: + + DISABLE = monitor.DISABLE + + def register_debugger(self): + monitor.use_tool_id(DEBUGGER_ID, 'pydevd') + + def unregister_debugger(self): + if monitor.get_tool(DEBUGGER_ID) == 'pydevd': + monitor.free_tool_id(DEBUGGER_ID) + + def trace_start_events(self, callback): + monitor.set_events(DEBUGGER_ID, monitor.events.PY_START | monitor.events.PY_RESUME) + + # Only one callback is registered at a time and to unregister 'None' can be passed. + monitor.register_callback(DEBUGGER_ID, monitor.events.PY_START, callback) + monitor.register_callback(DEBUGGER_ID, monitor.events.PY_RESUME, callback) + + def trace_line_events(self, code): + monitor.set_local_events(DEBUGGER_ID, code, monitor.events.LINE) diff --git a/plugins/org.python.pydev.core/pysrc/pydevd.py b/plugins/org.python.pydev.core/pysrc/pydevd.py index 336dd51e07..32c225c96d 100644 --- a/plugins/org.python.pydev.core/pysrc/pydevd.py +++ b/plugins/org.python.pydev.core/pysrc/pydevd.py @@ -4,6 +4,7 @@ This module starts the debugger. ''' import sys # @NoMove +from _pydevd_sys_monitoring import pydevd_sys_monitoring if sys.version_info[:2] < (3, 6): raise RuntimeError('The PyDev.Debugger requires Python 3.6 onwards to be run. If you need to use an older Python version, use an older version of the debugger.') import os @@ -56,7 +57,8 @@ clear_cached_thread_id, INTERACTIVE_MODE_AVAILABLE, SHOW_DEBUG_INFO_ENV, NULL, NO_FTRACE, IS_IRONPYTHON, JSON_PROTOCOL, IS_CPYTHON, HTTP_JSON_PROTOCOL, USE_CUSTOM_SYS_CURRENT_FRAMES_MAP, call_only_once, ForkSafeLock, IGNORE_BASENAMES_STARTING_WITH, EXCEPTION_TYPE_UNHANDLED, SUPPORT_GEVENT, - PYDEVD_IPYTHON_COMPATIBLE_DEBUGGING, PYDEVD_IPYTHON_CONTEXT) + PYDEVD_IPYTHON_COMPATIBLE_DEBUGGING, PYDEVD_IPYTHON_CONTEXT, + USE_SYS_MONITORING) from _pydevd_bundle.pydevd_defaults import PydevdCustomization # Note: import alias used on pydev_monkey. from _pydevd_bundle.pydevd_custom_frames import CustomFramesContainer, custom_frames_container_init from _pydevd_bundle.pydevd_dont_trace_files import DONT_TRACE, PYDEV_FILE, LIB_FILE, DONT_TRACE_DIRS @@ -1119,6 +1121,10 @@ def enable_tracing(self, thread_trace_func=None, apply_to_all_threads=False): this function is called on a multi-threaded program (either programmatically or attach to pid). ''' + if USE_SYS_MONITORING: + pydevd_sys_monitoring.start_monitoring(all_threads=apply_to_all_threads) + return + if pydevd_gevent_integration is not None: pydevd_gevent_integration.enable_gevent_integration() @@ -1144,7 +1150,10 @@ def enable_tracing(self, thread_trace_func=None, apply_to_all_threads=False): pydevd_tracing.set_trace_to_threads(thread_trace_func) def disable_tracing(self): - pydevd_tracing.SetTrace(None) + if USE_SYS_MONITORING: + pydevd_sys_monitoring.stop_monitoring(all_threads=False) + else: + pydevd_tracing.SetTrace(None) def on_breakpoints_changed(self, removed=False): ''' @@ -2417,6 +2426,9 @@ def prepare_to_run(self): self.start_auxiliary_daemon_threads() def patch_threads(self): + if USE_SYS_MONITORING: + pydevd_sys_monitoring.start_monitoring(all_threads=True) + return try: # not available in jython! threading.settrace(self.trace_dispatch) # for all future threads @@ -3024,12 +3036,15 @@ def _locked_settrace( def stoptrace(): pydev_log.debug("pydevd.stoptrace()") pydevd_tracing.restore_sys_set_trace_func() - sys.settrace(None) - try: - # not available in jython! - threading.settrace(None) # for all future threads - except: - pass + if USE_SYS_MONITORING: + pydevd_sys_monitoring.stop_monitoring(all_threads=True) + else: + sys.settrace(None) + try: + # not available in jython! + threading.settrace(None) # for all future threads + except: + pass from _pydev_bundle.pydev_monkey import undo_patch_thread_modules undo_patch_thread_modules() diff --git a/plugins/org.python.pydev.core/pysrc/pydevd_tracing.py b/plugins/org.python.pydev.core/pysrc/pydevd_tracing.py index d658b12483..7f9c14f76d 100644 --- a/plugins/org.python.pydev.core/pysrc/pydevd_tracing.py +++ b/plugins/org.python.pydev.core/pysrc/pydevd_tracing.py @@ -1,6 +1,6 @@ from _pydevd_bundle.pydevd_constants import get_frame, IS_CPYTHON, IS_64BIT_PROCESS, IS_WINDOWS, \ IS_LINUX, IS_MAC, DebugInfoHolder, LOAD_NATIVE_LIB_FLAG, \ - ENV_FALSE_LOWER_VALUES, ForkSafeLock + ENV_FALSE_LOWER_VALUES, ForkSafeLock, USE_SYS_MONITORING from _pydev_bundle._pydev_saved_modules import thread, threading from _pydev_bundle import pydev_log, pydev_monkey import os.path @@ -45,6 +45,8 @@ def _get_stack_str(frame): def _internal_set_trace(tracing_func): + if USE_SYS_MONITORING: + raise RuntimeError("pydevd: Using sys.monitoring, sys.settrace should not be called.") if TracingFunctionHolder._warn: frame = get_frame() if frame is not None and frame.f_back is not None: @@ -81,6 +83,8 @@ def _internal_set_trace(tracing_func): def SetTrace(tracing_func): + if USE_SYS_MONITORING: + raise RuntimeError('SetTrace should not be used when using sys.monitoring.') _last_tracing_func_thread_local.tracing_func = tracing_func if tracing_func is not None: @@ -108,12 +112,16 @@ def reapply_settrace(): def replace_sys_set_trace_func(): + if USE_SYS_MONITORING: + return if TracingFunctionHolder._original_tracing is None: TracingFunctionHolder._original_tracing = sys.settrace sys.settrace = _internal_set_trace def restore_sys_set_trace_func(): + if USE_SYS_MONITORING: + return if TracingFunctionHolder._original_tracing is not None: sys.settrace = TracingFunctionHolder._original_tracing TracingFunctionHolder._original_tracing = None @@ -272,6 +280,8 @@ def _load_python_helper_lib_uncached(): def set_trace_to_threads(tracing_func, thread_idents=None, create_dummy_thread=True): + if USE_SYS_MONITORING: + raise RuntimeError('Should not be called when using sys.monitoring.') assert tracing_func is not None ret = 0 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 13db7d3bc2..25e94c734a 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 @@ -3,9 +3,11 @@ DEBUGGER_ID = sys.monitoring.DEBUGGER_ID monitor = sys.monitoring -if __name__ == '__main__': + +def test_change_line_during_trace(): code_to_break_at_line = {} do_change_line = [0] + lines_traced = [] def _start_method(code, offset): monitor.set_local_events(DEBUGGER_ID, code, monitor.events.LINE) @@ -17,25 +19,29 @@ def _on_line(code, line): if lines_to_break and line in lines_to_break: do_change_line[0] += 1 if do_change_line[0] == 2: - frame = sys._getframe().f_back + frame = sys._getframe(1) print(frame.f_lineno) frame.f_lineno = line - 2 monitor.use_tool_id(DEBUGGER_ID, 'pydevd') - 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 method1(): # code.co_firstlineno - a = 1 # code.co_firstlineno + 1 - print('before a=2') # code.co_firstlineno + 2 - a = 2 # code.co_firstlineno + 3 - print('before a=3') # code.co_firstlineno + 4 - # a = 3 # code.co_firstlineno + 5 - - for _i in range(3): - method1() - - sys.monitoring.set_events(sys.monitoring.DEBUGGER_ID, 0) + try: + 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 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() + + assert lines_traced == [] + sys.monitoring.set_events(sys.monitoring.DEBUGGER_ID, 0) + finally: + sys.monitoring.free_tool_id(DEBUGGER_ID) diff --git a/plugins/org.python.pydev/src/org/python/pydev/ui/pythonpathconf/package_manager/CondaPackageManager.java b/plugins/org.python.pydev/src/org/python/pydev/ui/pythonpathconf/package_manager/CondaPackageManager.java index b65f33a93b..63f04e5a7c 100644 --- a/plugins/org.python.pydev/src/org/python/pydev/ui/pythonpathconf/package_manager/CondaPackageManager.java +++ b/plugins/org.python.pydev/src/org/python/pydev/ui/pythonpathconf/package_manager/CondaPackageManager.java @@ -40,6 +40,10 @@ public static List listCondaEnvironments(File condaExecutable) { null, encoding); Log.logInfo(output.o1); + if (output.o2 != null && output.o2.length() > 0) { + Log.logInfo("STDERR when listing conda environments:\n" + output.o2); + + } JsonObject jsonOutput = JsonValue.readFrom(output.o1).asObject(); JsonArray envs = jsonOutput.get("envs").asArray(); Set set = new HashSet<>();