[Lldb-commits] [lldb] [lldb-dap] Test Gardening, improving DebugCommunication. (PR #141689)
John Harrison via lldb-commits
lldb-commits at lists.llvm.org
Wed May 28 13:53:14 PDT 2025
================
@@ -224,99 +265,155 @@ def collect_output(self, category, timeout_secs, pattern, clear=True):
break
return collected_output if collected_output else None
- def _enqueue_recv_packet(self, packet: Optional[ProtocolMessage]):
- self.recv_condition.acquire()
+ def _enqueue_recv_packet(self, packet: Union[ProtocolMessage, EOFError]):
self.recv_packets.append(packet)
self.recv_condition.notify()
- self.recv_condition.release()
- def _handle_recv_packet(self, packet: Optional[ProtocolMessage]) -> bool:
+ def _handle_recv_packet(self, packet: _InternalProtocolMessage) -> bool:
"""Called by the read thread that is waiting for all incoming packets
to store the incoming packet in "self.recv_packets" in a thread safe
way. This function will then signal the "self.recv_condition" to
indicate a new packet is available. Returns True if the caller
should keep calling this function for more packets.
"""
- # If EOF, notify the read thread by enqueuing a None.
- if not packet:
- self._enqueue_recv_packet(None)
- return False
-
- # Check the packet to see if is an event packet
- keepGoing = True
- packet_type = packet["type"]
- if packet_type == "event":
- event = packet["event"]
- body = None
- if "body" in packet:
- body = packet["body"]
- # Handle the event packet and cache information from these packets
- # as they come in
- if event == "output":
- # Store any output we receive so clients can retrieve it later.
- category = body["category"]
- output = body["output"]
- self.output_condition.acquire()
- if category in self.output:
- self.output[category] += output
- else:
- self.output[category] = output
- self.output_condition.notify()
- self.output_condition.release()
- # no need to add 'output' event packets to our packets list
- return keepGoing
- elif event == "initialized":
- self.initialized = True
- elif event == "process":
- # When a new process is attached or launched, remember the
- # details that are available in the body of the event
- self.process_event_body = body
- elif event == "exited":
- # Process exited, mark the status to indicate the process is not
- # alive.
- self.exit_status = body["exitCode"]
- elif event == "continued":
- # When the process continues, clear the known threads and
- # thread_stop_reasons.
- all_threads_continued = body.get("allThreadsContinued", True)
- tid = body["threadId"]
- if tid in self.thread_stop_reasons:
- del self.thread_stop_reasons[tid]
- self._process_continued(all_threads_continued)
- elif event == "stopped":
- # Each thread that stops with a reason will send a
- # 'stopped' event. We need to remember the thread stop
- # reasons since the 'threads' command doesn't return
- # that information.
- self._process_stopped()
- tid = body["threadId"]
- self.thread_stop_reasons[tid] = body
- elif event.startswith("progress"):
- # Progress events come in as 'progressStart', 'progressUpdate',
- # and 'progressEnd' events. Keep these around in case test
- # cases want to verify them.
- self.progress_events.append(packet)
-
- elif packet_type == "response":
- if packet["command"] == "disconnect":
- keepGoing = False
- self._enqueue_recv_packet(packet)
- return keepGoing
+ # Hold the recv_condition for consistency of debugger state.
+ with self.recv_condition:
+ if isinstance(packet, EOFError):
+ self._enqueue_recv_packet(packet)
+ return False
+
+ keep_going = True
+
+ # Check the packet to see if is an event packet
+ if packet["type"] == "event" and "event" in packet:
+ event = packet["event"]
+ body = packet.get("body")
+ # Handle the event packet and cache DAP stateful information from
+ # these packets as they come in.
+ if event == "output" and body is not None:
+ # Store any output we receive so clients can retrieve it later.
+ category = body["category"]
+ output = body["output"]
+ self.output_condition.acquire()
+ if category in self.output:
+ self.output[category] += output
+ else:
+ self.output[category] = output
+ self.output_condition.notify()
+ self.output_condition.release()
+ # no need to add 'output' event packets to our packets list
+ return keep_going
+ elif event == "initialized":
+ self.initialized = True
+ elif event == "process" and body is not None:
+ # When a new process is attached or launched, remember the
+ # details that are available in the body of the event
+ self.process_event_body = body
+ elif event == "terminated":
+ # If we get the 'terminated' event then lldb-dap has exited
+ # itself.
+ self.terminated = True
+ elif event == "exited" and body is not None:
+ # Process exited, mark the status to indicate the process is not
+ # alive.
+ self.exit_status = body.get("exitCode", 0)
+ elif event == "continued" and body is not None:
+ # When the process continues, clear the known threads and
+ # thread_stop_reasons.
+ all_threads_continued = body.get("allThreadsContinued", True)
+ tid = body["threadId"]
+ if tid in self.thread_stop_reasons:
+ del self.thread_stop_reasons[tid]
+ self._process_continued(all_threads_continued)
+ elif event == "stopped" and body is not None:
+ # Each thread that stops with a reason will send a
+ # 'stopped' event. We need to remember the thread stop
+ # reasons since the 'threads' command doesn't return
+ # that information.
+ self._process_stopped()
+ tid = body["threadId"]
+ self.thread_stop_reasons[tid] = body
+ elif event.startswith("progress"):
+ # Progress events come in as 'progressStart', 'progressUpdate',
+ # and 'progressEnd' events. Keep these around in case test
+ # cases want to verify them.
+ self.progress_events.append(packet)
+
+ elif packet["type"] == "response":
+ if packet["command"] == "disconnect":
+ keep_going = False
+
+ elif packet["type"] == "request":
+ # Handle reverse requests and keep processing.
+ self._handle_reverse_request(packet)
+ return keep_going
+
+ self._enqueue_recv_packet(packet)
+ return keep_going
def _process_continued(self, all_threads_continued: bool):
self.threads = None
self.frame_scopes = {}
if all_threads_continued:
self.thread_stop_reasons = {}
- def send_packet(self, command_dict: Request, set_sequence=True):
+ def _handle_reverse_request(self, request: Request):
+ self.reverse_requests.append(request)
+ arguments = request.get("arguments")
+ if request["command"] == "runInTerminal" and arguments is not None:
+ in_shell = arguments.get("argsCanBeInterpretedByShell", False)
+ proc = subprocess.Popen(
+ arguments["args"],
+ env=arguments.get("env", {}),
+ cwd=arguments["cwd"],
+ stdin=subprocess.DEVNULL,
+ stdout=subprocess.DEVNULL,
+ stderr=subprocess.DEVNULL,
+ shell=in_shell,
+ )
+ body = {}
+ if in_shell:
+ body["shellProcessId"] = proc.pid
+ else:
+ body["processId"] = proc.pid
+ self.send_packet(
+ {
+ "type": "response",
+ "seq": 0,
+ "request_seq": request["seq"],
+ "success": True,
+ "command": "runInTerminal",
+ "message": None,
+ "body": body,
+ }
+ )
+ elif request["command"] == "startDebugging":
+ self.send_packet(
+ {
+ "type": "response",
+ "seq": 0,
+ "request_seq": request["seq"],
+ "success": True,
+ "message": None,
+ "command": "startDebugging",
+ "body": {},
+ }
+ )
+ else:
+ desc = 'unknown reverse request "%s"' % (request["command"])
+ raise ValueError(desc)
+
+ def send_packet(self, command_dict: ProtocolMessage) -> int:
"""Take the "command_dict" python dictionary and encode it as a JSON
string and send the contents as a packet to the VSCode debug
- adapter"""
+ adapter."""
+ seq = 0
# Set the sequence ID for this command automatically
- if set_sequence:
- command_dict["seq"] = self.sequence
+ if command_dict["type"] == "request":
+ seq = command_dict["seq"] = self.sequence
self.sequence += 1
+ else:
+ command_dict["seq"] = 0
----------------
ashgti wrote:
Updated with a slightly different version, LMKWYT
https://github.com/llvm/llvm-project/pull/141689
More information about the lldb-commits
mailing list