[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 Jan 30 10:41:48 PST 2025


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

>From f946fd70bb550320bb153a59b8acd702dc97e75d 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/2] [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         |  57 +++-
 .../test/tools/lldb-dap/lldbdap_testcase.py   |   6 +-
 lldb/test/API/tools/lldb-dap/server/Makefile  |   3 +
 .../tools/lldb-dap/server/TestDAP_server.py   |  84 ++++++
 lldb/test/API/tools/lldb-dap/server/main.c    |  10 +
 lldb/test/Shell/DAP/TestOptions.test          |   4 +-
 lldb/tools/lldb-dap/DAP.cpp                   |  12 +-
 lldb/tools/lldb-dap/DAP.h                     |   5 +-
 lldb/tools/lldb-dap/Options.td                |  22 +-
 lldb/tools/lldb-dap/lldb-dap.cpp              | 281 +++++++++++-------
 10 files changed, 351 insertions(+), 133 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 c29992ce9c7848..6a71579030afcf 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,61 @@ 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]
+                )
+
+            if connection.startswith("unix-connect://"):  # unix-connect:///path
+                s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+                s.connect(connection.removeprefix("unix-connect://"))
+            elif connection.startswith("connection://"):  # connection://[host]:port
+                host, port = connection.removeprefix("connection://").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):
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 a25466f07fa557..015c613956d86b 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 00000000000000..451278a0946ef2
--- /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 00000000000000..b37f6976a7591b
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py
@@ -0,0 +1,84 @@
+"""
+Test lldb-dap server integration.
+"""
+
+import os
+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 do_test_server(self, connection):
+        self.build()
+
+        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)
+
+        self.dap_server = dap_server.DebugAdaptorServer(
+            connection=server.connection,
+        )
+        program = self.getBuildArtifact("a.out")
+        source = "main.c"
+        breakpoint_line = line_number(source, "// breakpoint")
+
+        # Initial connection over the connection.
+        self.launch(
+            program,
+            args=["Alice"],
+            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, "Hello Alice!\r\n")
+        self.dap_server.request_disconnect()
+
+        # Second connection over the connection.
+        self.dap_server = dap_server.DebugAdaptorServer(
+            connection=server.connection,
+        )
+        self.launch(
+            program,
+            args=["Bob"],
+            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, "Hello Bob!\r\n")
+
+    def test_server_port(self):
+        """
+        Test launching a binary with a lldb-dap in server mode on a specific port.
+        """
+        self.do_test_server(connection="tcp://localhost:0")
+
+    @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.do_test_server(connection="unix://" + name)
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 00000000000000..446ae82532af57
--- /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 e37e9116e3cddb..d290cdae590fd6 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 a67abe582abd40..ac20445e3a1cf0 100644
--- a/lldb/tools/lldb-dap/DAP.cpp
+++ b/lldb/tools/lldb-dap/DAP.cpp
@@ -58,9 +58,9 @@ 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,
+         ReplMode repl_mode, StreamDescriptor input, StreamDescriptor output)
+    : 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),
@@ -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 846300cb945b0d..2190d941ea68ea 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,8 @@ 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,
+      ReplMode repl_mode, StreamDescriptor input, StreamDescriptor output);
   ~DAP();
   DAP(const DAP &rhs) = delete;
   void operator=(const DAP &rhs) = delete;
diff --git a/lldb/tools/lldb-dap/Options.td b/lldb/tools/lldb-dap/Options.td
index d7b4a065abec01..97a6ec118c47b5 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 9e0e7f21ce4fc7..1a5b024dcf3f36 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/MainLoop.h"
+#include "lldb/Host/MainLoopBase.h"
+#include "lldb/Host/Socket.h"
+#include "lldb/Host/common/TCPSocket.h"
+#include "lldb/Host/posix/DomainSocket.h"
+#include "lldb/Utility/Status.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/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 <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,10 @@ typedef int socklen_t;
 #endif
 
 using namespace lldb_dap;
+using lldb_private::MainLoop;
+using lldb_private::NativeSocket;
+using lldb_private::Socket;
+using lldb_private::Status;
 
 namespace {
 using namespace llvm::opt;
@@ -142,43 +157,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
@@ -4952,6 +4930,28 @@ static int DuplicateFileDescriptor(int fd) {
 #endif
 }
 
+static llvm::Expected<std::pair<Socket::SocketProtocol, std::string>>
+parseConnection(llvm::StringRef conn) {
+  if (conn.contains("://")) {
+    llvm::StringRef scheme, rest;
+    std::tie(scheme, rest) = conn.split("://");
+
+    if (scheme == "unix" || scheme == "unix-connect") {
+      return std::make_pair(Socket::ProtocolUnixDomain, rest.str());
+    }
+    if (scheme == "tcp" || scheme == "connect") {
+      return std::make_pair(Socket::ProtocolTcp, rest.str());
+    }
+  } else if (conn.starts_with("/")) {
+    return std::make_pair(Socket::ProtocolUnixDomain, conn.str());
+  } else if (conn.contains(":")) {
+    return std::make_pair(Socket::ProtocolTcp, conn.str());
+  }
+  return llvm::createStringError(
+      "expected '[unix://]/path' or '[tcp://][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,148 @@ 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 HandleClient =
+      [=, log = log.get()](std::string name, StreamDescriptor input,
+                           StreamDescriptor output, std::FILE *redirectOut,
+                           std::FILE *redirectErr) -> bool {
+    DAP dap = DAP(name, program_path.str(), log, default_repl_mode,
+                  std::move(input), std::move(output));
 
-    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 EXIT_FAILURE;
     }
 
-    redirectOut = stdout;
-    redirectErr = stderr;
+    RegisterRequestCallbacks(dap);
 
-    input = StreamDescriptor::from_file(fileno(stdin), false);
-    output = StreamDescriptor::from_file(stdout_fd, false);
-  }
+    dap.pre_init_commands = pre_init_commands;
 
-  DAP dap = DAP(program_path.str(), log.get(), default_repl_mode,
-                std::move(input), std::move(output));
+    // used only by TestVSCode_redirection_to_console.py
+    if (getenv("LLDB_DAP_TEST_STDOUT_STDERR_REDIRECTION") != nullptr)
+      redirection_test();
 
-  // 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;
-  }
+    if (auto Err = dap.Loop()) {
+      if (log)
+        *log << "Transport Error: " << llvm::toString(std::move(Err)) << "\n";
+      return false;
+    }
+    return true;
+  };
 
-  RegisterRequestCallbacks(dap);
+  if (!connection.empty()) {
+    auto maybeProtoclAndName = parseConnection(connection);
+    if (auto Err = maybeProtoclAndName.takeError()) {
+      llvm::errs() << "Invalid connection specification " << Err << "\n";
+      return EXIT_FAILURE;
+    }
 
-  for (const std::string &arg :
-       input_args.getAllArgValues(OPT_pre_init_command)) {
-    dap.pre_init_commands.push_back(arg);
-  }
+    Socket::SocketProtocol protocol;
+    std::string name;
+    std::tie(protocol, name) = *maybeProtoclAndName;
 
-  // used only by TestVSCode_redirection_to_console.py
-  if (getenv("LLDB_DAP_TEST_STDOUT_STDERR_REDIRECTION") != nullptr)
-    redirection_test();
+    Status error;
+    std::unique_ptr<Socket> listener = Socket::Create(protocol, error);
+    if (error.Fail()) {
+      llvm::errs() << "Failed to create listener for protocol "
+                   << Socket::FindSchemeByProtocol(protocol)
+                   << ", error: " << error.takeError() << "\n";
+      return EXIT_FAILURE;
+    }
 
-  bool CleanExit = true;
-  if (auto Err = dap.Loop()) {
+    error = listener->Listen(name, /* backlog */ 5);
+    if (error.Fail()) {
+      llvm::errs() << "Failed to listen, error: " << error.takeError() << "\n";
+      return EXIT_FAILURE;
+    }
+
+    std::string address =
+        llvm::join(listener->GetListeningConnectionURI(), ", ");
     if (log)
-      *log << "Transport Error: " << llvm::toString(std::move(Err)) << "\n";
-    CleanExit = false;
+      *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();
+
+    MainLoop mainloop;
+    mainloop.RegisterSignal(
+        SIGHUP, [](auto &RL) { RL.RequestTermination(); }, error);
+
+    unsigned int clientCount = 0;
+    auto OnAccept = [=, log = log.get(),
+                     &clientCount](std::unique_ptr<Socket> client) {
+      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} client connected: {1}", now.count(), name)
+                    .str()
+             << std::endl;
+      }
+      // Start a thread for each connection, unblocking the listening thread.
+      std::thread([=, client = std::move(client)]() {
+        HandleClient(
+            name,
+            StreamDescriptor::from_socket(client->GetNativeSocket(), false),
+            StreamDescriptor::from_socket(client->GetNativeSocket(), false),
+            /*=redirectOut*/ nullptr, /*=redirectErr*/ nullptr);
+      }).detach();
+    };
+
+    auto handles = listener->Accept(mainloop, OnAccept);
+    if (auto Err = handles.takeError()) {
+      llvm::errs() << "failed to register accept() with the main loop: " << Err
+                   << "\n";
+      return EXIT_FAILURE;
+    }
+
+    error = mainloop.Run();
+    if (error.Fail()) {
+      llvm::errs() << "failed to accept()" << error.takeError() << "\n";
+      return EXIT_FAILURE;
+    }
+
+    return EXIT_SUCCESS;
   }
 
-  return CleanExit ? EXIT_SUCCESS : EXIT_FAILURE;
+#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
+
+  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;
+  }
+
+  std::FILE *redirectOut = stdout;
+  std::FILE *redirectErr = stderr;
+
+  StreamDescriptor input = StreamDescriptor::from_file(fileno(stdin), false);
+  StreamDescriptor output = StreamDescriptor::from_file(stdout_fd, false);
+
+  return HandleClient("stdin/stdout", std::move(input), std::move(output),
+                      redirectOut, redirectErr)
+             ? EXIT_SUCCESS
+             : EXIT_FAILURE;
 }

>From 74b5c2e33e3855ce63a608a8f12247526e015416 Mon Sep 17 00:00:00 2001
From: John Harrison <harjohn at google.com>
Date: Thu, 30 Jan 2025 10:38:05 -0800
Subject: [PATCH 2/2] Addressing feedback:

* Trying to ensure we more uniformly handle errors. In some cases they were not properly consumed.
* Adjusted IOStream to use the existing `lldb::IOObjectSP` which greatly simplifies the implementation.
---
 .../test/tools/lldb-dap/dap_server.py         |  25 ++--
 .../tools/lldb-dap/server/TestDAP_server.py   |  42 +++---
 lldb/tools/lldb-dap/DAP.cpp                   |  16 +--
 lldb/tools/lldb-dap/DAP.h                     |   3 +-
 lldb/tools/lldb-dap/DAPForward.h              |   2 +
 lldb/tools/lldb-dap/IOStream.cpp              | 125 +++---------------
 lldb/tools/lldb-dap/IOStream.h                |  48 ++-----
 lldb/tools/lldb-dap/lldb-dap.cpp              | 125 +++++++++---------
 8 files changed, 132 insertions(+), 254 deletions(-)

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 6a71579030afcf..6d765e10236733 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
@@ -1207,11 +1207,12 @@ def __init__(
                     out.removeprefix(expected_prefix).rstrip("\r\n").split(",", 1)[0]
                 )
 
-            if connection.startswith("unix-connect://"):  # unix-connect:///path
+            scheme, address = connection.split("://")
+            if scheme == "unix-connect":  # unix-connect:///path
                 s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
-                s.connect(connection.removeprefix("unix-connect://"))
-            elif connection.startswith("connection://"):  # connection://[host]:port
-                host, port = connection.removeprefix("connection://").rsplit(":", 1)
+                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:
@@ -1390,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,
     )
 
@@ -1539,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/test/API/tools/lldb-dap/server/TestDAP_server.py b/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py
index b37f6976a7591b..59daa6117b44e4 100644
--- a/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py
+++ b/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py
@@ -3,6 +3,7 @@
 """
 
 import os
+import signal
 import tempfile
 
 import dap_server
@@ -12,9 +13,7 @@
 
 
 class TestDAP_server(lldbdap_testcase.DAPTestCaseBase):
-    def do_test_server(self, connection):
-        self.build()
-
+    def start_server(self, connection):
         log_file_path = self.getBuildArtifact("dap.txt")
         server = dap_server.DebugAdaptorServer(
             executable=self.lldbDAPExec,
@@ -28,46 +27,36 @@ def cleanup():
 
         self.addTearDownHook(cleanup)
 
+        return server
+
+    def run_debug_session(self, connection, name):
         self.dap_server = dap_server.DebugAdaptorServer(
-            connection=server.connection,
+            connection=connection,
         )
         program = self.getBuildArtifact("a.out")
         source = "main.c"
         breakpoint_line = line_number(source, "// breakpoint")
 
-        # Initial connection over the connection.
         self.launch(
             program,
-            args=["Alice"],
+            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, "Hello Alice!\r\n")
+        self.assertEqual(output, f"Hello {name}!\r\n")
         self.dap_server.request_disconnect()
 
-        # Second connection over the connection.
-        self.dap_server = dap_server.DebugAdaptorServer(
-            connection=server.connection,
-        )
-        self.launch(
-            program,
-            args=["Bob"],
-            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, "Hello Bob!\r\n")
-
     def test_server_port(self):
         """
         Test launching a binary with a lldb-dap in server mode on a specific port.
         """
-        self.do_test_server(connection="tcp://localhost:0")
+        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):
@@ -79,6 +68,9 @@ def test_server_unix_socket(self):
 
         def cleanup():
             os.unlink(name)
-
         self.addTearDownHook(cleanup)
-        self.do_test_server(connection="unix://" + name)
+
+        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/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp
index ac20445e3a1cf0..be79fdb7f5a0b4 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"
@@ -59,11 +58,12 @@ const char DEV_NULL[] = "/dev/null";
 namespace lldb_dap {
 
 DAP::DAP(std::string name, llvm::StringRef path, std::ofstream *log,
-         ReplMode repl_mode, StreamDescriptor input, StreamDescriptor output)
+         lldb::IOObjectSP input, lldb::IOObjectSP 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),
@@ -230,10 +230,10 @@ void DAP::StopIO() {
 // "Content-Length:" field followed by the length, followed by the raw
 // JSON bytes.
 void DAP::SendJSON(const std::string &json_str) {
-  output.write_full("Content-Length: ");
-  output.write_full(llvm::utostr(json_str.size()));
-  output.write_full("\r\n\r\n");
-  output.write_full(json_str);
+  output.write_full(log, "Content-Length: ");
+  output.write_full(log, llvm::utostr(json_str.size()));
+  output.write_full(log, "\r\n\r\n");
+  output.write_full(log, json_str);
 }
 
 // Serialize the JSON value into a string and send the JSON packet to
diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h
index 2190d941ea68ea..263efb344f94e0 100644
--- a/lldb/tools/lldb-dap/DAP.h
+++ b/lldb/tools/lldb-dap/DAP.h
@@ -205,7 +205,8 @@ struct DAP {
   std::string last_nonempty_var_expression;
 
   DAP(std::string name, llvm::StringRef path, std::ofstream *log,
-      ReplMode repl_mode, StreamDescriptor input, StreamDescriptor output);
+      lldb::IOObjectSP input, lldb::IOObjectSP 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 0196d83dcd6a91..15e286f3d08dc1 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/IOStream.cpp b/lldb/tools/lldb-dap/IOStream.cpp
index d2e8ec40b0a7b8..01214daae4aa60 100644
--- a/lldb/tools/lldb-dap/IOStream.cpp
+++ b/lldb/tools/lldb-dap/IOStream.cpp
@@ -7,83 +7,21 @@
 //===----------------------------------------------------------------------===//
 
 #include "IOStream.h"
-
-#if defined(_WIN32)
-#include <io.h>
-#else
-#include <netinet/in.h>
-#include <sys/socket.h>
-#include <unistd.h>
-#endif
-
+#include "lldb/Utility/IOObject.h" // IWYU pragma: keep
+#include "lldb/Utility/Status.h"
 #include <fstream>
-#include <string>
 
 using namespace lldb_dap;
 
-StreamDescriptor::StreamDescriptor() = default;
-
-StreamDescriptor::StreamDescriptor(StreamDescriptor &&other) {
-  *this = std::move(other);
-}
-
-StreamDescriptor::~StreamDescriptor() {
-  if (!m_close)
-    return;
-
-  if (m_is_socket)
-#if defined(_WIN32)
-    ::closesocket(m_socket);
-#else
-    ::close(m_socket);
-#endif
-  else
-    ::close(m_fd);
-}
-
-StreamDescriptor &StreamDescriptor::operator=(StreamDescriptor &&other) {
-  m_close = other.m_close;
-  other.m_close = false;
-  m_is_socket = other.m_is_socket;
-  if (m_is_socket)
-    m_socket = other.m_socket;
-  else
-    m_fd = other.m_fd;
-  return *this;
-}
-
-StreamDescriptor StreamDescriptor::from_socket(SOCKET s, bool close) {
-  StreamDescriptor sd;
-  sd.m_is_socket = true;
-  sd.m_socket = s;
-  sd.m_close = close;
-  return sd;
-}
-
-StreamDescriptor StreamDescriptor::from_file(int fd, bool close) {
-  StreamDescriptor sd;
-  sd.m_is_socket = false;
-  sd.m_fd = fd;
-  sd.m_close = close;
-  return sd;
-}
-
-bool OutputStream::write_full(llvm::StringRef str) {
-  while (!str.empty()) {
-    int bytes_written = 0;
-    if (descriptor.m_is_socket)
-      bytes_written = ::send(descriptor.m_socket, str.data(), str.size(), 0);
-    else
-      bytes_written = ::write(descriptor.m_fd, str.data(), str.size());
-
-    if (bytes_written < 0) {
-      if (errno == EINTR || errno == EAGAIN)
-        continue;
-      return false;
-    }
-    str = str.drop_front(bytes_written);
+bool OutputStream::write_full(std::ofstream *log, llvm::StringRef str) {
+  size_t num_bytes = str.size();
+  auto status = descriptor->Write(str.data(), num_bytes);
+  if (status.Fail()) {
+    std::string error = llvm::toString(status.takeError());
+    if (log)
+      *log << error << "\n";
+    return false;
   }
-
   return true;
 }
 
@@ -91,42 +29,17 @@ bool InputStream::read_full(std::ofstream *log, size_t length,
                             std::string &text) {
   std::string data;
   data.resize(length);
-
   char *ptr = &data[0];
-  while (length != 0) {
-    int bytes_read = 0;
-    if (descriptor.m_is_socket)
-      bytes_read = ::recv(descriptor.m_socket, ptr, length, 0);
-    else
-      bytes_read = ::read(descriptor.m_fd, ptr, length);
+  size_t num_bytes = length;
+  auto status = descriptor->Read(ptr, num_bytes);
 
-    if (bytes_read == 0) {
-      if (log)
-        *log << "End of file (EOF) reading from input file.\n";
-      return false;
-    }
-    if (bytes_read < 0) {
-      int reason = 0;
-#if defined(_WIN32)
-      if (descriptor.m_is_socket)
-        reason = WSAGetLastError();
-      else
-        reason = errno;
-#else
-      reason = errno;
-      if (reason == EINTR || reason == EAGAIN)
-        continue;
-#endif
-
-      if (log)
-        *log << "Error " << reason << " reading from input file.\n";
-      return false;
-    }
-
-    assert(bytes_read >= 0 && (size_t)bytes_read <= length);
-    ptr += bytes_read;
-    length -= bytes_read;
+  if (status.Fail() || length != num_bytes) {
+    std::string error = llvm::toString(status.takeError());
+    if (log)
+      *log << error << "\n";
+    return false;
   }
+
   text += data;
   return true;
 }
@@ -151,7 +64,7 @@ bool InputStream::read_expected(std::ofstream *log, llvm::StringRef expected) {
   if (expected != result) {
     if (log)
       *log << "Warning: Expected '" << expected.str() << "', got '" << result
-           << "\n";
+           << "'\n";
   }
   return true;
 }
diff --git a/lldb/tools/lldb-dap/IOStream.h b/lldb/tools/lldb-dap/IOStream.h
index 74889eb2e5a866..7d56e08afdfca6 100644
--- a/lldb/tools/lldb-dap/IOStream.h
+++ b/lldb/tools/lldb-dap/IOStream.h
@@ -9,50 +9,17 @@
 #ifndef LLDB_TOOLS_LLDB_DAP_IOSTREAM_H
 #define LLDB_TOOLS_LLDB_DAP_IOSTREAM_H
 
-#if defined(_WIN32)
-// We need to #define NOMINMAX in order to skip `min()` and `max()` macro
-// definitions that conflict with other system headers.
-// We also need to #undef GetObject (which is defined to GetObjectW) because
-// the JSON code we use also has methods named `GetObject()` and we conflict
-// against these.
-#define NOMINMAX
-#include <windows.h>
-#else
-typedef int SOCKET;
-#endif
-
+#include "lldb/lldb-forward.h"
 #include "llvm/ADT/StringRef.h"
-
 #include <fstream>
-#include <string>
 
-// Windows requires different system calls for dealing with sockets and other
-// types of files, so we can't simply have one code path that just uses read
-// and write everywhere.  So we need an abstraction in order to allow us to
-// treat them identically.
 namespace lldb_dap {
-struct StreamDescriptor {
-  StreamDescriptor();
-  ~StreamDescriptor();
-  StreamDescriptor(StreamDescriptor &&other);
-
-  StreamDescriptor &operator=(StreamDescriptor &&other);
-
-  static StreamDescriptor from_socket(SOCKET s, bool close);
-  static StreamDescriptor from_file(int fd, bool close);
-
-  bool m_is_socket = false;
-  bool m_close = false;
-  union {
-    int m_fd;
-    SOCKET m_socket;
-  };
-};
 
 struct InputStream {
-  StreamDescriptor descriptor;
+  // IOObject represent either a FD or socket.
+  lldb::IOObjectSP descriptor;
 
-  explicit InputStream(StreamDescriptor descriptor)
+  explicit InputStream(lldb::IOObjectSP descriptor)
       : descriptor(std::move(descriptor)) {}
 
   bool read_full(std::ofstream *log, size_t length, std::string &text);
@@ -63,12 +30,13 @@ struct InputStream {
 };
 
 struct OutputStream {
-  StreamDescriptor descriptor;
+  // IOObject represent either a FD or socket.
+  lldb::IOObjectSP descriptor;
 
-  explicit OutputStream(StreamDescriptor descriptor)
+  explicit OutputStream(lldb::IOObjectSP descriptor)
       : descriptor(std::move(descriptor)) {}
 
-  bool write_full(llvm::StringRef str);
+  bool write_full(std::ofstream *log, llvm::StringRef str);
 };
 } // namespace lldb_dap
 
diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp
index 1a5b024dcf3f36..803b28b3328eec 100644
--- a/lldb/tools/lldb-dap/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/lldb-dap.cpp
@@ -21,12 +21,13 @@
 #include "lldb/API/SBStream.h"
 #include "lldb/API/SBStringList.h"
 #include "lldb/Host/Config.h"
+#include "lldb/Host/File.h"
 #include "lldb/Host/MainLoop.h"
 #include "lldb/Host/MainLoopBase.h"
 #include "lldb/Host/Socket.h"
-#include "lldb/Host/common/TCPSocket.h"
-#include "lldb/Host/posix/DomainSocket.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"
@@ -44,6 +45,7 @@
 #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>
@@ -59,6 +61,7 @@
 #include <optional>
 #include <ostream>
 #include <set>
+#include <string>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <thread>
@@ -95,6 +98,8 @@ typedef int socklen_t;
 
 using namespace lldb_dap;
 using lldb_private::MainLoop;
+using lldb_private::MainLoopBase;
+using lldb_private::NativeFile;
 using lldb_private::NativeSocket;
 using lldb_private::Socket;
 using lldb_private::Status;
@@ -4835,10 +4840,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.
@@ -4931,24 +4936,25 @@ static int DuplicateFileDescriptor(int fd) {
 }
 
 static llvm::Expected<std::pair<Socket::SocketProtocol, std::string>>
-parseConnection(llvm::StringRef conn) {
-  if (conn.contains("://")) {
-    llvm::StringRef scheme, rest;
-    std::tie(scheme, rest) = conn.split("://");
+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 (scheme == "unix" || scheme == "unix-connect") {
-      return std::make_pair(Socket::ProtocolUnixDomain, rest.str());
-    }
-    if (scheme == "tcp" || scheme == "connect") {
-      return std::make_pair(Socket::ProtocolTcp, rest.str());
-    }
-  } else if (conn.starts_with("/")) {
-    return std::make_pair(Socket::ProtocolUnixDomain, conn.str());
-  } else if (conn.contains(":")) {
-    return std::make_pair(Socket::ProtocolTcp, conn.str());
+  if (uri && (uri->scheme == "unix" || uri->scheme == "unix-connect" ||
+              uri->path != "/")) {
+    return std::make_pair(Socket::ProtocolUnixDomain, uri->path.str());
   }
+
   return llvm::createStringError(
-      "expected '[unix://]/path' or '[tcp://][host]:port', got '%s'.",
+      "Unsupported connection specifier, expected 'unix-connect:///path' or "
+      "'connect://[host]:port', got '%s'.",
       conn.str().c_str());
 }
 
@@ -5060,40 +5066,40 @@ int main(int argc, char *argv[]) {
   }
 
   auto HandleClient =
-      [=, log = log.get()](std::string name, StreamDescriptor input,
-                           StreamDescriptor output, std::FILE *redirectOut,
+      [=, log = log.get()](std::string name, lldb::IOObjectSP input,
+                           lldb::IOObjectSP output, std::FILE *redirectOut,
                            std::FILE *redirectErr) -> bool {
-    DAP dap = DAP(name, program_path.str(), log, default_repl_mode,
-                  std::move(input), std::move(output));
+    DAP dap = DAP(name, program_path.str(), log, 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(redirectOut, redirectErr)) {
       llvm::logAllUnhandledErrors(
           std::move(Err), llvm::errs(),
           "Failed to configure lldb-dap IO operations: ");
-      return EXIT_FAILURE;
+      return false;
     }
 
     RegisterRequestCallbacks(dap);
 
-    dap.pre_init_commands = pre_init_commands;
-
     // 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: " << llvm::toString(std::move(Err)) << "\n";
+        *log << "Transport Error: " << errorMessage << "\n";
       return false;
     }
     return true;
   };
 
   if (!connection.empty()) {
-    auto maybeProtoclAndName = parseConnection(connection);
+    auto maybeProtoclAndName = validateConnection(connection);
     if (auto Err = maybeProtoclAndName.takeError()) {
-      llvm::errs() << "Invalid connection specification " << Err << "\n";
+      llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(),
+                                  "Invalid connection: ");
       return EXIT_FAILURE;
     }
 
@@ -5104,15 +5110,15 @@ int main(int argc, char *argv[]) {
     Status error;
     std::unique_ptr<Socket> listener = Socket::Create(protocol, error);
     if (error.Fail()) {
-      llvm::errs() << "Failed to create listener for protocol "
-                   << Socket::FindSchemeByProtocol(protocol)
-                   << ", error: " << error.takeError() << "\n";
+      llvm::logAllUnhandledErrors(error.takeError(), llvm::errs(),
+                                  "Failed to create socket listener: ");
       return EXIT_FAILURE;
     }
 
     error = listener->Listen(name, /* backlog */ 5);
     if (error.Fail()) {
-      llvm::errs() << "Failed to listen, error: " << error.takeError() << "\n";
+      llvm::logAllUnhandledErrors(error.takeError(), llvm::errs(),
+                                  "Failed to listen for connections: ");
       return EXIT_FAILURE;
     }
 
@@ -5127,44 +5133,42 @@ int main(int argc, char *argv[]) {
     llvm::outs().flush();
 
     MainLoop mainloop;
-    mainloop.RegisterSignal(
-        SIGHUP, [](auto &RL) { RL.RequestTermination(); }, error);
-
     unsigned int clientCount = 0;
-    auto OnAccept = [=, log = log.get(),
-                     &clientCount](std::unique_ptr<Socket> client) {
+    llvm::DefaultThreadPool pool(llvm::optimal_concurrency());
+    auto OnAccept = [=, &pool, &clientCount,
+                     log = log.get()](std::unique_ptr<Socket> client) {
       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} client connected: {1}", now.count(), name)
-                    .str()
-             << std::endl;
+        *log << llvm::formatv("{0:f9}", now.count()).str()
+             << " client connected: " << name << "\n";
       }
-      // Start a thread for each connection, unblocking the listening thread.
-      std::thread([=, client = std::move(client)]() {
-        HandleClient(
-            name,
-            StreamDescriptor::from_socket(client->GetNativeSocket(), false),
-            StreamDescriptor::from_socket(client->GetNativeSocket(), false),
-            /*=redirectOut*/ nullptr, /*=redirectErr*/ nullptr);
-      }).detach();
+
+      // Move the client into the connection pool to unblock accepting the next
+      // client.
+      lldb::IOObjectSP IO(std::move(client));
+      pool.async(HandleClient, name, IO, IO,
+                 /*redirectOut=*/nullptr, /*redirectErr=*/nullptr);
     };
 
     auto handles = listener->Accept(mainloop, OnAccept);
     if (auto Err = handles.takeError()) {
-      llvm::errs() << "failed to register accept() with the main loop: " << Err
-                   << "\n";
+      llvm::logAllUnhandledErrors(error.takeError(), llvm::errs(),
+                                  "Failed to register accept handler: ");
       return EXIT_FAILURE;
     }
 
     error = mainloop.Run();
     if (error.Fail()) {
-      llvm::errs() << "failed to accept()" << error.takeError() << "\n";
+      llvm::logAllUnhandledErrors(error.takeError(), llvm::errs(),
+                                  "Main run loop failed: ");
       return EXIT_FAILURE;
     }
 
+    pool.wait();
+
     return EXIT_SUCCESS;
   }
 
@@ -5187,14 +5191,11 @@ int main(int argc, char *argv[]) {
     return EXIT_FAILURE;
   }
 
-  std::FILE *redirectOut = stdout;
-  std::FILE *redirectErr = stderr;
-
-  StreamDescriptor input = StreamDescriptor::from_file(fileno(stdin), false);
-  StreamDescriptor output = StreamDescriptor::from_file(stdout_fd, false);
+  lldb::IOObjectSP input = std::make_shared<NativeFile>(stdin, false);
+  lldb::IOObjectSP output = std::make_shared<NativeFile>(
+      stdout_fd, lldb_private::NativeFile::eOpenOptionWriteOnly, false);
 
-  return HandleClient("stdin/stdout", std::move(input), std::move(output),
-                      redirectOut, redirectErr)
-             ? EXIT_SUCCESS
-             : EXIT_FAILURE;
+  bool status = HandleClient("stdin/stdout", std::move(input),
+                             std::move(output), stdout, stderr);
+  return status ? EXIT_SUCCESS : EXIT_FAILURE;
 }



More information about the lldb-commits mailing list