[Lldb-commits] [lldb] [lldb-dap] Refactoring lldb-dap port listening mode to allow multiple connections. (PR #116392)

John Harrison via lldb-commits lldb-commits at lists.llvm.org
Thu Feb 6 13:21:15 PST 2025


https://github.com/ashgti updated https://github.com/llvm/llvm-project/pull/116392

>From 88a8522f1b29b2ff392974322acdb722b7e00b70 Mon Sep 17 00:00:00 2001
From: John Harrison <harjohn at google.com>
Date: Tue, 28 Jan 2025 12:39:38 -0800
Subject: [PATCH 1/4] [lldb-dap] Refactoring lldb-dap port listening mode to
 allow multiple connections.

This adjusts the lldb-dap listening mode to accept multiple clients.
Each client initializes a new instance of DAP and an associated `lldb::SBDebugger` instance.

The listening mode is configured with the `--connection` option and supports listening on a port or a unix socket on supported platforms.
---
 .../test/tools/lldb-dap/dap_server.py         |  74 ++++-
 .../test/tools/lldb-dap/lldbdap_testcase.py   |   6 +-
 lldb/test/API/tools/lldb-dap/server/Makefile  |   3 +
 .../tools/lldb-dap/server/TestDAP_server.py   |  77 +++++
 lldb/test/API/tools/lldb-dap/server/main.c    |  10 +
 lldb/test/Shell/DAP/TestOptions.test          |   4 +-
 lldb/tools/lldb-dap/DAP.cpp                   |  18 +-
 lldb/tools/lldb-dap/DAP.h                     |   6 +-
 lldb/tools/lldb-dap/DAPForward.h              |   2 +
 lldb/tools/lldb-dap/Options.td                |  22 +-
 lldb/tools/lldb-dap/lldb-dap.cpp              | 283 +++++++++++-------
 11 files changed, 358 insertions(+), 147 deletions(-)
 create mode 100644 lldb/test/API/tools/lldb-dap/server/Makefile
 create mode 100644 lldb/test/API/tools/lldb-dap/server/TestDAP_server.py
 create mode 100644 lldb/test/API/tools/lldb-dap/server/main.c

diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
index c29992ce9c7848e..6d765e10236733a 100644
--- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
+++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
@@ -903,7 +903,7 @@ def request_setBreakpoints(self, file_path, line_array, data=None):
             "sourceModified": False,
         }
         if line_array is not None:
-            args_dict["lines"] = "%s" % line_array
+            args_dict["lines"] = line_array
             breakpoints = []
             for i, line in enumerate(line_array):
                 breakpoint_data = None
@@ -1150,11 +1150,12 @@ def request_setInstructionBreakpoints(self, memory_reference=[]):
         }
         return self.send_recv(command_dict)
 
+
 class DebugAdaptorServer(DebugCommunication):
     def __init__(
         self,
         executable=None,
-        port=None,
+        connection=None,
         init_commands=[],
         log_file=None,
         env=None,
@@ -1167,21 +1168,62 @@ def __init__(
 
             if log_file:
                 adaptor_env["LLDBDAP_LOG"] = log_file
+            args = [executable]
+
+            if connection is not None:
+                args.append("--connection")
+                args.append(connection)
+
             self.process = subprocess.Popen(
-                [executable],
+                args,
                 stdin=subprocess.PIPE,
                 stdout=subprocess.PIPE,
                 stderr=subprocess.PIPE,
                 env=adaptor_env,
             )
+
+        if connection is not None:
+            # If the process was also launched, parse the connection from the
+            # resolved connection. For example, if the connection
+            # `connection://localhost:0` was specified then the OS would pick a
+            # random port for listening and lldb-dap would print the listening
+            # port to stdout.
+            if self.process is not None:
+                # lldb-dap will print the listening address once the listener is
+                # made to stdout. The listener is formatted like
+                # `connection://host:port` or `unix-connection:///path`.
+                expected_prefix = "Listening for: "
+                out = self.process.stdout.readline().decode()
+                if not out.startswith(expected_prefix):
+                    self.process.kill()
+                    raise ValueError(
+                        "lldb-dap failed to print listening address, expected '{}', got '{}'".format(
+                            expected_prefix, out
+                        )
+                    )
+
+                # If the listener expanded into multiple addresses, use the first.
+                connection = (
+                    out.removeprefix(expected_prefix).rstrip("\r\n").split(",", 1)[0]
+                )
+
+            scheme, address = connection.split("://")
+            if scheme == "unix-connect":  # unix-connect:///path
+                s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+                s.connect(address)
+            elif scheme == "connection":  # connection://[host]:port
+                host, port = address.rsplit(":", 1)
+                # create_connection with try both ipv4 and ipv6.
+                s = socket.create_connection((host.strip("[]"), int(port)))
+            else:
+                raise ValueError("invalid connection: {}".format(connection))
             DebugCommunication.__init__(
-                self, self.process.stdout, self.process.stdin, init_commands, log_file
+                self, s.makefile("rb"), s.makefile("wb"), init_commands, log_file
             )
-        elif port is not None:
-            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-            s.connect(("127.0.0.1", port))
+            self.connection = connection
+        else:
             DebugCommunication.__init__(
-                self, s.makefile("r"), s.makefile("w"), init_commands
+                self, self.process.stdout, self.process.stdin, init_commands, log_file
             )
 
     def get_pid(self):
@@ -1349,10 +1391,9 @@ def main():
     )
 
     parser.add_option(
-        "--port",
-        type="int",
-        dest="port",
-        help="Attach a socket to a port instead of using STDIN for VSCode",
+        "--connection",
+        dest="connection",
+        help="Attach a socket connection of using STDIN for VSCode",
         default=None,
     )
 
@@ -1498,15 +1539,16 @@ def main():
 
     (options, args) = parser.parse_args(sys.argv[1:])
 
-    if options.vscode_path is None and options.port is None:
+    if options.vscode_path is None and options.connection is None:
         print(
             "error: must either specify a path to a Visual Studio Code "
             "Debug Adaptor vscode executable path using the --vscode "
-            "option, or a port to attach to for an existing lldb-dap "
-            "using the --port option"
+            "option, or using the --connection option"
         )
         return
-    dbg = DebugAdaptorServer(executable=options.vscode_path, port=options.port)
+    dbg = DebugAdaptorServer(
+        executable=options.vscode_path, connection=options.connection
+    )
     if options.debug:
         raw_input('Waiting for debugger to attach pid "%i"' % (dbg.get_pid()))
     if options.replay:
diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py
index a25466f07fa557f..015c613956d86b1 100644
--- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py
+++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py
@@ -1,5 +1,6 @@
 import os
 import time
+import subprocess
 
 import dap_server
 from lldbsuite.test.lldbtest import *
@@ -10,10 +11,10 @@
 class DAPTestCaseBase(TestBase):
     # set timeout based on whether ASAN was enabled or not. Increase
     # timeout by a factor of 10 if ASAN is enabled.
-    timeoutval = 10 * (10 if ('ASAN_OPTIONS' in os.environ) else 1)
+    timeoutval = 10 * (10 if ("ASAN_OPTIONS" in os.environ) else 1)
     NO_DEBUG_INFO_TESTCASE = True
 
-    def create_debug_adaptor(self, lldbDAPEnv=None):
+    def create_debug_adaptor(self, lldbDAPEnv=None, connection=None):
         """Create the Visual Studio Code debug adaptor"""
         self.assertTrue(
             is_exe(self.lldbDAPExec), "lldb-dap must exist and be executable"
@@ -21,6 +22,7 @@ def create_debug_adaptor(self, lldbDAPEnv=None):
         log_file_path = self.getBuildArtifact("dap.txt")
         self.dap_server = dap_server.DebugAdaptorServer(
             executable=self.lldbDAPExec,
+            connection=connection,
             init_commands=self.setUpCommands(),
             log_file=log_file_path,
             env=lldbDAPEnv,
diff --git a/lldb/test/API/tools/lldb-dap/server/Makefile b/lldb/test/API/tools/lldb-dap/server/Makefile
new file mode 100644
index 000000000000000..451278a0946ef2b
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/server/Makefile
@@ -0,0 +1,3 @@
+C_SOURCES := main.c
+
+include Makefile.rules
\ No newline at end of file
diff --git a/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py b/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py
new file mode 100644
index 000000000000000..9597f73b0659bee
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py
@@ -0,0 +1,77 @@
+"""
+Test lldb-dap server integration.
+"""
+
+import os
+import signal
+import tempfile
+
+import dap_server
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+import lldbdap_testcase
+
+
+class TestDAP_server(lldbdap_testcase.DAPTestCaseBase):
+    def start_server(self, connection):
+        log_file_path = self.getBuildArtifact("dap.txt")
+        server = dap_server.DebugAdaptorServer(
+            executable=self.lldbDAPExec,
+            connection=connection,
+            init_commands=self.setUpCommands(),
+            log_file=log_file_path,
+        )
+
+        def cleanup():
+            server.terminate()
+
+        self.addTearDownHook(cleanup)
+
+        return server
+
+    def run_debug_session(self, connection, name):
+        self.dap_server = dap_server.DebugAdaptorServer(
+            connection=connection,
+        )
+        program = self.getBuildArtifact("a.out")
+        source = "main.c"
+        breakpoint_line = line_number(source, "// breakpoint")
+
+        self.launch(
+            program,
+            args=[name],
+            disconnectAutomatically=False,
+        )
+        self.set_source_breakpoints(source, [breakpoint_line])
+        self.continue_to_next_stop()
+        self.continue_to_exit()
+        output = self.get_stdout()
+        self.assertEqual(output, f"Hello {name}!\r\n")
+        self.dap_server.request_disconnect()
+
+    def test_server_port(self):
+        """
+        Test launching a binary with a lldb-dap in server mode on a specific port.
+        """
+        self.build()
+        server = self.start_server(connection="tcp://localhost:0")
+        self.run_debug_session(server.connection, "Alice")
+        self.run_debug_session(server.connection, "Bob")
+
+    @skipIfWindows
+    def test_server_unix_socket(self):
+        """
+        Test launching a binary with a lldb-dap in server mode on a unix socket.
+        """
+        dir = tempfile.gettempdir()
+        name = dir + "/dap-connection-" + str(os.getpid())
+
+        def cleanup():
+            os.unlink(name)
+
+        self.addTearDownHook(cleanup)
+
+        self.build()
+        server = self.start_server(connection="unix://" + name)
+        self.run_debug_session(server.connection, "Alice")
+        self.run_debug_session(server.connection, "Bob")
diff --git a/lldb/test/API/tools/lldb-dap/server/main.c b/lldb/test/API/tools/lldb-dap/server/main.c
new file mode 100644
index 000000000000000..446ae82532af57e
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/server/main.c
@@ -0,0 +1,10 @@
+#include <stdio.h>
+
+int main(int argc, char const *argv[]) {
+  if (argc == 2) { // breakpoint 1
+    printf("Hello %s!\n", argv[1]);
+  } else {
+    printf("Hello World!\n");
+  }
+  return 0;
+}
diff --git a/lldb/test/Shell/DAP/TestOptions.test b/lldb/test/Shell/DAP/TestOptions.test
index e37e9116e3cddb5..d290cdae590fd67 100644
--- a/lldb/test/Shell/DAP/TestOptions.test
+++ b/lldb/test/Shell/DAP/TestOptions.test
@@ -1,8 +1,8 @@
 # RUN: lldb-dap --help | FileCheck %s
+# CHECK: --connection
 # CHECK: -g
 # CHECK: --help
 # CHECK: -h
-# CHECK: --port
-# CHECK: -p
+# CHECK: --repl-mode
 # CHECK: --wait-for-debugger
 
diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp
index a67abe582abd40c..11a4db5c7f0f48a 100644
--- a/lldb/tools/lldb-dap/DAP.cpp
+++ b/lldb/tools/lldb-dap/DAP.cpp
@@ -17,7 +17,6 @@
 #include "lldb/API/SBListener.h"
 #include "lldb/API/SBProcess.h"
 #include "lldb/API/SBStream.h"
-#include "lldb/Host/FileSystem.h"
 #include "lldb/Utility/Status.h"
 #include "lldb/lldb-defines.h"
 #include "lldb/lldb-enumerations.h"
@@ -58,12 +57,13 @@ const char DEV_NULL[] = "/dev/null";
 
 namespace lldb_dap {
 
-DAP::DAP(llvm::StringRef path, std::ofstream *log, ReplMode repl_mode,
-         StreamDescriptor input, StreamDescriptor output)
-    : debug_adaptor_path(path), log(log), input(std::move(input)),
+DAP::DAP(std::string name, llvm::StringRef path, std::ofstream *log,
+         StreamDescriptor input, StreamDescriptor output, ReplMode repl_mode,
+         std::vector<std::string> pre_init_commands)
+    : name(name), debug_adaptor_path(path), log(log), input(std::move(input)),
       output(std::move(output)), broadcaster("lldb-dap"),
-      exception_breakpoints(), focus_tid(LLDB_INVALID_THREAD_ID),
-      stop_at_entry(false), is_attach(false),
+      exception_breakpoints(), pre_init_commands(pre_init_commands),
+      focus_tid(LLDB_INVALID_THREAD_ID), stop_at_entry(false), is_attach(false),
       enable_auto_variable_summaries(false),
       enable_synthetic_child_debugging(false),
       display_extended_backtrace(false),
@@ -249,7 +249,8 @@ void DAP::SendJSON(const llvm::json::Value &json) {
   if (log) {
     auto now = std::chrono::duration<double>(
         std::chrono::system_clock::now().time_since_epoch());
-    *log << llvm::formatv("{0:f9} <-- ", now.count()).str() << std::endl
+    *log << llvm::formatv("{0:f9} {1} <-- ", now.count(), name).str()
+         << std::endl
          << "Content-Length: " << json_str.size() << "\r\n\r\n"
          << llvm::formatv("{0:2}", json).str() << std::endl;
   }
@@ -279,7 +280,8 @@ std::string DAP::ReadJSON() {
   if (log) {
     auto now = std::chrono::duration<double>(
         std::chrono::system_clock::now().time_since_epoch());
-    *log << llvm::formatv("{0:f9} --> ", now.count()).str() << std::endl
+    *log << llvm::formatv("{0:f9} {1} --> ", now.count(), name).str()
+         << std::endl
          << "Content-Length: " << length << "\r\n\r\n";
   }
   return json_str;
diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h
index 846300cb945b0d1..7c28dbf801179fa 100644
--- a/lldb/tools/lldb-dap/DAP.h
+++ b/lldb/tools/lldb-dap/DAP.h
@@ -139,6 +139,7 @@ struct SendEventRequestHandler : public lldb::SBCommandPluginInterface {
 };
 
 struct DAP {
+  std::string name;
   llvm::StringRef debug_adaptor_path;
   std::ofstream *log;
   InputStream input;
@@ -203,8 +204,9 @@ struct DAP {
   // will contain that expression.
   std::string last_nonempty_var_expression;
 
-  DAP(llvm::StringRef path, std::ofstream *log, ReplMode repl_mode,
-      StreamDescriptor input, StreamDescriptor output);
+  DAP(std::string name, llvm::StringRef path, std::ofstream *log,
+      StreamDescriptor input, StreamDescriptor output, ReplMode repl_mode,
+      std::vector<std::string> pre_init_commands);
   ~DAP();
   DAP(const DAP &rhs) = delete;
   void operator=(const DAP &rhs) = delete;
diff --git a/lldb/tools/lldb-dap/DAPForward.h b/lldb/tools/lldb-dap/DAPForward.h
index 0196d83dcd6a91b..15e286f3d08dc1e 100644
--- a/lldb/tools/lldb-dap/DAPForward.h
+++ b/lldb/tools/lldb-dap/DAPForward.h
@@ -11,6 +11,8 @@
 
 // IWYU pragma: begin_exports
 
+#include "lldb/lldb-forward.h"
+
 namespace lldb_dap {
 struct BreakpointBase;
 struct ExceptionBreakpoint;
diff --git a/lldb/tools/lldb-dap/Options.td b/lldb/tools/lldb-dap/Options.td
index d7b4a065abec010..97a6ec118c47b58 100644
--- a/lldb/tools/lldb-dap/Options.td
+++ b/lldb/tools/lldb-dap/Options.td
@@ -17,12 +17,13 @@ def: Flag<["-"], "g">,
   Alias<wait_for_debugger>,
   HelpText<"Alias for --wait-for-debugger">;
 
-def port: S<"port">,
-  MetaVarName<"<port>">,
-  HelpText<"Communicate with the lldb-dap tool over the defined port.">;
-def: Separate<["-"], "p">,
-  Alias<port>,
-  HelpText<"Alias for --port">;
+def connection
+    : S<"connection">,
+      MetaVarName<"<connection>">,
+      HelpText<
+          "Communicate with the lldb-dap tool over the specified connection. "
+          "Connections are specified like 'tcp://[host]:port' or "
+          "'unix:///path'.">;
 
 def launch_target: S<"launch-target">,
   MetaVarName<"<target>">,
@@ -40,9 +41,12 @@ def debugger_pid: S<"debugger-pid">,
   HelpText<"The PID of the lldb-dap instance that sent the launchInTerminal "
     "request when using --launch-target.">;
 
-def repl_mode: S<"repl-mode">,
-  MetaVarName<"<mode>">,
-  HelpText<"The mode for handling repl evaluation requests, supported modes: variable, command, auto.">;
+def repl_mode
+    : S<"repl-mode">,
+      MetaVarName<"<mode>">,
+      HelpText<
+          "The mode for handling repl evaluation requests, supported modes: "
+          "variable, command, auto.">;
 
 def pre_init_command: S<"pre-init-command">,
   MetaVarName<"<command>">,
diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp
index 9e0e7f21ce4fc7a..2c7fdb0f0d3cfb2 100644
--- a/lldb/tools/lldb-dap/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/lldb-dap.cpp
@@ -14,45 +14,55 @@
 #include "Watchpoint.h"
 #include "lldb/API/SBDeclaration.h"
 #include "lldb/API/SBEvent.h"
+#include "lldb/API/SBFile.h"
 #include "lldb/API/SBInstruction.h"
 #include "lldb/API/SBListener.h"
 #include "lldb/API/SBMemoryRegionInfo.h"
 #include "lldb/API/SBStream.h"
 #include "lldb/API/SBStringList.h"
 #include "lldb/Host/Config.h"
+#include "lldb/Host/Socket.h"
+#include "lldb/Utility/Status.h"
+#include "lldb/Utility/UriParser.h"
+#include "lldb/lldb-forward.h"
 #include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/DenseMap.h"
 #include "llvm/ADT/DenseSet.h"
 #include "llvm/ADT/ScopeExit.h"
 #include "llvm/ADT/StringExtras.h"
+#include "llvm/ADT/StringRef.h"
 #include "llvm/Option/Arg.h"
 #include "llvm/Option/ArgList.h"
 #include "llvm/Option/OptTable.h"
 #include "llvm/Option/Option.h"
 #include "llvm/Support/Base64.h"
-#include "llvm/Support/Errno.h"
+#include "llvm/Support/Error.h"
 #include "llvm/Support/FileSystem.h"
 #include "llvm/Support/InitLLVM.h"
 #include "llvm/Support/Path.h"
 #include "llvm/Support/PrettyStackTrace.h"
+#include "llvm/Support/Signals.h"
+#include "llvm/Support/ThreadPool.h"
 #include "llvm/Support/raw_ostream.h"
 #include <algorithm>
 #include <array>
-#include <cassert>
-#include <climits>
-#include <cstdarg>
 #include <cstdint>
 #include <cstdio>
 #include <cstdlib>
 #include <cstring>
 #include <fcntl.h>
+#include <fstream>
+#include <iostream>
 #include <map>
 #include <memory>
 #include <optional>
+#include <ostream>
 #include <set>
+#include <string>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <thread>
+#include <utility>
 #include <vector>
 
 #if defined(_WIN32)
@@ -68,6 +78,7 @@
 #else
 #include <netinet/in.h>
 #include <sys/socket.h>
+#include <sys/un.h>
 #include <unistd.h>
 #endif
 
@@ -83,6 +94,9 @@ typedef int socklen_t;
 #endif
 
 using namespace lldb_dap;
+using lldb_private::NativeSocket;
+using lldb_private::Socket;
+using lldb_private::Status;
 
 namespace {
 using namespace llvm::opt;
@@ -142,43 +156,6 @@ lldb::SBValueList *GetTopLevelScope(DAP &dap, int64_t variablesReference) {
   }
 }
 
-SOCKET AcceptConnection(std::ofstream *log, int portno) {
-  // Accept a socket connection from any host on "portno".
-  SOCKET newsockfd = -1;
-  struct sockaddr_in serv_addr, cli_addr;
-  SOCKET sockfd = socket(AF_INET, SOCK_STREAM, 0);
-  if (sockfd < 0) {
-    if (log)
-      *log << "error: opening socket (" << strerror(errno) << ")" << std::endl;
-  } else {
-    memset((char *)&serv_addr, 0, sizeof(serv_addr));
-    serv_addr.sin_family = AF_INET;
-    // serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
-    serv_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
-    serv_addr.sin_port = htons(portno);
-    if (bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
-      if (log)
-        *log << "error: binding socket (" << strerror(errno) << ")"
-             << std::endl;
-    } else {
-      listen(sockfd, 5);
-      socklen_t clilen = sizeof(cli_addr);
-      newsockfd =
-          llvm::sys::RetryAfterSignal(static_cast<SOCKET>(-1), accept, sockfd,
-                                      (struct sockaddr *)&cli_addr, &clilen);
-      if (newsockfd < 0)
-        if (log)
-          *log << "error: accept (" << strerror(errno) << ")" << std::endl;
-    }
-#if defined(_WIN32)
-    closesocket(sockfd);
-#else
-    close(sockfd);
-#endif
-  }
-  return newsockfd;
-}
-
 std::vector<const char *> MakeArgv(const llvm::ArrayRef<std::string> &strs) {
   // Create and return an array of "const char *", one for each C string in
   // "strs" and terminate the list with a NULL. This can be used for argument
@@ -4857,10 +4834,10 @@ static void printHelp(LLDBDAPOptTable &table, llvm::StringRef tool_name) {
   The debug adapter can be started in two modes.
 
   Running lldb-dap without any arguments will start communicating with the
-  parent over stdio. Passing a port number causes lldb-dap to start listening
-  for connections on that port.
+  parent over stdio. Passing a --connection URI will cause lldb-dap to listen 
+  for a connection in the specified mode.
 
-    lldb-dap -p <port>
+    lldb-dap --connection connection://localhost:<port>
 
   Passing --wait-for-debugger will pause the process at startup and wait for a
   debugger to attach to the process.
@@ -4952,6 +4929,29 @@ static int DuplicateFileDescriptor(int fd) {
 #endif
 }
 
+static llvm::Expected<std::pair<Socket::SocketProtocol, std::string>>
+validateConnection(llvm::StringRef conn) {
+  auto uri = lldb_private::URI::Parse(conn);
+
+  if (uri && (uri->scheme == "tcp" || uri->scheme == "connect" ||
+              !uri->hostname.empty() || uri->port)) {
+    return std::make_pair(
+        Socket::ProtocolTcp,
+        formatv("[{0}]:{1}", uri->hostname.empty() ? "0.0.0.0" : uri->hostname,
+                uri->port.value_or(0)));
+  }
+
+  if (uri && (uri->scheme == "unix" || uri->scheme == "unix-connect" ||
+              uri->path != "/")) {
+    return std::make_pair(Socket::ProtocolUnixDomain, uri->path.str());
+  }
+
+  return llvm::createStringError(
+      "Unsupported connection specifier, expected 'unix-connect:///path' or "
+      "'connect://[host]:port', got '%s'.",
+      conn.str().c_str());
+}
+
 int main(int argc, char *argv[]) {
   llvm::InitLLVM IL(argc, argv, /*InstallPipeSignalExitHandler=*/false);
 #if !defined(__APPLE__)
@@ -4987,9 +4987,9 @@ int main(int argc, char *argv[]) {
     } else if (repl_mode_value == "command") {
       default_repl_mode = ReplMode::Command;
     } else {
-      llvm::errs()
-          << "'" << repl_mode_value
-          << "' is not a valid option, use 'variable', 'command' or 'auto'.\n";
+      llvm::errs() << "'" << repl_mode_value
+                   << "' is not a valid option, use 'variable', 'command' or "
+                      "'auto'.\n";
       return EXIT_FAILURE;
     }
   }
@@ -5022,15 +5022,10 @@ int main(int argc, char *argv[]) {
     }
   }
 
-  int portno = -1;
-  if (auto *arg = input_args.getLastArg(OPT_port)) {
-    const auto *optarg = arg->getValue();
-    char *remainder;
-    portno = strtol(optarg, &remainder, 0);
-    if (remainder == optarg || *remainder != '\0') {
-      fprintf(stderr, "'%s' is not a valid port number.\n", optarg);
-      return EXIT_FAILURE;
-    }
+  std::string connection;
+  if (auto *arg = input_args.getLastArg(OPT_connection)) {
+    const auto *path = arg->getValue();
+    connection.assign(path);
   }
 
 #if !defined(_WIN32)
@@ -5058,72 +5053,144 @@ int main(int argc, char *argv[]) {
   auto terminate_debugger =
       llvm::make_scope_exit([] { lldb::SBDebugger::Terminate(); });
 
-  StreamDescriptor input;
-  StreamDescriptor output;
-  std::FILE *redirectOut = nullptr;
-  std::FILE *redirectErr = nullptr;
-  if (portno != -1) {
-    printf("Listening on port %i...\n", portno);
-    SOCKET socket_fd = AcceptConnection(log.get(), portno);
-    if (socket_fd < 0)
-      return EXIT_FAILURE;
+  std::vector<std::string> pre_init_commands;
+  for (const std::string &arg :
+       input_args.getAllArgValues(OPT_pre_init_command)) {
+    pre_init_commands.push_back(arg);
+  }
 
-    input = StreamDescriptor::from_socket(socket_fd, true);
-    output = StreamDescriptor::from_socket(socket_fd, false);
-  } else {
-#if defined(_WIN32)
-    // Windows opens stdout and stdin in text mode which converts \n to 13,10
-    // while the value is just 10 on Darwin/Linux. Setting the file mode to
-    // binary fixes this.
-    int result = _setmode(fileno(stdout), _O_BINARY);
-    assert(result);
-    result = _setmode(fileno(stdin), _O_BINARY);
-    UNUSED_IF_ASSERT_DISABLED(result);
-    assert(result);
-#endif
+  auto RunDAP = [](llvm::StringRef program_path, ReplMode repl_mode,
+                   std::vector<std::string> pre_init_commands,
+                   std::ofstream *log, std::string name, StreamDescriptor input,
+                   StreamDescriptor output, std::FILE *redirectOut = nullptr,
+                   std::FILE *redirectErr = nullptr) -> bool {
+    DAP dap = DAP(name, program_path, log, std::move(input), std::move(output),
+                  repl_mode, pre_init_commands);
 
-    int stdout_fd = DuplicateFileDescriptor(fileno(stdout));
-    if (stdout_fd == -1) {
+    // stdout/stderr redirection to the IDE's console
+    if (auto Err = dap.ConfigureIO(redirectOut, redirectErr)) {
       llvm::logAllUnhandledErrors(
-          llvm::errorCodeToError(llvm::errnoAsErrorCode()), llvm::errs(),
-          "Failed to configure stdout redirect: ");
+          std::move(Err), llvm::errs(),
+          "Failed to configure lldb-dap IO operations: ");
+      return false;
+    }
+
+    RegisterRequestCallbacks(dap);
+
+    // used only by TestVSCode_redirection_to_console.py
+    if (getenv("LLDB_DAP_TEST_STDOUT_STDERR_REDIRECTION") != nullptr)
+      redirection_test();
+
+    if (auto Err = dap.Loop()) {
+      std::string errorMessage = llvm::toString(std::move(Err));
+      if (log)
+        *log << "Transport Error: " << errorMessage << "\n";
+      return false;
+    }
+    return true;
+  };
+
+  if (!connection.empty()) {
+    auto maybeProtoclAndName = validateConnection(connection);
+    if (auto Err = maybeProtoclAndName.takeError()) {
+      llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(),
+                                  "Invalid connection: ");
       return EXIT_FAILURE;
     }
 
-    redirectOut = stdout;
-    redirectErr = stderr;
+    Socket::SocketProtocol protocol;
+    std::string name;
+    std::tie(protocol, name) = *maybeProtoclAndName;
 
-    input = StreamDescriptor::from_file(fileno(stdin), false);
-    output = StreamDescriptor::from_file(stdout_fd, false);
-  }
+    Status error;
+    std::unique_ptr<Socket> listener = Socket::Create(protocol, error);
+    if (error.Fail()) {
+      llvm::logAllUnhandledErrors(error.takeError(), llvm::errs(),
+                                  "Failed to create socket listener: ");
+      return EXIT_FAILURE;
+    }
 
-  DAP dap = DAP(program_path.str(), log.get(), default_repl_mode,
-                std::move(input), std::move(output));
+    error = listener->Listen(name, /* backlog */ 5);
+    if (error.Fail()) {
+      llvm::logAllUnhandledErrors(error.takeError(), llvm::errs(),
+                                  "Failed to listen for connections: ");
+      return EXIT_FAILURE;
+    }
 
-  // stdout/stderr redirection to the IDE's console
-  if (auto Err = dap.ConfigureIO(redirectOut, redirectErr)) {
-    llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(),
-                                "Failed to configure lldb-dap IO operations: ");
-    return EXIT_FAILURE;
-  }
+    std::string address =
+        llvm::join(listener->GetListeningConnectionURI(), ", ");
+    if (log)
+      *log << "started with connection listeners " << address << "\n";
+
+    llvm::outs() << "Listening for: " << address << "\n";
+    // Ensure listening address are flushed for calles to retrieve the resolve
+    // address.
+    llvm::outs().flush();
+
+    llvm::DefaultThreadPool pool(llvm::optimal_concurrency());
+    unsigned int clientCount = 0;
+
+    while (true) {
+      Socket *accepted_socket;
+      error = listener->Accept(/*timeout=*/std::nullopt, accepted_socket);
+      if (error.Fail()) {
+        llvm::logAllUnhandledErrors(error.takeError(), llvm::errs(),
+                                    "accept failed: ");
+        return EXIT_FAILURE;
+      }
 
-  RegisterRequestCallbacks(dap);
+      std::string name = llvm::formatv("client_{0}", clientCount++).str();
+      if (log) {
+        auto now = std::chrono::duration<double>(
+            std::chrono::system_clock::now().time_since_epoch());
+        *log << llvm::formatv("{0:f9}", now.count()).str()
+             << " client connected: " << name << "\n";
+      }
 
-  for (const std::string &arg :
-       input_args.getAllArgValues(OPT_pre_init_command)) {
-    dap.pre_init_commands.push_back(arg);
+      // Move the client into the connection pool to unblock accepting the next
+      // client.
+      pool.async(
+          [=](Socket *accepted_socket, std::ofstream *log) {
+            StreamDescriptor input = StreamDescriptor::from_socket(
+                accepted_socket->GetNativeSocket(), false);
+            // Close the output last for the best chance at error reporting.
+            StreamDescriptor output = StreamDescriptor::from_socket(
+                accepted_socket->GetNativeSocket(), true);
+            RunDAP(program_path, default_repl_mode, pre_init_commands, log,
+                   name, std::move(input), std::move(output));
+          },
+          accepted_socket, log.get());
+    }
+
+    pool.wait();
+
+    return EXIT_SUCCESS;
   }
 
-  // used only by TestVSCode_redirection_to_console.py
-  if (getenv("LLDB_DAP_TEST_STDOUT_STDERR_REDIRECTION") != nullptr)
-    redirection_test();
+#if defined(_WIN32)
+  // Windows opens stdout and stdin in text mode which converts \n to 13,10
+  // while the value is just 10 on Darwin/Linux. Setting the file mode to
+  // binary fixes this.
+  int result = _setmode(fileno(stdout), _O_BINARY);
+  assert(result);
+  result = _setmode(fileno(stdin), _O_BINARY);
+  UNUSED_IF_ASSERT_DISABLED(result);
+  assert(result);
+#endif
 
-  bool CleanExit = true;
-  if (auto Err = dap.Loop()) {
-    if (log)
-      *log << "Transport Error: " << llvm::toString(std::move(Err)) << "\n";
-    CleanExit = false;
+  int stdout_fd = DuplicateFileDescriptor(fileno(stdout));
+  if (stdout_fd == -1) {
+    llvm::logAllUnhandledErrors(
+        llvm::errorCodeToError(llvm::errnoAsErrorCode()), llvm::errs(),
+        "Failed to configure stdout redirect: ");
+    return EXIT_FAILURE;
   }
 
-  return CleanExit ? EXIT_SUCCESS : EXIT_FAILURE;
+  StreamDescriptor input = StreamDescriptor::from_file(fileno(stdin), false);
+  StreamDescriptor output = StreamDescriptor::from_file(stdout_fd, false);
+
+  bool status = RunDAP(program_path, default_repl_mode, pre_init_commands,
+                       log.get(), "stdin/stdout", std::move(input),
+                       std::move(output), stdout, stderr);
+  return status ? EXIT_SUCCESS : EXIT_FAILURE;
 }

>From 2fe4f85f7620be29c28452783a3d2cffafb91c31 Mon Sep 17 00:00:00 2001
From: John Harrison <harjohn at google.com>
Date: Tue, 4 Feb 2025 10:57:32 -0800
Subject: [PATCH 2/4] Applying review comments.

---
 lldb/test/API/tools/lldb-dap/server/Makefile | 2 +-
 lldb/tools/lldb-dap/DAP.cpp                  | 2 +-
 lldb/tools/lldb-dap/DAPForward.h             | 2 --
 lldb/tools/lldb-dap/lldb-dap.cpp             | 5 ++---
 4 files changed, 4 insertions(+), 7 deletions(-)

diff --git a/lldb/test/API/tools/lldb-dap/server/Makefile b/lldb/test/API/tools/lldb-dap/server/Makefile
index 451278a0946ef2b..10495940055b63d 100644
--- a/lldb/test/API/tools/lldb-dap/server/Makefile
+++ b/lldb/test/API/tools/lldb-dap/server/Makefile
@@ -1,3 +1,3 @@
 C_SOURCES := main.c
 
-include Makefile.rules
\ No newline at end of file
+include Makefile.rules
diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp
index 11a4db5c7f0f48a..cfaa8ad53c6340a 100644
--- a/lldb/tools/lldb-dap/DAP.cpp
+++ b/lldb/tools/lldb-dap/DAP.cpp
@@ -62,7 +62,7 @@ DAP::DAP(std::string name, llvm::StringRef path, std::ofstream *log,
          std::vector<std::string> pre_init_commands)
     : name(name), debug_adaptor_path(path), log(log), input(std::move(input)),
       output(std::move(output)), broadcaster("lldb-dap"),
-      exception_breakpoints(), pre_init_commands(pre_init_commands),
+      exception_breakpoints(), pre_init_commands(std::move(pre_init_commands)),
       focus_tid(LLDB_INVALID_THREAD_ID), stop_at_entry(false), is_attach(false),
       enable_auto_variable_summaries(false),
       enable_synthetic_child_debugging(false),
diff --git a/lldb/tools/lldb-dap/DAPForward.h b/lldb/tools/lldb-dap/DAPForward.h
index 15e286f3d08dc1e..0196d83dcd6a91b 100644
--- a/lldb/tools/lldb-dap/DAPForward.h
+++ b/lldb/tools/lldb-dap/DAPForward.h
@@ -11,8 +11,6 @@
 
 // IWYU pragma: begin_exports
 
-#include "lldb/lldb-forward.h"
-
 namespace lldb_dap {
 struct BreakpointBase;
 struct ExceptionBreakpoint;
diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp
index 2c7fdb0f0d3cfb2..fd86c8d8b3a9b56 100644
--- a/lldb/tools/lldb-dap/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/lldb-dap.cpp
@@ -5110,7 +5110,7 @@ int main(int argc, char *argv[]) {
       return EXIT_FAILURE;
     }
 
-    error = listener->Listen(name, /* backlog */ 5);
+    error = listener->Listen(name, /*backlog=*/5);
     if (error.Fail()) {
       llvm::logAllUnhandledErrors(error.takeError(), llvm::errs(),
                                   "Failed to listen for connections: ");
@@ -5127,9 +5127,8 @@ int main(int argc, char *argv[]) {
     // address.
     llvm::outs().flush();
 
-    llvm::DefaultThreadPool pool(llvm::optimal_concurrency());
+    llvm::DefaultThreadPool pool;
     unsigned int clientCount = 0;
-
     while (true) {
       Socket *accepted_socket;
       error = listener->Accept(/*timeout=*/std::nullopt, accepted_socket);

>From 606decbf8ea180880e899625c73de11f0fecafa1 Mon Sep 17 00:00:00 2001
From: John Harrison <harjohn at google.com>
Date: Wed, 5 Feb 2025 10:06:05 -0800
Subject: [PATCH 3/4] Ensuring the server mode tracks active DAP sessions and
 adding an interrupt handler to disconnect active sessions.

---
 .../tools/lldb-dap/server/TestDAP_server.py   |  31 +++
 lldb/tools/lldb-dap/DAP.cpp                   |  51 ++++
 lldb/tools/lldb-dap/DAP.h                     |  12 +-
 lldb/tools/lldb-dap/lldb-dap.cpp              | 225 +++++++++---------
 4 files changed, 210 insertions(+), 109 deletions(-)

diff --git a/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py b/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py
index 9597f73b0659bee..595bb7a0d139212 100644
--- a/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py
+++ b/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py
@@ -75,3 +75,34 @@ def cleanup():
         server = self.start_server(connection="unix://" + name)
         self.run_debug_session(server.connection, "Alice")
         self.run_debug_session(server.connection, "Bob")
+
+    @skipIfWindows
+    def test_server_interrupt(self):
+        """
+        Test launching a binary with lldb-dap in server mode and shutting down the server while the debug session is still active.
+        """
+        self.build()
+        server = self.start_server(connection="tcp://localhost:0")
+        self.dap_server = dap_server.DebugAdaptorServer(
+            connection=server.connection,
+        )
+        program = self.getBuildArtifact("a.out")
+        source = "main.c"
+        breakpoint_line = line_number(source, "// breakpoint")
+
+        self.launch(
+            program,
+            args=["Alice"],
+            disconnectAutomatically=False,
+        )
+        self.set_source_breakpoints(source, [breakpoint_line])
+        self.continue_to_next_stop()
+
+        # Interrupt the server which should disconnect all clients.
+        server.process.send_signal(signal.SIGINT)
+
+        self.dap_server.wait_for_terminated()
+        self.assertIsNone(
+            self.dap_server.exit_status,
+            "Process exited before interrupting lldb-dap server",
+        )
diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp
index cfaa8ad53c6340a..4da077e857a4a2f 100644
--- a/lldb/tools/lldb-dap/DAP.cpp
+++ b/lldb/tools/lldb-dap/DAP.cpp
@@ -795,6 +795,57 @@ bool DAP::HandleObject(const llvm::json::Object &object) {
   return false;
 }
 
+void DAP::SendTerminatedEvent() {
+  // Prevent races if the process exits while we're being asked to disconnect.
+  llvm::call_once(terminated_event_flag, [&] {
+    RunTerminateCommands();
+    // Send a "terminated" event
+    llvm::json::Object event(CreateTerminatedEventObject(target));
+    SendJSON(llvm::json::Value(std::move(event)));
+  });
+}
+
+lldb::SBError DAP::Disconnect() { return Disconnect(is_attach); }
+
+lldb::SBError DAP::Disconnect(bool terminateDebuggee) {
+  lldb::SBError error;
+  lldb::SBProcess process = target.GetProcess();
+  auto state = process.GetState();
+  switch (state) {
+  case lldb::eStateInvalid:
+  case lldb::eStateUnloaded:
+  case lldb::eStateDetached:
+  case lldb::eStateExited:
+    break;
+  case lldb::eStateConnected:
+  case lldb::eStateAttaching:
+  case lldb::eStateLaunching:
+  case lldb::eStateStepping:
+  case lldb::eStateCrashed:
+  case lldb::eStateSuspended:
+  case lldb::eStateStopped:
+  case lldb::eStateRunning:
+    debugger.SetAsync(false);
+    error = terminateDebuggee ? process.Kill() : process.Detach();
+    debugger.SetAsync(true);
+    break;
+  }
+  SendTerminatedEvent();
+
+  if (event_thread.joinable()) {
+    broadcaster.BroadcastEventByType(eBroadcastBitStopEventThread);
+    event_thread.join();
+  }
+  if (progress_event_thread.joinable()) {
+    broadcaster.BroadcastEventByType(eBroadcastBitStopProgressThread);
+    progress_event_thread.join();
+  }
+  StopIO();
+  disconnecting = true;
+
+  return error;
+}
+
 llvm::Error DAP::Loop() {
   while (!disconnecting) {
     llvm::json::Object object;
diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h
index 7c28dbf801179fa..6347dfb57890eb8 100644
--- a/lldb/tools/lldb-dap/DAP.h
+++ b/lldb/tools/lldb-dap/DAP.h
@@ -217,7 +217,8 @@ struct DAP {
   ///
   /// Errors in this operation will be printed to the log file and the IDE's
   /// console output as well.
-  llvm::Error ConfigureIO(std::FILE *overrideOut, std::FILE *overrideErr);
+  llvm::Error ConfigureIO(std::FILE *overrideOut = nullptr,
+                          std::FILE *overrideErr = nullptr);
 
   /// Stop the redirected IO threads and associated pipes.
   void StopIO();
@@ -305,6 +306,15 @@ struct DAP {
   PacketStatus GetNextObject(llvm::json::Object &object);
   bool HandleObject(const llvm::json::Object &object);
 
+  /// Disconnect the DAP session.
+  lldb::SBError Disconnect();
+
+  /// Disconnect the DAP session and optionally terminate the debuggee.
+  lldb::SBError Disconnect(bool terminateDebuggee);
+
+  /// Send a "terminated" event to indicate the process is done being debugged.
+  void SendTerminatedEvent();
+
   llvm::Error Loop();
 
   /// Send a Debug Adapter Protocol reverse request to the IDE.
diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp
index fd86c8d8b3a9b56..4b9b5fb4310ac58 100644
--- a/lldb/tools/lldb-dap/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/lldb-dap.cpp
@@ -21,6 +21,8 @@
 #include "lldb/API/SBStream.h"
 #include "lldb/API/SBStringList.h"
 #include "lldb/Host/Config.h"
+#include "lldb/Host/MainLoop.h"
+#include "lldb/Host/MainLoopBase.h"
 #include "lldb/Host/Socket.h"
 #include "lldb/Utility/Status.h"
 #include "lldb/Utility/UriParser.h"
@@ -42,7 +44,7 @@
 #include "llvm/Support/Path.h"
 #include "llvm/Support/PrettyStackTrace.h"
 #include "llvm/Support/Signals.h"
-#include "llvm/Support/ThreadPool.h"
+#include "llvm/Support/Threading.h"
 #include "llvm/Support/raw_ostream.h"
 #include <algorithm>
 #include <array>
@@ -55,6 +57,7 @@
 #include <iostream>
 #include <map>
 #include <memory>
+#include <mutex>
 #include <optional>
 #include <ostream>
 #include <set>
@@ -207,18 +210,6 @@ void SendContinuedEvent(DAP &dap) {
   dap.SendJSON(llvm::json::Value(std::move(event)));
 }
 
-// Send a "terminated" event to indicate the process is done being
-// debugged.
-void SendTerminatedEvent(DAP &dap) {
-  // Prevent races if the process exits while we're being asked to disconnect.
-  llvm::call_once(dap.terminated_event_flag, [&] {
-    dap.RunTerminateCommands();
-    // Send a "terminated" event
-    llvm::json::Object event(CreateTerminatedEventObject(dap.target));
-    dap.SendJSON(llvm::json::Value(std::move(event)));
-  });
-}
-
 // Send a thread stopped event for all threads as long as the process
 // is stopped.
 void SendThreadStoppedEvent(DAP &dap) {
@@ -390,6 +381,7 @@ void SendStdOutStdErr(DAP &dap, lldb::SBProcess &process) {
 }
 
 void ProgressEventThreadFunction(DAP &dap) {
+  llvm::set_thread_name(dap.name + ".progress_handler");
   lldb::SBListener listener("lldb-dap.progress.listener");
   dap.debugger.GetBroadcaster().AddListener(
       listener, lldb::SBDebugger::eBroadcastBitProgress |
@@ -424,8 +416,10 @@ void ProgressEventThreadFunction(DAP &dap) {
 // them prevent multiple threads from writing simultaneously so no locking
 // is required.
 void EventThreadFunction(DAP &dap) {
+  llvm::set_thread_name(dap.name + ".event_handler");
   lldb::SBEvent event;
   lldb::SBListener listener = dap.debugger.GetListener();
+  dap.broadcaster.AddListener(listener, eBroadcastBitStopEventThread);
   bool done = false;
   while (!done) {
     if (listener.WaitForEvent(1, event)) {
@@ -490,7 +484,7 @@ void EventThreadFunction(DAP &dap) {
               // launch.json
               dap.RunExitCommands();
               SendProcessExitedEvent(dap, process);
-              SendTerminatedEvent(dap);
+              dap.SendTerminatedEvent();
               done = true;
             }
             break;
@@ -1048,41 +1042,12 @@ void request_disconnect(DAP &dap, const llvm::json::Object &request) {
   bool defaultTerminateDebuggee = dap.is_attach ? false : true;
   bool terminateDebuggee =
       GetBoolean(arguments, "terminateDebuggee", defaultTerminateDebuggee);
-  lldb::SBProcess process = dap.target.GetProcess();
-  auto state = process.GetState();
-  switch (state) {
-  case lldb::eStateInvalid:
-  case lldb::eStateUnloaded:
-  case lldb::eStateDetached:
-  case lldb::eStateExited:
-    break;
-  case lldb::eStateConnected:
-  case lldb::eStateAttaching:
-  case lldb::eStateLaunching:
-  case lldb::eStateStepping:
-  case lldb::eStateCrashed:
-  case lldb::eStateSuspended:
-  case lldb::eStateStopped:
-  case lldb::eStateRunning:
-    dap.debugger.SetAsync(false);
-    lldb::SBError error = terminateDebuggee ? process.Kill() : process.Detach();
-    if (!error.Success())
-      EmplaceSafeString(response, "error", error.GetCString());
-    dap.debugger.SetAsync(true);
-    break;
-  }
-  SendTerminatedEvent(dap);
+
+  lldb::SBError error = dap.Disconnect(terminateDebuggee);
+  if (error.Fail())
+    EmplaceSafeString(response, "error", error.GetCString());
+
   dap.SendJSON(llvm::json::Value(std::move(response)));
-  if (dap.event_thread.joinable()) {
-    dap.broadcaster.BroadcastEventByType(eBroadcastBitStopEventThread);
-    dap.event_thread.join();
-  }
-  if (dap.progress_event_thread.joinable()) {
-    dap.broadcaster.BroadcastEventByType(eBroadcastBitStopProgressThread);
-    dap.progress_event_thread.join();
-  }
-  dap.StopIO();
-  dap.disconnecting = true;
 }
 
 // "ExceptionInfoRequest": {
@@ -5059,37 +5024,6 @@ int main(int argc, char *argv[]) {
     pre_init_commands.push_back(arg);
   }
 
-  auto RunDAP = [](llvm::StringRef program_path, ReplMode repl_mode,
-                   std::vector<std::string> pre_init_commands,
-                   std::ofstream *log, std::string name, StreamDescriptor input,
-                   StreamDescriptor output, std::FILE *redirectOut = nullptr,
-                   std::FILE *redirectErr = nullptr) -> bool {
-    DAP dap = DAP(name, program_path, log, std::move(input), std::move(output),
-                  repl_mode, pre_init_commands);
-
-    // stdout/stderr redirection to the IDE's console
-    if (auto Err = dap.ConfigureIO(redirectOut, redirectErr)) {
-      llvm::logAllUnhandledErrors(
-          std::move(Err), llvm::errs(),
-          "Failed to configure lldb-dap IO operations: ");
-      return false;
-    }
-
-    RegisterRequestCallbacks(dap);
-
-    // used only by TestVSCode_redirection_to_console.py
-    if (getenv("LLDB_DAP_TEST_STDOUT_STDERR_REDIRECTION") != nullptr)
-      redirection_test();
-
-    if (auto Err = dap.Loop()) {
-      std::string errorMessage = llvm::toString(std::move(Err));
-      if (log)
-        *log << "Transport Error: " << errorMessage << "\n";
-      return false;
-    }
-    return true;
-  };
-
   if (!connection.empty()) {
     auto maybeProtoclAndName = validateConnection(connection);
     if (auto Err = maybeProtoclAndName.takeError()) {
@@ -5103,7 +5037,7 @@ int main(int argc, char *argv[]) {
     std::tie(protocol, name) = *maybeProtoclAndName;
 
     Status error;
-    std::unique_ptr<Socket> listener = Socket::Create(protocol, error);
+    static std::unique_ptr<Socket> listener = Socket::Create(protocol, error);
     if (error.Fail()) {
       llvm::logAllUnhandledErrors(error.takeError(), llvm::errs(),
                                   "Failed to create socket listener: ");
@@ -5127,17 +5061,18 @@ int main(int argc, char *argv[]) {
     // address.
     llvm::outs().flush();
 
-    llvm::DefaultThreadPool pool;
+    static lldb_private::MainLoop g_loop;
+    llvm::sys::SetInterruptFunction([]() {
+      g_loop.AddPendingCallback(
+          [](lldb_private::MainLoopBase &loop) { loop.RequestTermination(); });
+    });
+    std::mutex active_dap_sessions_mutext;
+    std::set<DAP *> active_dap_sessions;
     unsigned int clientCount = 0;
-    while (true) {
-      Socket *accepted_socket;
-      error = listener->Accept(/*timeout=*/std::nullopt, accepted_socket);
-      if (error.Fail()) {
-        llvm::logAllUnhandledErrors(error.takeError(), llvm::errs(),
-                                    "accept failed: ");
-        return EXIT_FAILURE;
-      }
-
+    auto handle = listener->Accept(g_loop, [=, &active_dap_sessions_mutext,
+                                            &active_dap_sessions, &clientCount,
+                                            log = log.get()](
+                                               std::unique_ptr<Socket> sock) {
       std::string name = llvm::formatv("client_{0}", clientCount++).str();
       if (log) {
         auto now = std::chrono::duration<double>(
@@ -5146,24 +5081,80 @@ int main(int argc, char *argv[]) {
              << " client connected: " << name << "\n";
       }
 
-      // Move the client into the connection pool to unblock accepting the next
+      // Move the client into a background thread to unblock accepting the next
       // client.
-      pool.async(
-          [=](Socket *accepted_socket, std::ofstream *log) {
-            StreamDescriptor input = StreamDescriptor::from_socket(
-                accepted_socket->GetNativeSocket(), false);
-            // Close the output last for the best chance at error reporting.
-            StreamDescriptor output = StreamDescriptor::from_socket(
-                accepted_socket->GetNativeSocket(), true);
-            RunDAP(program_path, default_repl_mode, pre_init_commands, log,
-                   name, std::move(input), std::move(output));
-          },
-          accepted_socket, log.get());
+      std::thread client([=, &active_dap_sessions_mutext, &active_dap_sessions,
+                          sock = std::move(sock)]() {
+        llvm::set_thread_name(name + ".runloop");
+        StreamDescriptor input =
+            StreamDescriptor::from_socket(sock->GetNativeSocket(), false);
+        // Close the output last for the best chance at error reporting.
+        StreamDescriptor output =
+            StreamDescriptor::from_socket(sock->GetNativeSocket(), false);
+        DAP dap = DAP(name, program_path, log, std::move(input),
+                      std::move(output), default_repl_mode, pre_init_commands);
+
+        if (auto Err = dap.ConfigureIO()) {
+          llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(),
+                                      "Failed to configure stdout redirect: ");
+          return;
+        }
+
+        RegisterRequestCallbacks(dap);
+
+        {
+          std::scoped_lock lock(active_dap_sessions_mutext);
+          active_dap_sessions.insert(&dap);
+        }
+
+        if (auto Err = dap.Loop()) {
+          llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(),
+                                      "DAP session error: ");
+        }
+
+        {
+          std::scoped_lock lock(active_dap_sessions_mutext);
+          active_dap_sessions.erase(&dap);
+        }
+
+        if (log) {
+          auto now = std::chrono::duration<double>(
+              std::chrono::system_clock::now().time_since_epoch());
+          *log << llvm::formatv("{0:f9}", now.count()).str()
+               << " client closed: " << name << "\n";
+        }
+      });
+      client.detach();
+    });
+    if (auto Err = handle.takeError()) {
+      llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(),
+                                  "Registering accept handler failed: ");
+      return EXIT_FAILURE;
     }
 
-    pool.wait();
+    error = g_loop.Run();
+    if (error.Fail()) {
+      llvm::logAllUnhandledErrors(error.takeError(), llvm::errs(),
+                                  "MainLoop failed: ");
+      return EXIT_FAILURE;
+    }
 
-    return EXIT_SUCCESS;
+    if (log)
+      *log << "lldb-dap server shutdown requested, disconnecting remaining "
+              "clients...\n";
+
+    bool client_failed = false;
+    std::scoped_lock lock(active_dap_sessions_mutext);
+    for (auto *dap : active_dap_sessions) {
+      auto error = dap->Disconnect();
+      if (error.Fail()) {
+        client_failed = true;
+        llvm::errs() << "DAP client " << dap->name
+                     << " disconnected failed: " << error.GetCString() << "\n";
+      }
+    }
+
+    return client_failed ? EXIT_FAILURE : EXIT_SUCCESS;
   }
 
 #if defined(_WIN32)
@@ -5188,8 +5179,26 @@ int main(int argc, char *argv[]) {
   StreamDescriptor input = StreamDescriptor::from_file(fileno(stdin), false);
   StreamDescriptor output = StreamDescriptor::from_file(stdout_fd, false);
 
-  bool status = RunDAP(program_path, default_repl_mode, pre_init_commands,
-                       log.get(), "stdin/stdout", std::move(input),
-                       std::move(output), stdout, stderr);
-  return status ? EXIT_SUCCESS : EXIT_FAILURE;
+  DAP dap = DAP("stdin/stdout", program_path, log.get(), std::move(input),
+                std::move(output), default_repl_mode, pre_init_commands);
+
+  // stdout/stderr redirection to the IDE's console
+  if (auto Err = dap.ConfigureIO(stdout, stderr)) {
+    llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(),
+                                "Failed to configure stdout redirect: ");
+    return EXIT_FAILURE;
+  }
+
+  RegisterRequestCallbacks(dap);
+
+  // used only by TestVSCode_redirection_to_console.py
+  if (getenv("LLDB_DAP_TEST_STDOUT_STDERR_REDIRECTION") != nullptr)
+    redirection_test();
+
+  if (auto Err = dap.Loop()) {
+    llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(),
+                                "DAP session error: ");
+    return EXIT_FAILURE;
+  }
+  return EXIT_SUCCESS;
 }

>From 784cb25449a9f7dc25be70fe14289a13dd6f0e1d Mon Sep 17 00:00:00 2001
From: John Harrison <harjohn at google.com>
Date: Thu, 6 Feb 2025 13:16:19 -0800
Subject: [PATCH 4/4] Using a std::condition_variable to notify the main thread
 after all DAP sessions have disconnected to coordinate shutdowns.

---
 lldb/tools/lldb-dap/DAP.cpp              | 21 ++++++----
 lldb/tools/lldb-dap/IOStream.cpp         |  5 +--
 lldb/tools/lldb-dap/OutputRedirector.cpp |  3 ++
 lldb/tools/lldb-dap/lldb-dap.cpp         | 52 ++++++++++++++----------
 4 files changed, 48 insertions(+), 33 deletions(-)

diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp
index 4da077e857a4a2f..e7904d9a9c4ac79 100644
--- a/lldb/tools/lldb-dap/DAP.cpp
+++ b/lldb/tools/lldb-dap/DAP.cpp
@@ -21,6 +21,7 @@
 #include "lldb/lldb-defines.h"
 #include "lldb/lldb-enumerations.h"
 #include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/ScopeExit.h"
 #include "llvm/ADT/StringExtras.h"
 #include "llvm/ADT/Twine.h"
 #include "llvm/Support/Error.h"
@@ -224,6 +225,15 @@ llvm::Error DAP::ConfigureIO(std::FILE *overrideOut, std::FILE *overrideErr) {
 void DAP::StopIO() {
   out.Stop();
   err.Stop();
+
+  if (event_thread.joinable()) {
+    broadcaster.BroadcastEventByType(eBroadcastBitStopEventThread);
+    event_thread.join();
+  }
+  if (progress_event_thread.joinable()) {
+    broadcaster.BroadcastEventByType(eBroadcastBitStopProgressThread);
+    progress_event_thread.join();
+  }
 }
 
 // Send the JSON in "json_str" to the "out" stream. Correctly send the
@@ -830,23 +840,16 @@ lldb::SBError DAP::Disconnect(bool terminateDebuggee) {
     debugger.SetAsync(true);
     break;
   }
+
   SendTerminatedEvent();
 
-  if (event_thread.joinable()) {
-    broadcaster.BroadcastEventByType(eBroadcastBitStopEventThread);
-    event_thread.join();
-  }
-  if (progress_event_thread.joinable()) {
-    broadcaster.BroadcastEventByType(eBroadcastBitStopProgressThread);
-    progress_event_thread.join();
-  }
-  StopIO();
   disconnecting = true;
 
   return error;
 }
 
 llvm::Error DAP::Loop() {
+  auto stop_io = llvm::make_scope_exit([this]() { StopIO(); });
   while (!disconnecting) {
     llvm::json::Object object;
     lldb_dap::PacketStatus status = GetNextObject(object);
diff --git a/lldb/tools/lldb-dap/IOStream.cpp b/lldb/tools/lldb-dap/IOStream.cpp
index d2e8ec40b0a7b85..7d0f363440f53d6 100644
--- a/lldb/tools/lldb-dap/IOStream.cpp
+++ b/lldb/tools/lldb-dap/IOStream.cpp
@@ -7,6 +7,8 @@
 //===----------------------------------------------------------------------===//
 
 #include "IOStream.h"
+#include <fstream>
+#include <string>
 
 #if defined(_WIN32)
 #include <io.h>
@@ -16,9 +18,6 @@
 #include <unistd.h>
 #endif
 
-#include <fstream>
-#include <string>
-
 using namespace lldb_dap;
 
 StreamDescriptor::StreamDescriptor() = default;
diff --git a/lldb/tools/lldb-dap/OutputRedirector.cpp b/lldb/tools/lldb-dap/OutputRedirector.cpp
index 7935e17a653bec3..84ee810f1bfa4cc 100644
--- a/lldb/tools/lldb-dap/OutputRedirector.cpp
+++ b/lldb/tools/lldb-dap/OutputRedirector.cpp
@@ -67,6 +67,9 @@ Error OutputRedirector::RedirectTo(std::function<void(StringRef)> callback) {
           continue;
         break;
       }
+      // Skip the null byte used to trigger a Stop.
+      if (bytes_count == 1 && buffer[0] == '\0')
+        continue;
 
       callback(StringRef(buffer, bytes_count));
     }
diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp
index 4b9b5fb4310ac58..8fa0bf007794118 100644
--- a/lldb/tools/lldb-dap/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/lldb-dap.cpp
@@ -48,6 +48,7 @@
 #include "llvm/Support/raw_ostream.h"
 #include <algorithm>
 #include <array>
+#include <condition_variable>
 #include <cstdint>
 #include <cstdio>
 #include <cstdlib>
@@ -5066,12 +5067,13 @@ int main(int argc, char *argv[]) {
       g_loop.AddPendingCallback(
           [](lldb_private::MainLoopBase &loop) { loop.RequestTermination(); });
     });
-    std::mutex active_dap_sessions_mutext;
-    std::set<DAP *> active_dap_sessions;
+    std::condition_variable dap_sessions_condition;
+    std::mutex dap_sessions_mutex;
+    std::map<Socket *, DAP *> dap_sessions;
     unsigned int clientCount = 0;
-    auto handle = listener->Accept(g_loop, [=, &active_dap_sessions_mutext,
-                                            &active_dap_sessions, &clientCount,
-                                            log = log.get()](
+    auto handle = listener->Accept(g_loop, [=, &dap_sessions_condition,
+                                            &dap_sessions_mutex, &dap_sessions,
+                                            &clientCount, log = log.get()](
                                                std::unique_ptr<Socket> sock) {
       std::string name = llvm::formatv("client_{0}", clientCount++).str();
       if (log) {
@@ -5083,8 +5085,8 @@ int main(int argc, char *argv[]) {
 
       // Move the client into a background thread to unblock accepting the next
       // client.
-      std::thread client([=, &active_dap_sessions_mutext, &active_dap_sessions,
-                          sock = std::move(sock)]() {
+      std::thread client([=, &dap_sessions_condition, &dap_sessions_mutex,
+                          &dap_sessions, sock = std::move(sock)]() {
         llvm::set_thread_name(name + ".runloop");
         StreamDescriptor input =
             StreamDescriptor::from_socket(sock->GetNativeSocket(), false);
@@ -5103,8 +5105,8 @@ int main(int argc, char *argv[]) {
         RegisterRequestCallbacks(dap);
 
         {
-          std::scoped_lock lock(active_dap_sessions_mutext);
-          active_dap_sessions.insert(&dap);
+          std::scoped_lock lock(dap_sessions_mutex);
+          dap_sessions[sock.get()] = &dap;
         }
 
         if (auto Err = dap.Loop()) {
@@ -5112,17 +5114,16 @@ int main(int argc, char *argv[]) {
                                       "DAP session error: ");
         }
 
-        {
-          std::scoped_lock lock(active_dap_sessions_mutext);
-          active_dap_sessions.erase(&dap);
-        }
-
         if (log) {
           auto now = std::chrono::duration<double>(
               std::chrono::system_clock::now().time_since_epoch());
           *log << llvm::formatv("{0:f9}", now.count()).str()
                << " client closed: " << name << "\n";
         }
+
+        std::unique_lock lock(dap_sessions_mutex);
+        dap_sessions.erase(sock.get());
+        std::notify_all_at_thread_exit(dap_sessions_condition, std::move(lock));
       });
       client.detach();
     });
@@ -5144,16 +5145,25 @@ int main(int argc, char *argv[]) {
               "clients...\n";
 
     bool client_failed = false;
-    std::scoped_lock lock(active_dap_sessions_mutext);
-    for (auto *dap : active_dap_sessions) {
-      auto error = dap->Disconnect();
-      if (error.Fail()) {
-        client_failed = true;
-        llvm::errs() << "DAP client " << dap->name
-                     << " disconnected failed: " << error.GetCString() << "\n";
+    {
+      std::scoped_lock lock(dap_sessions_mutex);
+      for (auto [sock, dap] : dap_sessions) {
+        auto error = dap->Disconnect();
+        if (error.Fail()) {
+          client_failed = true;
+          llvm::errs() << "DAP client " << dap->name
+                       << " disconnected failed: " << error.GetCString()
+                       << "\n";
+        }
+        // Close the socket to ensure the DAP::Loop read finishes.
+        sock->Close();
       }
     }
 
+    // Wait for all clients to finish disconnecting.
+    std::unique_lock lock(dap_sessions_mutex);
+    dap_sessions_condition.wait(lock, [&] { return dap_sessions.empty(); });
+
     return client_failed ? EXIT_FAILURE : EXIT_SUCCESS;
   }
 



More information about the lldb-commits mailing list