From 5446eb9275b77d4a768c594aef83257f334074fd Mon Sep 17 00:00:00 2001 From: Stephen Tozer Date: Tue, 9 Dec 2025 11:32:09 +0000 Subject: [PATCH] [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). --- .../dexter/dex/debugger/DAP.py | 34 ++++++++++++++----- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/cross-project-tests/debuginfo-tests/dexter/dex/debugger/DAP.py b/cross-project-tests/debuginfo-tests/dexter/dex/debugger/DAP.py index 68ca50a5e81d..b75fe18d8ba0 100644 --- a/cross-project-tests/debuginfo-tests/dexter/dex/debugger/DAP.py +++ b/cross-project-tests/debuginfo-tests/dexter/dex/debugger/DAP.py @@ -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"]