[Dexter] Adjust launch sequencing to align closer with DAP spec (#170523)

Following PR #169744 the DAP launch sequencing of Dexter was changed to
complete a launch request/response before performing configuration
steps. This matches LLDB's current behaviour, but is not compatible with
the DAP specification and causes issues interfacing with other
debuggers.

This patch tries to bridge the gap by using a sequencing that is mostly
DAP-compliant while still interfacing correctly with lldb-dap: we send a
launch request first, then perform all configuration steps and send
configurationDone, and then await the launch response. For lldb-dap, we
do not wait for the launch response and may send configuration requests
before it is received, but lldb-dap appears to handle this without
issue. For other debug adapters, the launch request will be ignored
until the configurationDone request is received and responded to, at
which point the launch request will be acted upon and responded to.

As an additional note, the initialized event should be sent after the
initialize response and before the launch request according to the spec,
but as LLDB currently sends it after the launch response Dexter has
avoided checking for it. Since the initialized event is now being sent
after the launch response by LLDB, we can start checking for it earlier
in the sequence as well (though technically the client should receive
the initialized event before it sends the launch request).
This commit is contained in:
Stephen Tozer
2025-12-09 11:32:09 +00:00
committed by GitHub
parent 9bfb3be8c8
commit 5446eb9275

View File

@@ -763,23 +763,39 @@ class DAP(DebuggerBase, metaclass=abc.ABCMeta):
launch_request = self._get_launch_params(cmdline)
# Per DAP protocol, the correct sequence is:
# Per DAP protocol, we follow the sequence:
# 1. Send launch request
# 2. Wait for launch response and "initialized" event
# 3. Set breakpoints
# 4. Send configurationDone to start the process
# 2. Set breakpoints
# 3. Send configurationDone to start the process
# 4. Wait for launch and configurationDone responses, and a "process" event, to confirm successful launch
# NB: Technically, we should also wait for the "initialized" event before sending the launch request, but in
# practice there are DAP implementations that do not send the initialized event until post-launch, and all
# adapters seem to accept us not waiting for the initialized event, so ignoring it gives maximum compatibility.
launch_req_id = self.send_message(self.make_request("launch", launch_request))
launch_response = self._await_response(launch_req_id)
if not launch_response["success"]:
raise DebuggerException(
f"failure launching debugger: \"{launch_response['body']['error']['format']}\""
)
# Wait for the initialized event; for LLDB, this will be sent after the launch request has been processed;
# for other debuggers, it will have been sent some time after the initialize response was sent.
# NB: In all current cases this timeout is never hit because the initialized event is received almost
# immediately after either the initialize response or the launch request/response; if this starts being hit, we
# probably need to parameterize this.
initialize_timeout = Timeout(3)
while not self._debugger_state.initialized:
if initialize_timeout.timed_out():
raise TimeoutError(
f"Timed out while waiting for initialized event from DAP"
)
time.sleep(0.001)
# Set breakpoints after receiving launch response but before configurationDone.
self._flush_breakpoints()
# Send configurationDone to allow the process to start running.
config_done_req_id = self.send_message(self.make_request("configurationDone"))
launch_response = self._await_response(launch_req_id)
if not launch_response["success"]:
raise DebuggerException(
f"failure launching debugger: \"{launch_response['body']['error']['format']}\""
)
config_done_response = self._await_response(config_done_req_id)
assert config_done_response["success"]