diff --git a/src/debugpy/_vendored/pydevd/_pydev_bundle/pydev_log.py b/src/debugpy/_vendored/pydevd/_pydev_bundle/pydev_log.py index 981b54bd..71551956 100644 --- a/src/debugpy/_vendored/pydevd/_pydev_bundle/pydev_log.py +++ b/src/debugpy/_vendored/pydevd/_pydev_bundle/pydev_log.py @@ -1,4 +1,5 @@ -from _pydevd_bundle.pydevd_constants import DebugInfoHolder, SHOW_COMPILE_CYTHON_COMMAND_LINE, NULL, LOG_TIME +from _pydevd_bundle.pydevd_constants import DebugInfoHolder, SHOW_COMPILE_CYTHON_COMMAND_LINE, NULL, LOG_TIME, \ + ForkSafeLock from contextlib import contextmanager import traceback import os @@ -6,18 +7,19 @@ import sys class _LoggingGlobals(object): - _warn_once_map = {} _debug_stream_filename = None - _debug_stream = sys.stderr + _debug_stream = NULL _debug_stream_initialized = False + _initialize_lock = ForkSafeLock() def initialize_debug_stream(reinitialize=False): ''' :param bool reinitialize: Reinitialize is used to update the debug stream after a fork (thus, if it wasn't - initialized, we don't need to do anything). + initialized, we don't need to do anything, just wait for the first regular log call + to initialize). ''' if reinitialize: if not _LoggingGlobals._debug_stream_initialized: @@ -26,32 +28,69 @@ def initialize_debug_stream(reinitialize=False): if _LoggingGlobals._debug_stream_initialized: return - _LoggingGlobals._debug_stream_initialized = True + with _LoggingGlobals._initialize_lock: + # Initialization is done lazilly, so, it's possible that multiple threads try to initialize + # logging. - # Note: we cannot initialize with sys.stderr because when forking we may end up logging things in 'os' calls. - _LoggingGlobals._debug_stream = NULL - _LoggingGlobals._debug_stream_filename = None + # Check initial conditions again after obtaining the lock. + if reinitialize: + if not _LoggingGlobals._debug_stream_initialized: + return + else: + if _LoggingGlobals._debug_stream_initialized: + return - if not DebugInfoHolder.PYDEVD_DEBUG_FILE: - _LoggingGlobals._debug_stream = sys.stderr - else: - # Add pid to the filename. - try: - dirname = os.path.dirname(DebugInfoHolder.PYDEVD_DEBUG_FILE) - basename = os.path.basename(DebugInfoHolder.PYDEVD_DEBUG_FILE) - try: - os.makedirs(dirname) - except: - pass # Ignore error if it already exists. + _LoggingGlobals._debug_stream_initialized = True - name, ext = os.path.splitext(basename) - debug_file = os.path.join(dirname, name + '.' + str(os.getpid()) + ext) - _LoggingGlobals._debug_stream = open(debug_file, 'w') - _LoggingGlobals._debug_stream_filename = debug_file - except: + # Note: we cannot initialize with sys.stderr because when forking we may end up logging things in 'os' calls. + _LoggingGlobals._debug_stream = NULL + _LoggingGlobals._debug_stream_filename = None + + if not DebugInfoHolder.PYDEVD_DEBUG_FILE: _LoggingGlobals._debug_stream = sys.stderr - # Don't fail when trying to setup logging, just show the exception. - traceback.print_exc() + else: + # Add pid to the filename. + try: + target_file = DebugInfoHolder.PYDEVD_DEBUG_FILE + debug_file = _compute_filename_with_pid(target_file) + _LoggingGlobals._debug_stream = open(debug_file, 'w') + _LoggingGlobals._debug_stream_filename = debug_file + except Exception: + _LoggingGlobals._debug_stream = sys.stderr + # Don't fail when trying to setup logging, just show the exception. + traceback.print_exc() + + +def _compute_filename_with_pid(target_file, pid=None): + # Note: used in tests. + dirname = os.path.dirname(target_file) + basename = os.path.basename(target_file) + try: + os.makedirs(dirname) + except Exception: + pass # Ignore error if it already exists. + + name, ext = os.path.splitext(basename) + if pid is None: + pid = os.getpid() + return os.path.join(dirname, '%s.%s%s' % (name, pid, ext)) + + +def log_to(log_file:str, log_level:int=3) -> None: + with _LoggingGlobals._initialize_lock: + # Can be set directly. + DebugInfoHolder.DEBUG_TRACE_LEVEL = log_level + + if DebugInfoHolder.PYDEVD_DEBUG_FILE != log_file: + # Note that we don't need to reset it unless it actually changed + # (would be the case where it's set as an env var in a new process + # and a subprocess initializes logging to the same value). + _LoggingGlobals._debug_stream = NULL + _LoggingGlobals._debug_stream_filename = None + + DebugInfoHolder.PYDEVD_DEBUG_FILE = log_file + + _LoggingGlobals._debug_stream_initialized = False def list_log_files(pydevd_debug_file): @@ -71,28 +110,33 @@ def log_context(trace_level, stream): ''' To be used to temporarily change the logging settings. ''' - original_trace_level = DebugInfoHolder.DEBUG_TRACE_LEVEL - original_debug_stream = _LoggingGlobals._debug_stream - original_pydevd_debug_file = DebugInfoHolder.PYDEVD_DEBUG_FILE - original_debug_stream_filename = _LoggingGlobals._debug_stream_filename - original_initialized = _LoggingGlobals._debug_stream_initialized + with _LoggingGlobals._initialize_lock: + original_trace_level = DebugInfoHolder.DEBUG_TRACE_LEVEL + original_debug_stream = _LoggingGlobals._debug_stream + original_pydevd_debug_file = DebugInfoHolder.PYDEVD_DEBUG_FILE + original_debug_stream_filename = _LoggingGlobals._debug_stream_filename + original_initialized = _LoggingGlobals._debug_stream_initialized - DebugInfoHolder.DEBUG_TRACE_LEVEL = trace_level - _LoggingGlobals._debug_stream = stream - _LoggingGlobals._debug_stream_initialized = True + DebugInfoHolder.DEBUG_TRACE_LEVEL = trace_level + _LoggingGlobals._debug_stream = stream + _LoggingGlobals._debug_stream_initialized = True try: yield finally: - DebugInfoHolder.DEBUG_TRACE_LEVEL = original_trace_level - _LoggingGlobals._debug_stream = original_debug_stream - DebugInfoHolder.PYDEVD_DEBUG_FILE = original_pydevd_debug_file - _LoggingGlobals._debug_stream_filename = original_debug_stream_filename - _LoggingGlobals._debug_stream_initialized = original_initialized + with _LoggingGlobals._initialize_lock: + DebugInfoHolder.DEBUG_TRACE_LEVEL = original_trace_level + _LoggingGlobals._debug_stream = original_debug_stream + DebugInfoHolder.PYDEVD_DEBUG_FILE = original_pydevd_debug_file + _LoggingGlobals._debug_stream_filename = original_debug_stream_filename + _LoggingGlobals._debug_stream_initialized = original_initialized import time _last_log_time = time.time() +# Set to True to show pid in each logged message (usually the file has it, but sometimes it's handy). +_LOG_PID = False + def _pydevd_log(level, msg, *args): ''' @@ -120,6 +164,10 @@ def _pydevd_log(level, msg, *args): msg = '%.2fs - %s\n' % (time_diff, msg,) else: msg = '%s\n' % (msg,) + + if _LOG_PID: + msg = '<%s> - %s\n' % (os.getpid(), msg,) + try: try: initialize_debug_stream() # Do it as late as possible diff --git a/src/debugpy/_vendored/pydevd/_pydev_bundle/pydev_monkey.py b/src/debugpy/_vendored/pydevd/_pydev_bundle/pydev_monkey.py index 306a450f..9f6c15fe 100644 --- a/src/debugpy/_vendored/pydevd/_pydev_bundle/pydev_monkey.py +++ b/src/debugpy/_vendored/pydevd/_pydev_bundle/pydev_monkey.py @@ -4,7 +4,7 @@ import re import sys from _pydev_bundle._pydev_saved_modules import threading from _pydevd_bundle.pydevd_constants import get_global_debugger, IS_WINDOWS, IS_JYTHON, get_current_thread_id, \ - sorted_dict_repr, set_global_debugger + sorted_dict_repr, set_global_debugger, DebugInfoHolder from _pydev_bundle import pydev_log from contextlib import contextmanager from _pydevd_bundle import pydevd_constants @@ -68,6 +68,13 @@ def _get_setup_updated_with_protocol_and_ppid(setup, is_exec=False): else: pydev_log.debug('Unexpected protocol: %s', protocol) + + if DebugInfoHolder.PYDEVD_DEBUG_FILE: + setup['log-file'] = DebugInfoHolder.PYDEVD_DEBUG_FILE + + if DebugInfoHolder.DEBUG_TRACE_LEVEL: + setup['log-level'] = DebugInfoHolder.DEBUG_TRACE_LEVEL + return setup diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_api.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_api.py index 28a28d6d..4015152b 100644 --- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_api.py +++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_api.py @@ -551,8 +551,7 @@ class PyDevdAPI(object): if not supported_type: raise NameError(breakpoint_type) - if DebugInfoHolder.DEBUG_TRACE_BREAKPOINTS > 0: - pydev_log.debug('Added breakpoint:%s - line:%s - func_name:%s\n', canonical_normalized_filename, line, func_name) + pydev_log.debug('Added breakpoint:%s - line:%s - func_name:%s\n', canonical_normalized_filename, line, func_name) if canonical_normalized_filename in file_to_id_to_breakpoint: id_to_pybreakpoint = file_to_id_to_breakpoint[canonical_normalized_filename] @@ -672,10 +671,10 @@ class PyDevdAPI(object): else: try: id_to_pybreakpoint = file_to_id_to_breakpoint.get(canonical_normalized_filename, {}) - if DebugInfoHolder.DEBUG_TRACE_BREAKPOINTS > 0: + if DebugInfoHolder.DEBUG_TRACE_LEVEL >= 1: existing = id_to_pybreakpoint[breakpoint_id] pydev_log.info('Removed breakpoint:%s - line:%s - func_name:%s (id: %s)\n' % ( - canonical_normalized_filename, existing.line, existing.func_name.encode('utf-8'), breakpoint_id)) + canonical_normalized_filename, existing.line, existing.func_name, breakpoint_id)) del id_to_pybreakpoint[breakpoint_id] py_db.consolidate_breakpoints(canonical_normalized_filename, id_to_pybreakpoint, file_to_line_to_breakpoints) diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py index 930adda8..163fd7ab 100644 --- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py +++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py @@ -274,13 +274,14 @@ class ReaderThread(PyDBDaemonThread): if hasattr(line, 'decode'): line = line.decode('utf-8') - if DebugInfoHolder.DEBUG_RECORD_SOCKET_READS: - pydev_log.critical(u'debugger: received >>%s<<\n' % (line,)) + if DebugInfoHolder.DEBUG_TRACE_LEVEL >= 3: + pydev_log.debug('debugger: received >>%s<<\n', line) - args = line.split(u'\t', 2) + args = line.split('\t', 2) try: cmd_id = int(args[0]) - pydev_log.debug('Received command: %s %s\n' % (ID_TO_MEANING.get(str(cmd_id), '???'), line,)) + if DebugInfoHolder.DEBUG_TRACE_LEVEL >= 3: + pydev_log.debug('Received command: %s %s\n', ID_TO_MEANING.get(str(cmd_id), '???'), line) self.process_command(cmd_id, int(args[1]), args[2]) except: if sys is not None and pydev_log_exception is not None: # Could happen at interpreter shutdown @@ -1351,12 +1352,12 @@ def internal_get_completions(dbg, seq, thread_id, frame_id, act_tok, line=-1, co try: remove_path = None try: - qualifier = u'' + qualifier = '' if column >= 0: token_and_qualifier = extract_token_and_qualifier(act_tok, line, column) act_tok = token_and_qualifier[0] if act_tok: - act_tok += u'.' + act_tok += '.' qualifier = token_and_qualifier[1] frame = dbg.find_frame(thread_id, frame_id) diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_command_line_handling.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_command_line_handling.py index 2fe9a614..ea7f745f 100644 --- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_command_line_handling.py +++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_command_line_handling.py @@ -1,4 +1,5 @@ import os +import sys class ArgHandlerWithParam: @@ -68,8 +69,11 @@ ACCEPTED_ARG_HANDLERS = [ ArgHandlerWithParam('access-token'), ArgHandlerWithParam('client-access-token'), + # Logging + ArgHandlerWithParam('log-file'), + ArgHandlerWithParam('log-level', int, 0), + ArgHandlerBool('server'), - ArgHandlerBool('DEBUG_RECORD_SOCKET_READS'), ArgHandlerBool('multiproc'), # Used by PyCharm (reuses connection: ssh tunneling) ArgHandlerBool('multiprocess'), # Used by PyDev (creates new connection to ide) ArgHandlerBool('save-signatures'), @@ -132,6 +136,8 @@ def process_command_line(argv): setup['file'] = '' setup['qt-support'] = '' + initial_argv = tuple(argv) + i = 0 del argv[0] while i < len(argv): @@ -169,10 +175,9 @@ def process_command_line(argv): i = len(argv) # pop out, file is our last argument elif argv[i] == '--DEBUG': - from pydevd import set_debug - del argv[i] - set_debug(setup) + sys.stderr.write('pydevd: --DEBUG parameter deprecated. Use `--debug-level=3` instead.\n') else: - raise ValueError("Unexpected option: " + argv[i]) + raise ValueError("Unexpected option: %s when processing: %s" % (argv[i], initial_argv)) return setup + diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_constants.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_constants.py index 1b36524e..013b6cf8 100644 --- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_constants.py +++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_constants.py @@ -39,10 +39,6 @@ class DebugInfoHolder: # General information DEBUG_TRACE_LEVEL = 0 # 0 = critical, 1 = info, 2 = debug, 3 = verbose - # Flags to debug specific points of the code. - DEBUG_RECORD_SOCKET_READS = False - DEBUG_TRACE_BREAKPOINTS = -1 - PYDEVD_DEBUG_FILE = None @@ -292,7 +288,6 @@ DEFAULT_VALUE = "__pydevd_value_async" ASYNC_EVAL_TIMEOUT_SEC = 60 NEXT_VALUE_SEPARATOR = "__pydev_val__" BUILTINS_MODULE_NAME = 'builtins' -SHOW_DEBUG_INFO_ENV = is_true_in_env(('PYCHARM_DEBUG', 'PYDEV_DEBUG', 'PYDEVD_DEBUG')) # Pandas customization. PANDAS_MAX_ROWS = as_int_in_env('PYDEVD_PANDAS_MAX_ROWS', 60) @@ -335,11 +330,11 @@ EXCEPTION_TYPE_UNHANDLED = 'UNHANDLED' EXCEPTION_TYPE_USER_UNHANDLED = 'USER_UNHANDLED' EXCEPTION_TYPE_HANDLED = 'HANDLED' +SHOW_DEBUG_INFO_ENV = is_true_in_env(('PYCHARM_DEBUG', 'PYDEV_DEBUG', 'PYDEVD_DEBUG')) + if SHOW_DEBUG_INFO_ENV: # show debug info before the debugger start - DebugInfoHolder.DEBUG_RECORD_SOCKET_READS = True DebugInfoHolder.DEBUG_TRACE_LEVEL = 3 - DebugInfoHolder.DEBUG_TRACE_BREAKPOINTS = 1 DebugInfoHolder.PYDEVD_DEBUG_FILE = os.getenv('PYDEVD_DEBUG_FILE') diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py index ff01ecd3..c305c6da 100644 --- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py +++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py @@ -163,7 +163,7 @@ class PyDevJsonCommandProcessor(object): return NetCommand(CMD_RETURN, 0, error_response, is_json=True) else: - if DebugInfoHolder.DEBUG_RECORD_SOCKET_READS and DebugInfoHolder.DEBUG_TRACE_LEVEL >= 1: + if DebugInfoHolder.DEBUG_TRACE_LEVEL >= 1: pydev_log.info('Process %s: %s\n' % ( request.__class__.__name__, json.dumps(request.to_dict(update_ids_to_dap=True), indent=4, sort_keys=True),)) diff --git a/src/debugpy/_vendored/pydevd/pydevd.py b/src/debugpy/_vendored/pydevd/pydevd.py index 67382dee..64520310 100644 --- a/src/debugpy/_vendored/pydevd/pydevd.py +++ b/src/debugpy/_vendored/pydevd/pydevd.py @@ -64,11 +64,11 @@ from _pydevd_bundle.pydevd_extension_api import DebuggerEventHandler from _pydevd_bundle.pydevd_frame_utils import add_exception_to_frame, remove_exception_from_frame from _pydevd_bundle.pydevd_net_command_factory_xml import NetCommandFactory from _pydevd_bundle.pydevd_trace_dispatch import ( - trace_dispatch as _trace_dispatch, global_cache_skips, global_cache_frame_skips, fix_top_level_trace_and_get_trace_func) + trace_dispatch as _trace_dispatch, global_cache_skips, global_cache_frame_skips, fix_top_level_trace_and_get_trace_func, USING_CYTHON) from _pydevd_bundle.pydevd_utils import save_main_module, is_current_thread_main_thread, \ import_attr_from_module from _pydevd_frame_eval.pydevd_frame_eval_main import ( - frame_eval_func, dummy_trace_dispatch) + frame_eval_func, dummy_trace_dispatch, USING_FRAME_EVAL) import pydev_ipython # @UnusedImport from _pydevd_bundle.pydevd_source_mapping import SourceMapping from _pydevd_bundle.pydevd_concurrency_analyser.pydevd_concurrency_logger import ThreadingLogger, AsyncioLogger, send_concurrency_message, cur_time @@ -1814,22 +1814,19 @@ class PyDB(object): if eb.notify_on_unhandled_exceptions: cp = self.break_on_uncaught_exceptions.copy() cp[exception] = eb - if DebugInfoHolder.DEBUG_TRACE_BREAKPOINTS > 0: - pydev_log.critical("Exceptions to hook on terminate: %s.", cp) + pydev_log.info("Exceptions to hook on terminate: %s.", cp) self.break_on_uncaught_exceptions = cp if eb.notify_on_handled_exceptions: cp = self.break_on_caught_exceptions.copy() cp[exception] = eb - if DebugInfoHolder.DEBUG_TRACE_BREAKPOINTS > 0: - pydev_log.critical("Exceptions to hook always: %s.", cp) + pydev_log.info("Exceptions to hook always: %s.", cp) self.break_on_caught_exceptions = cp if eb.notify_on_user_unhandled_exceptions: cp = self.break_on_user_uncaught_exceptions.copy() cp[exception] = eb - if DebugInfoHolder.DEBUG_TRACE_BREAKPOINTS > 0: - pydev_log.critical("Exceptions to hook on user uncaught code: %s.", cp) + pydev_log.info("Exceptions to hook on user uncaught code: %s.", cp) self.break_on_user_uncaught_exceptions = cp return eb @@ -2600,12 +2597,6 @@ def send_json_message(msg): return True -def set_debug(setup): - setup['DEBUG_RECORD_SOCKET_READS'] = True - setup['DEBUG_TRACE_BREAKPOINTS'] = 1 - setup['DEBUG_TRACE_LEVEL'] = 3 - - def enable_qt_support(qt_support_mode): from _pydev_bundle import pydev_monkey_qt pydev_monkey_qt.patch_qt(qt_support_mode) @@ -3239,14 +3230,40 @@ for handler in pydevd_extension_utils.extensions_of_type(DebuggerEventHandler): handler.on_debugger_modules_loaded(debugger_version=__version__) +def log_to(log_file:str, log_level=3) -> None: + ''' + In pydevd it's possible to log by setting the following environment variables: + + PYDEVD_DEBUG=1 (sets the default log level to 3 along with other default options) + PYDEVD_DEBUG_FILE= + + Note that the file will have the pid of the process added to it (so, logging to + /path/to/file.log would actually start logging to /path/to/file..log -- if subprocesses are + logged, each new subprocess will have the logging set to its own pid). + + Usually setting the environment variable is preferred as it'd log information while + pydevd is still doing its imports and not just after this method is called, but on + cases where this is hard to do this function may be called to set the tracing after + pydevd itself is already imported. + ''' + pydev_log.log_to(log_file, log_level) + + +def _log_initial_info(): + pydev_log.debug("Initial arguments: %s", (sys.argv,)) + pydev_log.debug("Current pid: %s", os.getpid()) + pydev_log.debug("Using cython: %s", USING_CYTHON) + pydev_log.debug("Using frame eval: %s", USING_FRAME_EVAL) + pydev_log.debug("Using gevent mode: %s / imported gevent module support: %s", SUPPORT_GEVENT, bool(pydevd_gevent_integration)) + + #======================================================================================================================= # main #======================================================================================================================= def main(): # parse the command line. --file is our last argument that is required - pydev_log.debug("Initial arguments: %s", (sys.argv,)) - pydev_log.debug("Current pid: %s", os.getpid()) + _log_initial_info() try: from _pydevd_bundle.pydevd_command_line_handling import process_command_line setup = process_command_line(sys.argv) @@ -3255,6 +3272,24 @@ def main(): pydev_log.exception() usage(1) + log_trace_level = setup.get('log-level') + + # Note: the logging info could've been changed (this would happen if this is a + # subprocess and the value in the environment variable does not match the value in the + # argument because the user used `pydevd.log_to` instead of supplying the environment + # variable). If this is the case, update the logging info and re-log some information + # in the new target. + new_debug_file = setup.get('log-file') + if new_debug_file and DebugInfoHolder.PYDEVD_DEBUG_FILE != new_debug_file: + # The debug file can't be set directly, we need to use log_to() so that the a + # new stream is actually created for the new file. + log_to(new_debug_file, log_trace_level if log_trace_level is not None else 3) + _log_initial_info() # The redirection info just changed, log it again. + + elif log_trace_level is not None: + # The log file was not specified + DebugInfoHolder.DEBUG_TRACE_LEVEL = log_trace_level + if setup['print-in-debugger-startup']: try: pid = ' (pid: %s)' % os.getpid() @@ -3267,13 +3302,6 @@ def main(): pydevd_vm_type.setup_type(setup.get('vm_type', None)) - if SHOW_DEBUG_INFO_ENV: - set_debug(setup) - - DebugInfoHolder.DEBUG_RECORD_SOCKET_READS = setup.get('DEBUG_RECORD_SOCKET_READS', DebugInfoHolder.DEBUG_RECORD_SOCKET_READS) - DebugInfoHolder.DEBUG_TRACE_BREAKPOINTS = setup.get('DEBUG_TRACE_BREAKPOINTS', DebugInfoHolder.DEBUG_TRACE_BREAKPOINTS) - DebugInfoHolder.DEBUG_TRACE_LEVEL = setup.get('DEBUG_TRACE_LEVEL', DebugInfoHolder.DEBUG_TRACE_LEVEL) - port = setup['port'] host = setup['client'] f = setup['file'] diff --git a/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/attach_script.py b/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/attach_script.py index c8ad0037..9c636a47 100644 --- a/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/attach_script.py +++ b/src/debugpy/_vendored/pydevd/pydevd_attach_to_process/attach_script.py @@ -168,8 +168,6 @@ def attach(port, host, protocol=''): if py_db is not None: py_db.dispose_and_kill_all_pydevd_threads(wait=False) - # pydevd.DebugInfoHolder.DEBUG_RECORD_SOCKET_READS = True - # pydevd.DebugInfoHolder.DEBUG_TRACE_BREAKPOINTS = 3 # pydevd.DebugInfoHolder.DEBUG_TRACE_LEVEL = 3 pydevd.settrace( port=port, diff --git a/src/debugpy/_vendored/pydevd/tests_python/debugger_fixtures.py b/src/debugpy/_vendored/pydevd/tests_python/debugger_fixtures.py index 8a337cf8..2ab0d23e 100644 --- a/src/debugpy/_vendored/pydevd/tests_python/debugger_fixtures.py +++ b/src/debugpy/_vendored/pydevd/tests_python/debugger_fixtures.py @@ -418,7 +418,7 @@ def case_setup_multiprocessing(debugger_runner_simple): def update_command_line_args(writer, args): ret = debugger_unittest.AbstractWriterThread.update_command_line_args(writer, args) - ret.insert(ret.index('--DEBUG_RECORD_SOCKET_READS'), '--multiprocess') + ret.insert(ret.index('--client'), '--multiprocess') return ret WriterThread.update_command_line_args = update_command_line_args diff --git a/src/debugpy/_vendored/pydevd/tests_python/debugger_unittest.py b/src/debugpy/_vendored/pydevd/tests_python/debugger_unittest.py index d7c77546..bdf7008c 100644 --- a/src/debugpy/_vendored/pydevd/tests_python/debugger_unittest.py +++ b/src/debugpy/_vendored/pydevd/tests_python/debugger_unittest.py @@ -397,7 +397,6 @@ class DebuggerRunner(object): localhost = pydev_localhost.get_localhost() ret = [ writer.get_pydevd_file(), - '--DEBUG_RECORD_SOCKET_READS', ] if not IS_PY36_OR_GREATER or not IS_CPYTHON or not TEST_CYTHON: diff --git a/src/debugpy/_vendored/pydevd/tests_python/resources/_debugger_case_logging.py b/src/debugpy/_vendored/pydevd/tests_python/resources/_debugger_case_logging.py new file mode 100644 index 00000000..b6355b86 --- /dev/null +++ b/src/debugpy/_vendored/pydevd/tests_python/resources/_debugger_case_logging.py @@ -0,0 +1,63 @@ +import subprocess +import sys +import json +from _pydev_bundle import pydev_log +import os +import io + + +def gen_debug_info(): + from _pydevd_bundle.pydevd_constants import DebugInfoHolder + dct = {} + for name in ( + 'PYDEVD_DEBUG_FILE', + 'DEBUG_TRACE_LEVEL', + ): + dct[name] = getattr(DebugInfoHolder, name) + + return dct + + +if __name__ == "__main__": + if '-print-debug' in sys.argv: + info = gen_debug_info() # break on 2nd process + pydev_log.info('Something in print-debug') + + print('>>> print-debug pid: %s' % os.getpid()) + print(json.dumps(info)) + + else: + # Note: when running tests we usually have logging setup, + # so, we create a context so that our changes are restored + # when it finishes (as the `log_to` function will just reset + # whatever is there). + s = io.StringIO() + with pydev_log.log_context(trace_level=3, stream=s): + target_log_file = os.getenv('TARGET_LOG_FILE') + + pydev_log.log_to(target_log_file, 1) + new_debug_info = gen_debug_info() + subprocess_pid = None + with subprocess.Popen( + [sys.executable, __file__, '-print-debug'], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) as process: + subprocess_pid = process.pid + stdout, stderr = process.communicate(input) + + output = stdout.decode('utf-8') + pydev_log.info('Something in initial') + + log_contents = open(pydev_log._compute_filename_with_pid(target_log_file)).read() + assert 'Something in initial' in log_contents, 'Did not find "Something in initial" in %s' % (log_contents,) + + log_contents = open(pydev_log._compute_filename_with_pid(target_log_file, pid=subprocess_pid)).read() + assert 'Something in print-debug' in log_contents, 'Did not find "Something in print-debug" in %s' % (log_contents,) + + output = ''.join(output.splitlines(keepends=True)[1:]) # Remove the first line + loaded_debug_info = json.loads(output) + assert loaded_debug_info == new_debug_info, 'Expected %s. Found: %s' % (new_debug_info, loaded_debug_info) + print('>>> Initial pid: %s' % os.getpid()) + print(output) + print('TEST SUCEEDED') diff --git a/src/debugpy/_vendored/pydevd/tests_python/test_debugger.py b/src/debugpy/_vendored/pydevd/tests_python/test_debugger.py index 567983ba..9262185d 100644 --- a/src/debugpy/_vendored/pydevd/tests_python/test_debugger.py +++ b/src/debugpy/_vendored/pydevd/tests_python/test_debugger.py @@ -3836,10 +3836,12 @@ def test_step_over_my_code_global_setting_and_explicit_include(case_setup): def test_access_token(case_setup): def update_command_line_args(self, args): - args.insert(2, '--access-token') - args.insert(3, 'bar123') - args.insert(2, '--client-access-token') - args.insert(3, 'foo234') + i = args.index('--client') + assert i > 0 + args.insert(i, '--access-token') + args.insert(i + 1, 'bar123') + args.insert(i, '--client-access-token') + args.insert(i + 1, 'foo234') return args with case_setup.test_file('_debugger_case_print.py', update_command_line_args=update_command_line_args) as writer: diff --git a/src/debugpy/_vendored/pydevd/tests_python/test_debugger_json.py b/src/debugpy/_vendored/pydevd/tests_python/test_debugger_json.py index 6d0d4015..73ea3258 100644 --- a/src/debugpy/_vendored/pydevd/tests_python/test_debugger_json.py +++ b/src/debugpy/_vendored/pydevd/tests_python/test_debugger_json.py @@ -4130,8 +4130,8 @@ def test_ppid(case_setup, pyfile): def update_command_line_args(writer, args): ret = debugger_unittest.AbstractWriterThread.update_command_line_args(writer, args) - ret.insert(ret.index('--DEBUG_RECORD_SOCKET_READS'), '--ppid') - ret.insert(ret.index('--DEBUG_RECORD_SOCKET_READS'), '22') + ret.insert(ret.index('--client'), '--ppid') + ret.insert(ret.index('--client'), '22') return ret with case_setup.test_file( @@ -4937,7 +4937,7 @@ def test_no_subprocess_patching(case_setup_multiprocessing, apply_multiprocessin def update_command_line_args(writer, args): ret = debugger_unittest.AbstractWriterThread.update_command_line_args(writer, args) - ret.insert(ret.index('--DEBUG_RECORD_SOCKET_READS'), '--multiprocess') + ret.insert(ret.index('--client'), '--multiprocess') if apply_multiprocessing_patch: ret.append('apply-multiprocessing-patch') return ret @@ -6304,6 +6304,72 @@ def test_ipython_stepping_step_in(case_setup): writer.finished_ok = True +def test_logging_api(case_setup_multiprocessing, tmpdir): + import threading + from tests_python.debugger_unittest import AbstractWriterThread + + log_file = str(tmpdir.join('pydevd_in_test_logging.log')) + + def get_environ(self): + env = os.environ.copy() + env["TARGET_LOG_FILE"] = log_file + return env + + with case_setup_multiprocessing.test_file( + '_debugger_case_logging.py', + get_environ=get_environ + ) as writer: + json_facade = JsonFacade(writer) + json_facade.write_launch() + + break1_line = writer.get_line_index_with_content("break on 2nd process") + json_facade.write_set_breakpoints([break1_line]) + + server_socket = writer.server_socket + secondary_finished_ok = [False] + + class SecondaryProcessWriterThread(AbstractWriterThread): + + TEST_FILE = writer.get_main_filename() + _sequence = -1 + + class SecondaryProcessThreadCommunication(threading.Thread): + + def run(self): + from tests_python.debugger_unittest import ReaderThread + server_socket.listen(1) + self.server_socket = server_socket + new_sock, addr = server_socket.accept() + + reader_thread = ReaderThread(new_sock) + reader_thread.name = ' *** Multiprocess Reader Thread' + reader_thread.start() + + writer2 = SecondaryProcessWriterThread() + writer2.reader_thread = reader_thread + writer2.sock = new_sock + json_facade2 = JsonFacade(writer2) + + json_facade2.write_set_breakpoints([break1_line, ]) + json_facade2.write_make_initial_run() + + json_facade2.wait_for_thread_stopped() + json_facade2.write_continue() + secondary_finished_ok[0] = True + + secondary_process_thread_communication = SecondaryProcessThreadCommunication() + secondary_process_thread_communication.start() + time.sleep(.1) + + json_facade.write_make_initial_run() + secondary_process_thread_communication.join(10) + if secondary_process_thread_communication.is_alive(): + raise AssertionError('The SecondaryProcessThreadCommunication did not finish') + + assert secondary_finished_ok[0] + writer.finished_ok = True + + if __name__ == '__main__': pytest.main(['-k', 'test_replace_process', '-s']) diff --git a/src/debugpy/_vendored/pydevd/tests_python/test_utilities.py b/src/debugpy/_vendored/pydevd/tests_python/test_utilities.py index b9dfbe33..09b8b19d 100644 --- a/src/debugpy/_vendored/pydevd/tests_python/test_utilities.py +++ b/src/debugpy/_vendored/pydevd/tests_python/test_utilities.py @@ -255,10 +255,7 @@ def _check_tracing_other_threads(): import pydevd_tracing import time from tests_python.debugger_unittest import wait_for_condition - try: - import _thread - except ImportError: - import thread as _thread + import _thread # This method is called in a subprocess, so, make sure we exit properly even if we somehow # deadlock somewhere else.