Added multiThread option to enhance multithreaded debugging

This commit is contained in:
ZengYuhong 2023-12-06 22:43:56 +08:00
parent ef9a67fe15
commit ef4f122724
7 changed files with 36 additions and 9 deletions

View file

@ -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)

View file

@ -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',
}

View file

@ -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)

View file

@ -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

View file

@ -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:

View file

@ -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

View file

@ -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):