[7/7] Defer DAP launch command until after configurationDone

Message ID 20241121-dap-launch-v3-v1-7-c1fa046b3285@adacore.com
State New
Headers
Series Rewrite DAP launch and attach implementations |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gdb_build--master-aarch64 fail Patch failed to apply
linaro-tcwg-bot/tcwg_gdb_build--master-arm fail Patch failed to apply

Commit Message

Tom Tromey Nov. 21, 2024, 8:35 p.m. UTC
  PR dap/32090 points out that gdb's DAP "launch" sequencing is
incorrect.  The current approach (which is itself a 2nd
implementation...) was based on a misreading of the spec.  The spec
has since been clarified here:

    https://github.com/microsoft/debug-adapter-protocol/issues/497

The clarification here is that a client is free to send the "launch"
(or "attach") request at any point after the "initialized" event has
been sent by gdb.  However, the "launch" does not cause any action to
be taken -- and does not send a response -- until after
"configurationDone" has been seen.

This patch implements this by arranging for the launch and attach
commands to return a DeferredRequest object.

All the tests needed updates.  I've also added a new test that checks
that the deferred "launch" request can be cancelled.  (Note that the
cancellation is lazy -- it also waits until configurationDone is seen.
This could be fixed, but I was not sure whether it is important to do
so.)

Finally, the "launch" command has a somewhat funny sequencing now.
Simply sending the command and waiting for a response yielded strange
results if the inferior did not stop -- in this case, the repsonse was
never sent.  So now, the command is split into two parts, with some
setup being done synchronously (for better error propagation) and the
actual "run" being done async.

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=32090
---
 gdb/NEWS                                  |   6 ++
 gdb/python/lib/gdb/dap/launch.py          | 152 ++++++++++++++++++++++--------
 gdb/testsuite/gdb.dap/ada-arrays.exp      |   7 +-
 gdb/testsuite/gdb.dap/ada-nested.exp      |   7 +-
 gdb/testsuite/gdb.dap/ada-scopes.exp      |   7 +-
 gdb/testsuite/gdb.dap/args-env.exp        |   7 +-
 gdb/testsuite/gdb.dap/assign.exp          |   7 +-
 gdb/testsuite/gdb.dap/attach.exp          |  19 ++--
 gdb/testsuite/gdb.dap/basic-dap.exp       |   7 +-
 gdb/testsuite/gdb.dap/bt-nodebug.exp      |   7 +-
 gdb/testsuite/gdb.dap/cancel-launch.exp   |  56 +++++++++++
 gdb/testsuite/gdb.dap/catch-exception.exp |   7 +-
 gdb/testsuite/gdb.dap/children.exp        |   7 +-
 gdb/testsuite/gdb.dap/cond-bp.exp         |   7 +-
 gdb/testsuite/gdb.dap/cwd.exp             |  10 +-
 gdb/testsuite/gdb.dap/cxx-exception.exp   |   7 +-
 gdb/testsuite/gdb.dap/disassem.exp        |   7 +-
 gdb/testsuite/gdb.dap/frameless.exp       |   7 +-
 gdb/testsuite/gdb.dap/global.exp          |   7 +-
 gdb/testsuite/gdb.dap/hover.exp           |   7 +-
 gdb/testsuite/gdb.dap/insn-bp.exp         |  12 +--
 gdb/testsuite/gdb.dap/lazy-string.exp     |   7 +-
 gdb/testsuite/gdb.dap/log-message.exp     |   7 +-
 gdb/testsuite/gdb.dap/memory.exp          |   7 +-
 gdb/testsuite/gdb.dap/modules.exp         |   7 +-
 gdb/testsuite/gdb.dap/pause.exp           |   7 +-
 gdb/testsuite/gdb.dap/ptrref.exp          |   7 +-
 gdb/testsuite/gdb.dap/remote-dap.exp      |  10 +-
 gdb/testsuite/gdb.dap/rust-slices.exp     |   7 +-
 gdb/testsuite/gdb.dap/scopes.exp          |   7 +-
 gdb/testsuite/gdb.dap/sources.exp         |  14 +--
 gdb/testsuite/gdb.dap/stack-format.exp    |   7 +-
 gdb/testsuite/gdb.dap/step-out.exp        |   7 +-
 gdb/testsuite/gdb.dap/stop-at-main.exp    |   7 +-
 gdb/testsuite/gdb.dap/terminate.exp       |   7 +-
 gdb/testsuite/lib/dap-support.exp         |  33 ++++---
 36 files changed, 337 insertions(+), 164 deletions(-)
  

Comments

Kévin Le Gouguec Dec. 2, 2024, 4:20 p.m. UTC | #1
Tom Tromey <tromey@adacore.com> writes:

> diff --git a/gdb/testsuite/gdb.dap/cancel-launch.exp b/gdb/testsuite/gdb.dap/cancel-launch.exp
[…]
> +set launch_id [dap_launch $testfile]
> +
> +# Set a breakpoint.  This is done to ensure that the launch request is
> +# definitely in the deferred state when we try to cancel it.
> +set line [gdb_get_line_number "Distinguishing comment"]
> +dap_check_request_and_response "set breakpoint by line number" \
> +    setBreakpoints \
> +    [format {o source [o path [%s]] breakpoints [a [o line [i %d]]]} \
> +	 [list s $srcfile] $line]
> +
> +set cancel_id [dap_send_request cancel \
> +		   [format {o requestId [i %d]} $launch_id]]
> +
> +dap_read_response cancel $cancel_id
> +
> +# The cancellation isn't actually processed until configurationDone is
> +# sent.  While this seems fine, it's unclear if gdb should be more
> +# eager here and try to cancel a deferred task before it is
> +# rescheduled.
> +dap_check_request_and_response "configurationDone" configurationDone
> +
> +dap_read_response launch $launch_id

Do I understand correctly that this response to "launch" should be
  {"success": false, "message": "cancelled"}
?  If so, would it be worth testing?
  
Tom Tromey Dec. 3, 2024, 7:27 p.m. UTC | #2
>>>>> "Kévin" == Kévin Le Gouguec <legouguec@adacore.com> writes:

>> +dap_read_response launch $launch_id

Kévin> Do I understand correctly that this response to "launch" should be
Kévin>   {"success": false, "message": "cancelled"}
Kévin> ?  If so, would it be worth testing?

Yeah, good idea.  Locally I changed it to:

    set resp [lindex [dap_read_response launch $launch_id] 0]
    gdb_assert {[dict get $resp success] == "false"} \
        "launch failed"
    gdb_assert {[dict get $resp message] == "cancelled"} \
        "launch cancelled"

Tom
  

Patch

diff --git a/gdb/NEWS b/gdb/NEWS
index 046daad0eae5fe8d853beb6cb0bb751ee6f23a77..327da08cf2f7137bc3b3685000454ba3d9b61e7a 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -92,6 +92,12 @@ 
      the return value from the latest "stepOut" command, when
      appropriate.
 
+  ** The "launch" and "attach" requests were rewritten in accordance
+     with some clarifications to the spec.  Now they can be sent at
+     any time after the "initialized" event, but will not take effect
+     (or send a response) until after the "configurationDone" request
+     has been sent.
+
 * New commands
 
 set style line-number foreground COLOR
diff --git a/gdb/python/lib/gdb/dap/launch.py b/gdb/python/lib/gdb/dap/launch.py
index 6444c8b7b6741c4847a6c12df78685afca37847a..fc1890c5a43d645d18da586c614ec8d30cf51071 100644
--- a/gdb/python/lib/gdb/dap/launch.py
+++ b/gdb/python/lib/gdb/dap/launch.py
@@ -21,8 +21,42 @@  from typing import Mapping, Optional, Sequence
 import gdb
 
 from .events import exec_and_expect_stop, expect_process, expect_stop
-from .server import capability, request
-from .startup import DAPException, exec_and_log, in_gdb_thread
+from .server import (
+    DeferredRequest,
+    call_function_later,
+    capability,
+    request,
+    send_gdb,
+    send_gdb_with_response,
+)
+from .startup import DAPException, exec_and_log, in_dap_thread, in_gdb_thread
+
+# A launch or attach promise that that will be fulfilled after a
+# configurationDone request has been processed.
+_launch_or_attach_promise = None
+
+
+# A DeferredRequest that handles either a "launch" or "attach"
+# request.
+class _LaunchOrAttachDeferredRequest(DeferredRequest):
+    def __init__(self, callback):
+        self._callback = callback
+        global _launch_or_attach_promise
+        if _launch_or_attach_promise is not None:
+            raise DAPException("launch or attach already specified")
+        _launch_or_attach_promise = self
+
+    # Invoke the callback and return the result.
+    @in_dap_thread
+    def invoke(self):
+        return self._callback()
+
+    # Override this so we can clear the global when rescheduling.
+    @in_dap_thread
+    def reschedule(self):
+        global _launch_or_attach_promise
+        _launch_or_attach_promise = None
+        super().reschedule()
 
 
 # A wrapper for the 'file' command that correctly quotes its argument.
@@ -37,7 +71,7 @@  def file_command(program):
 # Any parameters here are necessarily extensions -- DAP requires this
 # from implementations.  Any additions or changes here should be
 # documented in the gdb manual.
-@request("launch", response=False)
+@request("launch", on_dap_thread=True)
 def launch(
     *,
     program: Optional[str] = None,
@@ -48,27 +82,51 @@  def launch(
     stopOnEntry: bool = False,
     **extra,
 ):
-    if cwd is not None:
-        exec_and_log("cd " + cwd)
-    if program is not None:
-        file_command(program)
-    inf = gdb.selected_inferior()
-    inf.arguments = args
-    if env is not None:
-        inf.clear_env()
-        for name, value in env.items():
-            inf.set_env(name, value)
-    expect_process("process")
-    if stopAtBeginningOfMainSubprogram:
-        cmd = "start"
-    elif stopOnEntry:
-        cmd = "starti"
-    else:
-        cmd = "run"
-    exec_and_expect_stop(cmd)
-
-
-@request("attach")
+    # Launch setup is handled here.  This is done synchronously so
+    # that errors can be reported in a natural way.
+    @in_gdb_thread
+    def _setup_launch():
+        if cwd is not None:
+            exec_and_log("cd " + cwd)
+        if program is not None:
+            file_command(program)
+        inf = gdb.selected_inferior()
+        inf.arguments = args
+        if env is not None:
+            inf.clear_env()
+            for name, value in env.items():
+                inf.set_env(name, value)
+
+    # Actual launching done here.  See below for more info.
+    @in_gdb_thread
+    def _do_launch():
+        expect_process("process")
+        if stopAtBeginningOfMainSubprogram:
+            cmd = "start"
+        elif stopOnEntry:
+            cmd = "starti"
+        else:
+            cmd = "run"
+        exec_and_expect_stop(cmd)
+
+    @in_dap_thread
+    def _launch_impl():
+        send_gdb_with_response(_setup_launch)
+        # We do not wait for the result here.  It might be a little
+        # nicer if we did -- perhaps the various thread events would
+        # occur in a more logical sequence -- but if the inferior does
+        # not stop, then the launch response will not be seen either,
+        # which seems worse.
+        send_gdb(_do_launch)
+        # Launch response does not have a body.
+        return None
+
+    # The launch itself is deferred until the configurationDone
+    # request.
+    return _LaunchOrAttachDeferredRequest(_launch_impl)
+
+
+@request("attach", on_dap_thread=True)
 def attach(
     *,
     program: Optional[str] = None,
@@ -76,21 +134,39 @@  def attach(
     target: Optional[str] = None,
     **args,
 ):
-    if program is not None:
-        file_command(program)
-    if pid is not None:
-        cmd = "attach " + str(pid)
-    elif target is not None:
-        cmd = "target remote " + target
-    else:
-        raise DAPException("attach requires either 'pid' or 'target'")
-    expect_process("attach")
-    expect_stop("attach")
-    exec_and_log(cmd)
+    # The actual attach is handled by this function.
+    @in_gdb_thread
+    def _do_attach():
+        if program is not None:
+            file_command(program)
+        if pid is not None:
+            cmd = "attach " + str(pid)
+        elif target is not None:
+            cmd = "target remote " + target
+        else:
+            raise DAPException("attach requires either 'pid' or 'target'")
+        expect_process("attach")
+        expect_stop("attach")
+        exec_and_log(cmd)
+        # Attach response does not have a body.
+        return None
+
+    @in_dap_thread
+    def _attach_impl():
+        return send_gdb_with_response(_do_attach)
+
+    # The attach itself is deferred until the configurationDone
+    # request.
+    return _LaunchOrAttachDeferredRequest(_attach_impl)
 
 
 @capability("supportsConfigurationDoneRequest")
-@request("configurationDone")
+@request("configurationDone", on_dap_thread=True)
 def config_done(**args):
-    # Nothing to do.
-    return None
+    # Handle the launch or attach.
+    global _launch_or_attach_promise
+    if _launch_or_attach_promise is None:
+        raise DAPException("launch or attach not specified")
+    # Resolve the launch or attach, but only after the
+    # configurationDone response has been sent.
+    call_function_later(_launch_or_attach_promise.reschedule)
diff --git a/gdb/testsuite/gdb.dap/ada-arrays.exp b/gdb/testsuite/gdb.dap/ada-arrays.exp
index 0de361f96bd78cfddf8f3cf67a26c794bef081aa..64c6eb28c892821dbf3f31c06c98f46bc3eba24f 100644
--- a/gdb/testsuite/gdb.dap/ada-arrays.exp
+++ b/gdb/testsuite/gdb.dap/ada-arrays.exp
@@ -33,6 +33,8 @@  if {[dap_initialize] == ""} {
     return
 }
 
+set launch_id [dap_launch $testfile]
+
 # Stop in a C frame, but examine values in an Ada frame, to make sure
 # cross-language scenarios work correctly.
 set line [gdb_get_line_number "STOP" $testdir/cstuff.c]
@@ -44,9 +46,8 @@  set line_bpno [dap_get_breakpoint_number $obj]
 
 dap_check_request_and_response "configurationDone" configurationDone
 
-if {[dap_launch $testfile] == ""} {
-    return
-}
+dap_check_response "launch response" launch $launch_id
+
 dap_wait_for_event_and_check "inferior started" thread "body reason" started
 
 dap_wait_for_event_and_check "stopped at line breakpoint" stopped \
diff --git a/gdb/testsuite/gdb.dap/ada-nested.exp b/gdb/testsuite/gdb.dap/ada-nested.exp
index 3415da3e9cbd9ebe2a6055e7289b1e4270479b68..7f28d27e365615e8880f0af1496bf2cfd1e94a6a 100644
--- a/gdb/testsuite/gdb.dap/ada-nested.exp
+++ b/gdb/testsuite/gdb.dap/ada-nested.exp
@@ -31,6 +31,8 @@  if {[dap_initialize] == ""} {
     return
 }
 
+set launch_id [dap_launch $testfile]
+
 set line [gdb_get_line_number "STOP"]
 set obj [dap_check_request_and_response "set breakpoint" \
 	     setBreakpoints \
@@ -41,9 +43,8 @@  set fn_bpno [dap_get_breakpoint_number $obj]
 
 dap_check_request_and_response "configurationDone" configurationDone
 
-if {[dap_launch $binfile] == ""} {
-    return
-}
+dap_check_response "launch response" launch $launch_id
+
 dap_wait_for_event_and_check "stopped at breakpoint" stopped \
     "body reason" breakpoint \
     "body hitBreakpointIds" $fn_bpno
diff --git a/gdb/testsuite/gdb.dap/ada-scopes.exp b/gdb/testsuite/gdb.dap/ada-scopes.exp
index 4d895a5270d7c32d10c717f8b1428d4d2c5fdd6e..ef1302e0644dfe1435f453fd72ece3548d14dff4 100644
--- a/gdb/testsuite/gdb.dap/ada-scopes.exp
+++ b/gdb/testsuite/gdb.dap/ada-scopes.exp
@@ -29,6 +29,8 @@  if {[dap_initialize] == ""} {
     return
 }
 
+set launch_id [dap_launch $testfile]
+
 set line [gdb_get_line_number "STOP"]
 set obj [dap_check_request_and_response "set breakpoint" \
 	     setBreakpoints \
@@ -39,9 +41,8 @@  set fn_bpno [dap_get_breakpoint_number $obj]
 
 dap_check_request_and_response "configurationDone" configurationDone
 
-if {[dap_launch $binfile] == ""} {
-    return
-}
+dap_check_response "launch response" launch $launch_id
+
 dap_wait_for_event_and_check "stopped at breakpoint" stopped \
     "body reason" breakpoint \
     "body hitBreakpointIds" $fn_bpno
diff --git a/gdb/testsuite/gdb.dap/args-env.exp b/gdb/testsuite/gdb.dap/args-env.exp
index d6511733348d78572a518a975bae4005a908a254..5f02b00dd5a5ae35a184230985f7f96d5563b915 100644
--- a/gdb/testsuite/gdb.dap/args-env.exp
+++ b/gdb/testsuite/gdb.dap/args-env.exp
@@ -29,6 +29,8 @@  if {[dap_initialize] == ""} {
     return
 }
 
+set launch_id [dap_launch $testfile arguments {a "b c"} env {{DEI something}}]
+
 set line [gdb_get_line_number "BREAK"]
 set obj [dap_check_request_and_response "set breakpoint by line number" \
 	     setBreakpoints \
@@ -38,9 +40,8 @@  set line_bpno [dap_get_breakpoint_number $obj]
 
 dap_check_request_and_response "configurationDone" configurationDone
 
-if {[dap_launch $testfile arguments {a "b c"} env {{DEI something}}] == ""} {
-    return
-}
+dap_check_response "launch response" launch $launch_id
+
 dap_wait_for_event_and_check "inferior started" thread "body reason" started
 
 dap_wait_for_event_and_check "stopped at line breakpoint" stopped \
diff --git a/gdb/testsuite/gdb.dap/assign.exp b/gdb/testsuite/gdb.dap/assign.exp
index 6703a97bb7a549f3fce5885348ebd4dceac0def0..6665b6a6a31f122f7b87405dfed4fbb73303b5be 100644
--- a/gdb/testsuite/gdb.dap/assign.exp
+++ b/gdb/testsuite/gdb.dap/assign.exp
@@ -36,6 +36,8 @@  save_vars GDBFLAGS {
     }
 }
 
+set launch_id [dap_launch $testfile]
+
 set line [gdb_get_line_number "STOP"]
 set obj [dap_check_request_and_response "set breakpoint by line number" \
 	     setBreakpoints \
@@ -45,9 +47,8 @@  set line_bpno [dap_get_breakpoint_number $obj]
 
 dap_check_request_and_response "configurationDone" configurationDone
 
-if {[dap_launch $testfile] == ""} {
-    return
-}
+dap_check_response "launch response" launch $launch_id
+
 dap_wait_for_event_and_check "stopped at line breakpoint" stopped \
     "body reason" breakpoint \
     "body hitBreakpointIds" $line_bpno
diff --git a/gdb/testsuite/gdb.dap/attach.exp b/gdb/testsuite/gdb.dap/attach.exp
index ce2a16c1193b2a4ac3f3519fbb7c724126ac6e01..e0cd57b2009e115af5ae80d2aeed40545ae9e308 100644
--- a/gdb/testsuite/gdb.dap/attach.exp
+++ b/gdb/testsuite/gdb.dap/attach.exp
@@ -29,17 +29,14 @@  set test_spawn_id [spawn_wait_for_attach $binfile]
 set testpid [spawn_id_get_pid $test_spawn_id]
 
 # Test that attaching works at all.
-set result [dap_attach $testpid $binfile]
-
-set found 0
-foreach ev [lindex $result 1] {
-    if {[dict get $ev type] == "event"
-	&& [dict get $ev event] == "stopped"
-	&& [dict get $ev body reason] == "attach"} {
-	set found 1
-    }
-}
-gdb_assert {$found} "saw stopped event for attach"
+set attach_id [dap_attach $testpid $binfile]
+
+dap_check_request_and_response "configurationDone" configurationDone
+
+dap_wait_for_event_and_check "stopped" stopped \
+    "body reason" attach
+
+dap_check_response "attach response" attach $attach_id
 
 dap_shutdown true
 
diff --git a/gdb/testsuite/gdb.dap/basic-dap.exp b/gdb/testsuite/gdb.dap/basic-dap.exp
index 6ef9a5b0f6e11d0ca62b2e6afe6c4931245637c9..dd785ef53644b0451d57752f3f4995db38ea595a 100644
--- a/gdb/testsuite/gdb.dap/basic-dap.exp
+++ b/gdb/testsuite/gdb.dap/basic-dap.exp
@@ -29,6 +29,8 @@  if {[dap_initialize] == ""} {
     return
 }
 
+set launch_id [dap_launch $testfile]
+
 set obj [dap_check_request_and_response "set breakpoint on two functions" \
 	     setFunctionBreakpoints \
 	     {o breakpoints [a [o name [s function_breakpoint_here]] \
@@ -86,9 +88,8 @@  gdb_assert {$new_line_bpno == $line_bpno} "re-setting kept same breakpoint numbe
 
 dap_check_request_and_response "configurationDone" configurationDone
 
-if {[dap_launch $testfile] == ""} {
-    return
-}
+dap_check_response "launch response" launch $launch_id
+
 dap_wait_for_event_and_check "inferior started" thread "body reason" started
 
 # While waiting for the stopped event, we might receive breakpoint changed
diff --git a/gdb/testsuite/gdb.dap/bt-nodebug.exp b/gdb/testsuite/gdb.dap/bt-nodebug.exp
index 550b9c5c5ac242c0449d49bf7b38140680d42683..319dcf0c583d09428467976a5e50a87268c87ce1 100644
--- a/gdb/testsuite/gdb.dap/bt-nodebug.exp
+++ b/gdb/testsuite/gdb.dap/bt-nodebug.exp
@@ -31,6 +31,8 @@  if {[dap_initialize] == ""} {
     return
 }
 
+set launch_id [dap_launch $testfile]
+
 set obj [dap_check_request_and_response "set breakpoint on inner" \
 	     setFunctionBreakpoints \
 	     {o breakpoints [a [o name [s function_breakpoint_here]]]}]
@@ -38,9 +40,8 @@  set fn_bpno [dap_get_breakpoint_number $obj]
 
 dap_check_request_and_response "configurationDone" configurationDone
 
-if {[dap_launch $testfile] == ""} {
-    return
-}
+dap_check_response "launch response" launch $launch_id
+
 dap_wait_for_event_and_check "inferior started" thread "body reason" started
 
 lassign [dap_wait_for_event_and_check "stopped at function breakpoint" stopped \
diff --git a/gdb/testsuite/gdb.dap/cancel-launch.exp b/gdb/testsuite/gdb.dap/cancel-launch.exp
new file mode 100644
index 0000000000000000000000000000000000000000..94eba7ebe513b2a2344524349af4c6e63824905e
--- /dev/null
+++ b/gdb/testsuite/gdb.dap/cancel-launch.exp
@@ -0,0 +1,56 @@ 
+# Copyright 2024 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# Test cancellation of a "launch" command.
+
+require allow_dap_tests
+
+load_lib dap-support.exp
+
+# Anything will work, we aren't going to run it.
+standard_testfile sources.c
+
+if {[build_executable ${testfile}.exp $testfile $srcfile] == -1} {
+    return
+}
+
+if {[dap_initialize] == ""} {
+    return
+}
+
+set launch_id [dap_launch $testfile]
+
+# Set a breakpoint.  This is done to ensure that the launch request is
+# definitely in the deferred state when we try to cancel it.
+set line [gdb_get_line_number "Distinguishing comment"]
+dap_check_request_and_response "set breakpoint by line number" \
+    setBreakpoints \
+    [format {o source [o path [%s]] breakpoints [a [o line [i %d]]]} \
+	 [list s $srcfile] $line]
+
+set cancel_id [dap_send_request cancel \
+		   [format {o requestId [i %d]} $launch_id]]
+
+dap_read_response cancel $cancel_id
+
+# The cancellation isn't actually processed until configurationDone is
+# sent.  While this seems fine, it's unclear if gdb should be more
+# eager here and try to cancel a deferred task before it is
+# rescheduled.
+dap_check_request_and_response "configurationDone" configurationDone
+
+dap_read_response launch $launch_id
+
+dap_shutdown
diff --git a/gdb/testsuite/gdb.dap/catch-exception.exp b/gdb/testsuite/gdb.dap/catch-exception.exp
index a1ced06cad2d108983452b391650e6f6c6c685a3..97937466c406b374fa16c33406e846ca3ea1a90f 100644
--- a/gdb/testsuite/gdb.dap/catch-exception.exp
+++ b/gdb/testsuite/gdb.dap/catch-exception.exp
@@ -29,6 +29,8 @@  if {[dap_initialize] == ""} {
     return
 }
 
+set launch_id [dap_launch $testfile]
+
 set obj [dap_check_request_and_response "set exception catchpoints" \
 	     setExceptionBreakpoints \
 	     {o filters [a [s nosuchfilter] [s assert]] \
@@ -69,9 +71,8 @@  foreach spec $bps {
 
 dap_check_request_and_response "configurationDone" configurationDone
 
-if {[dap_launch $binfile] == ""} {
-    return
-}
+dap_check_response "launch response" launch $launch_id
+
 dap_wait_for_event_and_check "stopped at first raise" stopped \
     "body reason" breakpoint \
     "body hitBreakpointIds" 2
diff --git a/gdb/testsuite/gdb.dap/children.exp b/gdb/testsuite/gdb.dap/children.exp
index f5dfe2cbb91e57183989a63b61351ec332f71d30..783fff98ce91dd342ebd03f70cb74af4cedbdf89 100644
--- a/gdb/testsuite/gdb.dap/children.exp
+++ b/gdb/testsuite/gdb.dap/children.exp
@@ -36,6 +36,8 @@  save_vars GDBFLAGS {
     }
 }
 
+set launch_id [dap_launch $testfile]
+
 set line [gdb_get_line_number "STOP"]
 set obj [dap_check_request_and_response "set breakpoint by line number" \
 	     setBreakpoints \
@@ -45,9 +47,8 @@  set line_bpno [dap_get_breakpoint_number $obj]
 
 dap_check_request_and_response "configurationDone" configurationDone
 
-if {[dap_launch $testfile] == ""} {
-    return
-}
+dap_check_response "launch response" launch $launch_id
+
 dap_wait_for_event_and_check "stopped at line breakpoint" stopped \
     "body reason" breakpoint \
     "body hitBreakpointIds" $line_bpno
diff --git a/gdb/testsuite/gdb.dap/cond-bp.exp b/gdb/testsuite/gdb.dap/cond-bp.exp
index 2bd52ba83a0d6d62b56e3616b0998ffb7f3d2f2b..28d4b8e8ff3663fca99f8cf67525a0dbf143d8ea 100644
--- a/gdb/testsuite/gdb.dap/cond-bp.exp
+++ b/gdb/testsuite/gdb.dap/cond-bp.exp
@@ -29,6 +29,8 @@  if {[dap_initialize] == ""} {
     return
 }
 
+set launch_id [dap_launch $testfile]
+
 set line [gdb_get_line_number "STOP"]
 
 # Test some breakpoint-setting failure modes.
@@ -65,9 +67,8 @@  set fn_bpno [dap_get_breakpoint_number $obj]
 
 dap_check_request_and_response "configurationDone" configurationDone
 
-if {[dap_launch $testfile] == ""} {
-    return
-}
+dap_check_response "launch response" launch $launch_id
+
 dap_wait_for_event_and_check "stopped at function breakpoint" stopped \
     "body reason" breakpoint \
     "body hitBreakpointIds" $fn_bpno
diff --git a/gdb/testsuite/gdb.dap/cwd.exp b/gdb/testsuite/gdb.dap/cwd.exp
index 6b8829965c522c7e34fea0e93566e42f07c8dcba..b3c9f9c21dea99c8bfffadb226f71d664574dac1 100644
--- a/gdb/testsuite/gdb.dap/cwd.exp
+++ b/gdb/testsuite/gdb.dap/cwd.exp
@@ -29,14 +29,14 @@  if {[dap_initialize] == ""} {
     return
 }
 
-dap_check_request_and_response "configurationDone" configurationDone
-
 # Starting the inferior will fail if the change of cwd does not work.
 set the_dir [file dirname $testfile]
 set the_file [file tail $testfile]
-if {[dap_launch $the_file cwd $the_dir stop_at_main 1] == ""} {
-    return
-}
+set launch_id [dap_launch $the_file cwd $the_dir stop_at_main 1]
+
+dap_check_request_and_response "configurationDone" configurationDone
+
+dap_check_response "launch response" launch $launch_id
 
 # We didn't explicitly set a breakpoint, so if we hit one, it worked.
 dap_wait_for_event_and_check "stopped at function breakpoint" stopped \
diff --git a/gdb/testsuite/gdb.dap/cxx-exception.exp b/gdb/testsuite/gdb.dap/cxx-exception.exp
index 13320439b6d5b9343a806ea9a4bd19cc052a3ff0..c79c5dba6a70dc0fcb8b0f83533f7eac233c974b 100644
--- a/gdb/testsuite/gdb.dap/cxx-exception.exp
+++ b/gdb/testsuite/gdb.dap/cxx-exception.exp
@@ -28,6 +28,8 @@  if {[dap_initialize] == ""} {
     return
 }
 
+set launch_id [dap_launch $testfile]
+
 set obj [dap_check_request_and_response "set exception catchpoints" \
 	     setExceptionBreakpoints \
 	     {o filters [a [s throw] [s rethrow] [s catch]]}]
@@ -49,9 +51,8 @@  foreach bp $bps {
 
 dap_check_request_and_response "configurationDone" configurationDone
 
-if {[dap_launch $testfile] == ""} {
-    return
-}
+dap_check_response "launch response" launch $launch_id
+
 dap_wait_for_event_and_check "stopped at throw" stopped \
     "body reason" breakpoint \
     "body hitBreakpointIds" 1
diff --git a/gdb/testsuite/gdb.dap/disassem.exp b/gdb/testsuite/gdb.dap/disassem.exp
index 87fb516931a4e85cb317e0a54d81aa9966ad1b97..6fdfc6358cf72baadb6d54755e5b75e14fd88f99 100644
--- a/gdb/testsuite/gdb.dap/disassem.exp
+++ b/gdb/testsuite/gdb.dap/disassem.exp
@@ -29,6 +29,8 @@  if {[dap_initialize] == ""} {
     return
 }
 
+set launch_id [dap_launch $testfile]
+
 set obj [dap_check_request_and_response "set breakpoint" \
 	     setFunctionBreakpoints \
 	     {o breakpoints [a [o name [s main]]]}]
@@ -36,9 +38,8 @@  set fn_bpno [dap_get_breakpoint_number $obj]
 
 dap_check_request_and_response "configurationDone" configurationDone
 
-if {[dap_launch $testfile] == ""} {
-    return
-}
+dap_check_response "launch response" launch $launch_id
+
 dap_wait_for_event_and_check "inferior started" thread "body reason" started
 
 dap_wait_for_event_and_check "stopped at line breakpoint" stopped \
diff --git a/gdb/testsuite/gdb.dap/frameless.exp b/gdb/testsuite/gdb.dap/frameless.exp
index 63ee521af656ae441a9a211f7af687d55d2ecf87..6963eed62c4e78912c05f0ebbb51f4a1d0f99e26 100644
--- a/gdb/testsuite/gdb.dap/frameless.exp
+++ b/gdb/testsuite/gdb.dap/frameless.exp
@@ -29,6 +29,8 @@  if {[dap_initialize] == ""} {
     return
 }
 
+set launch_id [dap_launch $testfile]
+
 set line [gdb_get_line_number "BREAK"]
 set obj [dap_check_request_and_response "set breakpoint by line number" \
 	     setBreakpoints \
@@ -38,9 +40,8 @@  set line_bpno [dap_get_breakpoint_number $obj]
 
 dap_check_request_and_response "configurationDone" configurationDone
 
-if {[dap_launch $testfile] == ""} {
-    return
-}
+dap_check_response "launch response" launch $launch_id
+
 dap_wait_for_event_and_check "inferior started" thread "body reason" started
 dap_wait_for_event_and_check "stopped at line breakpoint" stopped \
     "body reason" breakpoint \
diff --git a/gdb/testsuite/gdb.dap/global.exp b/gdb/testsuite/gdb.dap/global.exp
index 79f7f2facf570c41764ed70d2d8908ea84c90299..2704f46cfaf0c56be83a08934d53016fdc9680a3 100644
--- a/gdb/testsuite/gdb.dap/global.exp
+++ b/gdb/testsuite/gdb.dap/global.exp
@@ -27,6 +27,8 @@  if {[dap_initialize] == ""} {
     return
 }
 
+set launch_id [dap_launch $testfile]
+
 set line [gdb_get_line_number "BREAK"]
 set obj [dap_check_request_and_response "set breakpoint by line number" \
 	     setBreakpoints \
@@ -36,9 +38,8 @@  set line_bpno [dap_get_breakpoint_number $obj]
 
 dap_check_request_and_response "configurationDone" configurationDone
 
-if {[dap_launch $testfile] == ""} {
-    return
-}
+dap_check_response "launch response" launch $launch_id
+
 dap_wait_for_event_and_check "inferior started" thread "body reason" started
 
 dap_wait_for_event_and_check "stopped at line breakpoint" stopped \
diff --git a/gdb/testsuite/gdb.dap/hover.exp b/gdb/testsuite/gdb.dap/hover.exp
index 0c80650b152e3636244aba0f8a9df007b8d26cf0..f637c4a06d108bb0353e094dca58aa65fb2e29a4 100644
--- a/gdb/testsuite/gdb.dap/hover.exp
+++ b/gdb/testsuite/gdb.dap/hover.exp
@@ -29,6 +29,8 @@  if {[dap_initialize] == ""} {
     return
 }
 
+set launch_id [dap_launch $testfile]
+
 set line [gdb_get_line_number "BREAK"]
 set obj [dap_check_request_and_response "set breakpoint by line number" \
 	     setBreakpoints \
@@ -38,9 +40,8 @@  set line_bpno [dap_get_breakpoint_number $obj]
 
 dap_check_request_and_response "configurationDone" configurationDone
 
-if {[dap_launch $testfile] == ""} {
-    return
-}
+dap_check_response "launch response" launch $launch_id
+
 dap_wait_for_event_and_check "inferior started" thread "body reason" started
 
 dap_wait_for_event_and_check "stopped at breakpoint" stopped \
diff --git a/gdb/testsuite/gdb.dap/insn-bp.exp b/gdb/testsuite/gdb.dap/insn-bp.exp
index 4a4c14484e176fb9d3026462f5758e0d9103da9c..6542262b21d11722fdfc053c75387445e053857e 100644
--- a/gdb/testsuite/gdb.dap/insn-bp.exp
+++ b/gdb/testsuite/gdb.dap/insn-bp.exp
@@ -39,6 +39,8 @@  if {[dap_initialize] == ""} {
     return
 }
 
+set launch_id [dap_launch $testfile]
+
 set obj [dap_check_request_and_response "set breakpoint on main" \
 	     setFunctionBreakpoints \
 	     {o breakpoints [a [o name [s main]]]}]
@@ -56,14 +58,12 @@  gdb_assert {[dict get $bp verified] == "false"} \
 
 dap_check_request_and_response "configurationDone" configurationDone
 
-if {[dap_launch $testfile] == ""} {
-    return
-}
-
 # The event we're looking for should occur during startup, but we want
 # to leave open the possibility that it occurs when waiting for the
-# stopped event.  So, keep both event lists around and search them
+# stopped event.  So, keep all event lists around and search them
 # once below.
+lassign [dap_check_response "launch response" launch $launch_id] \
+    unused objs0
 lassign [dap_wait_for_event_and_check "inferior started" \
 	     thread "body reason" started] \
     unused objs1
@@ -72,7 +72,7 @@  lassign [dap_wait_for_event_and_check "stopped at breakpoint" stopped \
 	    "body hitBreakpointIds" $fn_bpno] unused objs2
 
 set found_bp_event 0
-foreach obj [concat $objs1 $objs2] {
+foreach obj [concat $objs0 $objs1 $objs2] {
     if { [dict get $obj "type"] != "event" } {
 	continue
     }
diff --git a/gdb/testsuite/gdb.dap/lazy-string.exp b/gdb/testsuite/gdb.dap/lazy-string.exp
index 54422209fdacbda59c9b932991733377e4b07351..d070347e760bc44b37a77a4abcb5d06599784329 100644
--- a/gdb/testsuite/gdb.dap/lazy-string.exp
+++ b/gdb/testsuite/gdb.dap/lazy-string.exp
@@ -36,6 +36,8 @@  save_vars GDBFLAGS {
     }
 }
 
+set launch_id [dap_launch $testfile]
+
 set line [gdb_get_line_number "STOP"]
 set obj [dap_check_request_and_response "set breakpoint by line number" \
 	     setBreakpoints \
@@ -45,9 +47,8 @@  set line_bpno [dap_get_breakpoint_number $obj]
 
 dap_check_request_and_response "configurationDone" configurationDone
 
-if {[dap_launch $testfile] == ""} {
-    return
-}
+dap_check_response "launch response" launch $launch_id
+
 dap_wait_for_event_and_check "stopped at line breakpoint" stopped \
     "body reason" breakpoint \
     "body hitBreakpointIds" $line_bpno
diff --git a/gdb/testsuite/gdb.dap/log-message.exp b/gdb/testsuite/gdb.dap/log-message.exp
index e966b962a7128d43943c8d2aca420eb9603ded75..a0070afdb2ff28e94afd2e2f6a02cfe71d3a9211 100644
--- a/gdb/testsuite/gdb.dap/log-message.exp
+++ b/gdb/testsuite/gdb.dap/log-message.exp
@@ -29,6 +29,8 @@  if {[dap_initialize] == ""} {
     return
 }
 
+set launch_id [dap_launch $testfile]
+
 set line [gdb_get_line_number "HERE"]
 set obj [dap_check_request_and_response "set breakpoint" \
 	     setBreakpoints \
@@ -40,9 +42,8 @@  set fn_bpno [dap_get_breakpoint_number $obj]
 
 dap_check_request_and_response "configurationDone" configurationDone
 
-if {[dap_launch $testfile] == ""} {
-    return
-}
+dap_check_response "launch response" launch $launch_id
+
 dap_wait_for_event_and_check "inferior started" thread "body reason" started
 
 dap_wait_for_event_and_check "logging output" output \
diff --git a/gdb/testsuite/gdb.dap/memory.exp b/gdb/testsuite/gdb.dap/memory.exp
index c4e4fb3238b55f194a6041b6da8c427e47a58b4b..89ae8bb7b1a3ba681281906900ac2df3cfd6bd39 100644
--- a/gdb/testsuite/gdb.dap/memory.exp
+++ b/gdb/testsuite/gdb.dap/memory.exp
@@ -37,6 +37,8 @@  save_vars { env(ASAN_OPTIONS) env(TSAN_OPTIONS) } {
     }
 }
 
+set launch_id [dap_launch $testfile]
+
 set line [gdb_get_line_number "BREAK"]
 set obj [dap_check_request_and_response "set breakpoint by line number" \
 	     setBreakpoints \
@@ -46,9 +48,8 @@  set line_bpno [dap_get_breakpoint_number $obj]
 
 dap_check_request_and_response "configurationDone" configurationDone
 
-if {[dap_launch $testfile] == ""} {
-    return
-}
+dap_check_response "launch response" launch $launch_id
+
 dap_wait_for_event_and_check "inferior started" thread "body reason" started
 
 dap_wait_for_event_and_check "stopped at line breakpoint" stopped \
diff --git a/gdb/testsuite/gdb.dap/modules.exp b/gdb/testsuite/gdb.dap/modules.exp
index 87cebda7131f2ac829f1c3c5479ce87ec7eff54a..2e41679e691fe3252bd0391cc63cac03b661f0b6 100644
--- a/gdb/testsuite/gdb.dap/modules.exp
+++ b/gdb/testsuite/gdb.dap/modules.exp
@@ -42,6 +42,8 @@  if {[dap_initialize] == ""} {
     return
 }
 
+set launch_id [dap_launch $testfile]
+
 set obj [dap_check_request_and_response "set breakpoint on stop function" \
 	     setFunctionBreakpoints \
 	     {o breakpoints [a [o name [s stop]]]}]
@@ -49,9 +51,8 @@  set fn_bpno [dap_get_breakpoint_number $obj]
 
 dap_check_request_and_response "configurationDone" configurationDone
 
-if {[dap_launch $testfile] == ""} {
-    return
-}
+dap_check_response "launch response" launch $launch_id
+
 dap_wait_for_event_and_check "stopped at function breakpoint" stopped \
     "body reason" breakpoint \
     "body hitBreakpointIds" $fn_bpno
diff --git a/gdb/testsuite/gdb.dap/pause.exp b/gdb/testsuite/gdb.dap/pause.exp
index 9038d0f037fcc7fa16fc25b79a1f5fbd3a86c780..c74fea3ebfd95496c4763d91646240c2c220f1c8 100644
--- a/gdb/testsuite/gdb.dap/pause.exp
+++ b/gdb/testsuite/gdb.dap/pause.exp
@@ -29,6 +29,8 @@  if {[dap_initialize] == ""} {
     return
 }
 
+set launch_id [dap_launch $testfile]
+
 # Set a conditional breakpoint that will never fire.  This is done to
 # test the state-tracking in events -- an inferior call from a
 # breakpoint condition should not cause any sort of stop or continue
@@ -43,9 +45,8 @@  dap_check_request_and_response "set conditional breakpoint" \
 
 dap_check_request_and_response "configurationDone" configurationDone
 
-if {[dap_launch $testfile] == ""} {
-    return
-}
+dap_check_response "launch response" launch $launch_id
+
 dap_wait_for_event_and_check "process event generated" process \
     "body startMethod" process
 dap_wait_for_event_and_check "inferior started" thread "body reason" started
diff --git a/gdb/testsuite/gdb.dap/ptrref.exp b/gdb/testsuite/gdb.dap/ptrref.exp
index 236ffae12d54f02b1dfc1a2ad74a0ff69ad6ab39..2a972aca0455b52fdaba5924bd552bf1fd09aec5 100644
--- a/gdb/testsuite/gdb.dap/ptrref.exp
+++ b/gdb/testsuite/gdb.dap/ptrref.exp
@@ -27,6 +27,8 @@  if {[dap_initialize] == ""} {
     return
 }
 
+set launch_id [dap_launch $testfile]
+
 set line [gdb_get_line_number "BREAK"]
 set obj [dap_check_request_and_response "set breakpoint by line number" \
 	     setBreakpoints \
@@ -36,9 +38,8 @@  set line_bpno [dap_get_breakpoint_number $obj]
 
 dap_check_request_and_response "configurationDone" configurationDone
 
-if {[dap_launch $testfile] == ""} {
-    return
-}
+dap_check_response "launch response" launch $launch_id
+
 dap_wait_for_event_and_check "inferior started" thread "body reason" started
 
 dap_wait_for_event_and_check "stopped at line breakpoint" stopped \
diff --git a/gdb/testsuite/gdb.dap/remote-dap.exp b/gdb/testsuite/gdb.dap/remote-dap.exp
index 5c28a045e7032433e0389c32964efa31ddc6078e..bca66dd10550a7349e7b5cf0143959e01555e04f 100644
--- a/gdb/testsuite/gdb.dap/remote-dap.exp
+++ b/gdb/testsuite/gdb.dap/remote-dap.exp
@@ -42,8 +42,12 @@  lassign [gdbserver_start "" $target_exec] protocol port
 gdb_assert {$protocol == "remote"}
 
 # We just want to test that attaching works at all.
-if {[dap_target_remote $port] != ""} {
-    dap_shutdown true
-}
+set attach_id [dap_target_remote $port]
+
+dap_check_request_and_response "configurationDone" configurationDone
+
+dap_check_response "attach response" attach $attach_id
+
+dap_shutdown true
 
 close_gdbserver
diff --git a/gdb/testsuite/gdb.dap/rust-slices.exp b/gdb/testsuite/gdb.dap/rust-slices.exp
index 4af8c11e6848372621db80334250f7d064b6f14c..1ed640a9af179be01ce552af0dc5b0dfbdaf827d 100644
--- a/gdb/testsuite/gdb.dap/rust-slices.exp
+++ b/gdb/testsuite/gdb.dap/rust-slices.exp
@@ -32,6 +32,8 @@  if {[dap_initialize] == ""} {
     return
 }
 
+set launch_id [dap_launch $testfile]
+
 set line [gdb_get_line_number "STOP"]
 set obj [dap_check_request_and_response "set breakpoint by line number" \
 	     setBreakpoints \
@@ -41,9 +43,8 @@  set line_bpno [dap_get_breakpoint_number $obj]
 
 dap_check_request_and_response "configurationDone" configurationDone
 
-if {[dap_launch $testfile] == ""} {
-    return
-}
+dap_check_response "launch response" launch $launch_id
+
 dap_wait_for_event_and_check "inferior started" thread "body reason" started
 
 dap_wait_for_event_and_check "stopped at line breakpoint" stopped \
diff --git a/gdb/testsuite/gdb.dap/scopes.exp b/gdb/testsuite/gdb.dap/scopes.exp
index aa3bb688c0e85f0492b578b40f3d235a1a4fb95f..a8a0c01690eda1758bdf94763169f85c574ddb2f 100644
--- a/gdb/testsuite/gdb.dap/scopes.exp
+++ b/gdb/testsuite/gdb.dap/scopes.exp
@@ -29,6 +29,8 @@  if {[dap_initialize] == ""} {
     return
 }
 
+set launch_id [dap_launch $testfile]
+
 set line [gdb_get_line_number "BREAK"]
 set obj [dap_check_request_and_response "set breakpoint by line number" \
 	     setBreakpoints \
@@ -38,9 +40,8 @@  set line_bpno [dap_get_breakpoint_number $obj]
 
 dap_check_request_and_response "configurationDone" configurationDone
 
-if {[dap_launch $testfile] == ""} {
-    return
-}
+dap_check_response "launch response" launch $launch_id
+
 dap_wait_for_event_and_check "inferior started" thread "body reason" started
 
 dap_wait_for_event_and_check "stopped at line breakpoint" stopped \
diff --git a/gdb/testsuite/gdb.dap/sources.exp b/gdb/testsuite/gdb.dap/sources.exp
index ee853cc68aab636bb22a93ee53afe4b4ca9bfd45..305a27c72616d11c4bd4d94a4049114d486a0c30 100644
--- a/gdb/testsuite/gdb.dap/sources.exp
+++ b/gdb/testsuite/gdb.dap/sources.exp
@@ -29,14 +29,16 @@  if {[dap_initialize] == ""} {
     return
 }
 
-if {[dap_launch $testfile stop_at_main 1] == ""} {
-    return
-}
+set launch_id [dap_launch $testfile stop_at_main 1]
 
-proc do_tests {} {
-    dap_wait_for_event_and_check "stopped at function breakpoint" stopped \
-	"body reason" breakpoint
+dap_check_request_and_response "configurationDone" configurationDone
 
+dap_check_response "launch response" launch $launch_id
+
+dap_wait_for_event_and_check "stopped at function breakpoint" stopped \
+    "body reason" breakpoint
+
+proc do_tests {} {
     set obj [dap_check_request_and_response loadedSources loadedSources]
     if { $obj == "" } {
 	return
diff --git a/gdb/testsuite/gdb.dap/stack-format.exp b/gdb/testsuite/gdb.dap/stack-format.exp
index b81183a016ebb8b1aa0bbf571d602ab4de26ebab..4056e1f1f68f96f34685226269757ad2b04854b9 100644
--- a/gdb/testsuite/gdb.dap/stack-format.exp
+++ b/gdb/testsuite/gdb.dap/stack-format.exp
@@ -36,6 +36,8 @@  save_vars GDBFLAGS {
     }
 }
 
+set launch_id [dap_launch $testfile]
+
 set line [gdb_get_line_number "BREAK"]
 set obj [dap_check_request_and_response "set breakpoint by line number" \
 	     setBreakpoints \
@@ -45,9 +47,8 @@  set line_bpno [dap_get_breakpoint_number $obj]
 
 dap_check_request_and_response "configurationDone" configurationDone
 
-if {[dap_launch $testfile] == ""} {
-    return
-}
+dap_check_response "launch response" launch $launch_id
+
 dap_wait_for_event_and_check "stopped at line breakpoint" stopped \
     "body reason" breakpoint \
     "body hitBreakpointIds" $line_bpno
diff --git a/gdb/testsuite/gdb.dap/step-out.exp b/gdb/testsuite/gdb.dap/step-out.exp
index 193264fa2504238d80b430bf511ed31b989f1842..cfe730e03f16bcc2b12a40906966bf908b7526ef 100644
--- a/gdb/testsuite/gdb.dap/step-out.exp
+++ b/gdb/testsuite/gdb.dap/step-out.exp
@@ -29,6 +29,8 @@  if {[dap_initialize] == ""} {
     return
 }
 
+set launch_id [dap_launch $testfile]
+
 set obj [dap_check_request_and_response "set breakpoint on function" \
 	     setFunctionBreakpoints \
 	     {o breakpoints [a [o name [s function_breakpoint_here]]]}]
@@ -36,9 +38,8 @@  set fn_bpno [dap_get_breakpoint_number $obj]
 
 dap_check_request_and_response "configurationDone" configurationDone
 
-if {[dap_launch $testfile] == ""} {
-    return
-}
+dap_check_response "launch response" launch $launch_id
+
 dap_wait_for_event_and_check "inferior started" thread "body reason" started
 
 dap_wait_for_event_and_check "stopped at function breakpoint" stopped \
diff --git a/gdb/testsuite/gdb.dap/stop-at-main.exp b/gdb/testsuite/gdb.dap/stop-at-main.exp
index 4c3e57a23e4407443f72c43d51be49c4e5a06607..52b94f40c678686f120b15482efe73d508f0ed3b 100644
--- a/gdb/testsuite/gdb.dap/stop-at-main.exp
+++ b/gdb/testsuite/gdb.dap/stop-at-main.exp
@@ -29,11 +29,12 @@  if {[dap_initialize] == ""} {
     return
 }
 
+set launch_id [dap_launch $testfile stop_at_main 1]
+
 dap_check_request_and_response "start inferior" configurationDone
 
-if {[dap_launch $testfile stop_at_main 1] == ""} {
-    return
-}
+dap_check_response "launch response" launch $launch_id
+
 # We didn't explicitly set a breakpoint, so if we hit one, it worked.
 dap_wait_for_event_and_check "stopped at function breakpoint" stopped \
     "body reason" breakpoint
diff --git a/gdb/testsuite/gdb.dap/terminate.exp b/gdb/testsuite/gdb.dap/terminate.exp
index 90d01945a64cec480258612694dc4c84ab02ebe9..9872351a292f6d7e22d123e3b5249326342da4f0 100644
--- a/gdb/testsuite/gdb.dap/terminate.exp
+++ b/gdb/testsuite/gdb.dap/terminate.exp
@@ -31,11 +31,12 @@  if {[dap_initialize] == ""} {
     return
 }
 
+set launch_id [dap_launch $testfile]
+
 dap_check_request_and_response "start inferior" configurationDone
 
-if {[dap_launch $testfile] == ""} {
-    return
-}
+dap_check_response "launch response" launch $launch_id
+
 dap_wait_for_event_and_check "inferior started" thread "body reason" started
 
 dap_wait_for_event_and_check "terminated event" terminated
diff --git a/gdb/testsuite/lib/dap-support.exp b/gdb/testsuite/lib/dap-support.exp
index 61355b56fee70f43b3e637dc4eed4f2c09e2fac2..0e481ac083d073a5f1491d2d59ce1f323b601e4e 100644
--- a/gdb/testsuite/lib/dap-support.exp
+++ b/gdb/testsuite/lib/dap-support.exp
@@ -248,10 +248,10 @@  proc dap_request_and_response {command {obj {}}} {
     return [dap_read_response $command $seq]
 }
 
-# Like dap_request_and_response, but also checks that the response
-# indicates success.  NAME is used to issue a test result.
-proc dap_check_request_and_response {name command {obj {}}} {
-    set response_and_events [dap_request_and_response $command $obj]
+# Wait for a response to the given request, and issue a pass/fail.
+# Returns the response and events like dap_request_and_response.
+proc dap_check_response {name cmd request} {
+    set response_and_events [dap_read_response $cmd $request]
     set response [lindex $response_and_events 0]
     if {[dict get $response success] != "true"} {
 	verbose "request failure: $response"
@@ -262,6 +262,13 @@  proc dap_check_request_and_response {name command {obj {}}} {
     return $response_and_events
 }
 
+# Like dap_request_and_response, but also checks that the response
+# indicates success.  NAME is used to issue a test result.
+proc dap_check_request_and_response {name command {obj {}}} {
+    set seq [dap_send_request $command $obj]
+    return [dap_check_response $name $command $seq]
+}
+
 # Start gdb, send a DAP initialization request and return the
 # response.  This approach lets the caller check the feature list, if
 # desired.  Returns the empty string on failure.  NAME is used as the
@@ -278,10 +285,9 @@  proc dap_initialize {{name "initialize"}} {
 }
 
 # Send a launch request specifying FILE as the program to use for the
-# inferior.  Returns the empty string on failure, or the response
-# object from the launch request.  If specified, ARGS is a dictionary
-# of key-value pairs, each passed to the launch request.  Valid keys
-# are:
+# inferior.  Returns the request ID.  If specified, ARGS is a
+# dictionary of key-value pairs, each passed to the launch request.
+# Valid keys are:
 #
 # * arguments - value is a list of strings passed as command-line
 #   arguments to the inferior
@@ -334,12 +340,12 @@  proc dap_launch {file {args {}}} {
 	}
     }
 
-    return [dap_check_request_and_response "startup - launch" launch $params]
+    return [dap_send_request launch $params]
 }
 
 # Start gdb, send a DAP initialize request, and then an attach request
 # specifying PID as the inferior process ID.  Returns the empty string
-# on failure, or the response object from the attach request.
+# on failure, or the attach request sequence ID.
 proc dap_attach {pid {prog ""}} {
     if {[dap_initialize "startup - initialize"] == ""} {
 	return ""
@@ -350,18 +356,17 @@  proc dap_attach {pid {prog ""}} {
 	append args [format { program [s %s]} $prog]
     }
 
-    return [dap_check_request_and_response "startup - attach" attach $args]
+    return [dap_send_request attach $args]
 }
 
 # Start gdb, send a DAP initialize request, and then an attach request
 # specifying TARGET as the remote target.  Returns the empty string on
-# failure, or the response object from the attach request.
+# failure, or the attach request sequence ID.
 proc dap_target_remote {target} {
     if {[dap_initialize "startup - initialize"] == ""} {
 	return ""
     }
-    return [dap_check_request_and_response "startup - target" attach \
-		[format {o target [s %s]} $target]]
+    return [dap_send_request attach [format {o target [s %s]} $target]]
 }
 
 # Read the most recent DAP log file and check it for exceptions.