From ef4f122724234f87669bacd5e41e8cf26ff1d06a Mon Sep 17 00:00:00 2001 From: ZengYuhong <18627229501@163.com> Date: Wed, 6 Dec 2023 22:43:56 +0800 Subject: [PATCH] Added multiThread option to enhance multithreaded debugging --- .../_vendored/pydevd/_pydevd_bundle/pydevd_api.py | 3 +++ .../pydevd/_pydevd_bundle/pydevd_json_debug_options.py | 9 +++++++++ .../_pydevd_bundle/pydevd_net_command_factory_json.py | 4 ++-- .../_pydevd_bundle/pydevd_net_command_factory_xml.py | 4 ++++ .../_pydevd_bundle/pydevd_process_net_command_json.py | 7 ++++--- src/debugpy/_vendored/pydevd/pydevd.py | 8 +++++++- src/debugpy/adapter/clients.py | 10 +++++++--- 7 files changed, 36 insertions(+), 9 deletions(-) diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_api.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_api.py index 0b21c59b..6873d1bc 100644 --- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_api.py +++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_api.py @@ -129,6 +129,9 @@ class PyDevdAPI(object): def set_gui_event_loop(self, py_db, gui_event_loop): py_db._gui_event_loop = gui_event_loop + def set_multi_thread(self, py_db, multi_thread): + py_db.set_multi_thread(multi_thread) + def send_error_message(self, py_db, msg): cmd = py_db.cmd_factory.make_warning_message('pydevd: %s\n' % (msg,)) py_db.writer.add_command(cmd) diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_json_debug_options.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_json_debug_options.py index 0165455c..6ecd218a 100644 --- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_json_debug_options.py +++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_json_debug_options.py @@ -15,6 +15,7 @@ class DebugOptions(object): 'max_exception_stack_frames', 'gui_event_loop', 'client_os', + 'multi_thread' ] def __init__(self): @@ -28,6 +29,7 @@ class DebugOptions(object): self.max_exception_stack_frames = 0 self.gui_event_loop = 'matplotlib' self.client_os = None + self.multi_thread = False def to_json(self): dct = {} @@ -60,6 +62,9 @@ class DebugOptions(object): if 'CLIENT_OS_TYPE' in debug_options: self.client_os = debug_options.get('CLIENT_OS_TYPE') + if 'MULTI_THREAD' in debug_options: + self.multi_thread = debug_options.get('MULTI_THREAD') + # Note: _max_exception_stack_frames cannot be set by debug options. def update_from_args(self, args): @@ -99,6 +104,8 @@ class DebugOptions(object): if 'clientOS' in args: self.client_os = str(args['clientOS']).upper() + if 'multiThread' in args: + self.multi_thread = bool_parser(args['multiThread']) def int_parser(s, default_value=0): try: @@ -128,6 +135,7 @@ DEBUG_OPTIONS_PARSER = { 'STOP_ON_ENTRY': bool_parser, 'SHOW_RETURN_VALUE': bool_parser, 'MULTIPROCESS': bool_parser, + 'MULTI_THREAD': bool_parser, } DEBUG_OPTIONS_BY_FLAG = { @@ -145,6 +153,7 @@ DEBUG_OPTIONS_BY_FLAG = { 'StopOnEntry': 'STOP_ON_ENTRY=True', 'ShowReturnValue': 'SHOW_RETURN_VALUE=True', 'Multiprocess': 'MULTIPROCESS=True', + 'MultiThread': 'MULTI_THREAD=False', } diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_json.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_json.py index 266653f4..02a4f823 100644 --- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_json.py +++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_json.py @@ -384,7 +384,7 @@ class NetCommandFactoryJson(NetCommandFactory): description=exc_desc, threadId=thread_id, text=exc_name, - allThreadsStopped=True, + allThreadsStopped= thread_id == '*' if self.multi_thread else True, preserveFocusHint=preserve_focus_hint, ) event = pydevd_schema.StoppedEvent(body) @@ -392,7 +392,7 @@ class NetCommandFactoryJson(NetCommandFactory): @overrides(NetCommandFactory.make_thread_resume_single_notification) def make_thread_resume_single_notification(self, thread_id): - body = ContinuedEventBody(threadId=thread_id, allThreadsContinued=True) + body = ContinuedEventBody(threadId=thread_id, allThreadsContinued=thread_id == '*' if self.multi_thread else True) event = pydevd_schema.ContinuedEvent(body) return NetCommand(CMD_THREAD_RESUME_SINGLE_NOTIFICATION, 0, event, is_json=True) diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_xml.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_xml.py index 7df81059..79f43b26 100644 --- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_xml.py +++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_xml.py @@ -37,6 +37,7 @@ class NetCommandFactory(object): def __init__(self): self._additional_thread_id_to_thread_name = {} + self.multi_thread = False def _thread_to_xml(self, thread): """ thread information as XML """ @@ -508,3 +509,6 @@ This may mean a number of things: def make_exit_command(self, py_db): return NULL_EXIT_COMMAND + + def set_multi_thread(self, multi_thread): + self.multi_thread = multi_thread 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 42eb5991..9dccd52d 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 @@ -327,7 +327,7 @@ class PyDevJsonCommandProcessor(object): def _set_debug_options(self, py_db, args, start_reason): rules = args.get('rules') - stepping_resumes_all_threads = args.get('steppingResumesAllThreads', True) + stepping_resumes_all_threads = not args.get('multiThread', False) and args.get('steppingResumesAllThreads', True) self.api.set_stepping_resumes_all_threads(py_db, stepping_resumes_all_threads) terminate_child_processes = args.get('terminateChildProcesses', True) @@ -472,6 +472,7 @@ class PyDevJsonCommandProcessor(object): self.api.stop_on_entry() self.api.set_gui_event_loop(py_db, self._options.gui_event_loop) + self.api.set_multi_thread(py_db, self._options.multi_thread) def _send_process_event(self, py_db, start_method): argv = getattr(sys, 'argv', []) @@ -735,7 +736,7 @@ class PyDevJsonCommandProcessor(object): arguments = request.arguments # : :type arguments: SetFunctionBreakpointsArguments function_breakpoints = [] - suspend_policy = 'ALL' + suspend_policy = 'NONE' if self._options.multi_thread else 'ALL' # Not currently covered by the DAP. is_logpoint = False @@ -775,7 +776,7 @@ class PyDevJsonCommandProcessor(object): self.api.remove_all_breakpoints(py_db, filename) btype = 'python-line' - suspend_policy = 'ALL' + suspend_policy = 'NONE' if self._options.multi_thread else 'ALL' if not filename.lower().endswith('.py'): # Note: check based on original file, not mapping. if self._options.django_debug: diff --git a/src/debugpy/_vendored/pydevd/pydevd.py b/src/debugpy/_vendored/pydevd/pydevd.py index ae865b16..16144f91 100644 --- a/src/debugpy/_vendored/pydevd/pydevd.py +++ b/src/debugpy/_vendored/pydevd/pydevd.py @@ -714,6 +714,8 @@ class PyDB(object): # DAP related self._dap_messages_listeners = [] + self.multi_thread = False + if set_as_global: # Set as the global instance only after it's initialized. set_global_debugger(self) @@ -1842,6 +1844,10 @@ class PyDB(object): return eb + def set_multi_thread(self, multi_thread): + self.multi_thread = multi_thread + self.cmd_factory.set_multi_thread(multi_thread) + def set_suspend(self, thread, stop_reason, suspend_other_threads=False, is_pause=False, original_step_cmd=-1): ''' :param thread: @@ -1882,7 +1888,7 @@ class PyDB(object): info.conditional_breakpoint_exception = None self._send_breakpoint_condition_exception(thread, conditional_breakpoint_exception_tuple) - if not suspend_other_threads and self.multi_threads_single_notification: + if not self.multi_thread and not suspend_other_threads and self.multi_threads_single_notification: # In the mode which gives a single notification when all threads are # stopped, stop all threads whenever a set_suspend is issued. suspend_other_threads = True diff --git a/src/debugpy/adapter/clients.py b/src/debugpy/adapter/clients.py index ee1d1514..3d535f02 100644 --- a/src/debugpy/adapter/clients.py +++ b/src/debugpy/adapter/clients.py @@ -111,6 +111,7 @@ class Client(components.Component): }, ) sessions.report_sockets() + self.multi_thread = False def propagate_after_start(self, event): # pydevd starts sending events as soon as we connect, but the client doesn't @@ -228,6 +229,7 @@ class Client(components.Component): self._initialize_request = None arguments = request.arguments + self.multi_thread = arguments.get('multiThread', False) if self.launcher: redirecting = arguments.get("console") == "internalConsole" if "RedirectOutput" in debug_options: @@ -629,12 +631,14 @@ class Client(components.Component): @message_handler def pause_request(self, request): - request.arguments["threadId"] = "*" + if not self.multi_thread: + request.arguments["threadId"] = "*" return self.server.channel.delegate(request) @message_handler def continue_request(self, request): - request.arguments["threadId"] = "*" + if not self.multi_thread: + request.arguments["threadId"] = "*" try: return self.server.channel.delegate(request) @@ -642,7 +646,7 @@ class Client(components.Component): # pydevd can sometimes allow the debuggee to exit before the queued # "continue" response gets sent. Thus, a failed "continue" response # indicating that the server disconnected should be treated as success. - return {"allThreadsContinued": True} + return {"allThreadsContinued": not self.multi_thread} @message_handler def debugpySystemInfo_request(self, request):