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

Pavel Labath via lldb-commits lldb-commits at lists.llvm.org
Thu Feb 6 02:06:01 PST 2025


================
@@ -5058,72 +5018,187 @@ 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)
+  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);
+  }
+
+  if (!connection.empty()) {
+    auto maybeProtoclAndName = validateConnection(connection);
+    if (auto Err = maybeProtoclAndName.takeError()) {
+      llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(),
+                                  "Invalid connection: ");
       return EXIT_FAILURE;
+    }
 
-    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
+    Socket::SocketProtocol protocol;
+    std::string name;
+    std::tie(protocol, name) = *maybeProtoclAndName;
 
-    int stdout_fd = DuplicateFileDescriptor(fileno(stdout));
-    if (stdout_fd == -1) {
-      llvm::logAllUnhandledErrors(
-          llvm::errorCodeToError(llvm::errnoAsErrorCode()), llvm::errs(),
-          "Failed to configure stdout redirect: ");
+    Status 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: ");
       return EXIT_FAILURE;
     }
 
-    redirectOut = stdout;
-    redirectErr = stderr;
+    error = listener->Listen(name, /*backlog=*/5);
+    if (error.Fail()) {
+      llvm::logAllUnhandledErrors(error.takeError(), llvm::errs(),
+                                  "Failed to listen for connections: ");
+      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();
+
+    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;
+    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>(
+            std::chrono::system_clock::now().time_since_epoch());
+        *log << llvm::formatv("{0:f9}", now.count()).str()
+             << " client connected: " << name << "\n";
+      }
+
+      // 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)]() {
+        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;
+    }
+
+    error = g_loop.Run();
+    if (error.Fail()) {
+      llvm::logAllUnhandledErrors(error.takeError(), llvm::errs(),
+                                  "MainLoop failed: ");
+      return EXIT_FAILURE;
+    }
+
+    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;
----------------
labath wrote:

There's a race here between this thread exiting (and destroying the `active_dap_sessions` list) and the now-disconnected threads trying to remove themselves from it.

I think that could be fixed by waiting until this list becomes empty (ideally using the aforementioned [std::notify_all_at_thread_exit](https://en.cppreference.com/w/cpp/thread/notify_all_at_thread_exit) to delay cond_var notification until the thread is well into shutting itself down.

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


More information about the lldb-commits mailing list