[Lldb-commits] [lldb] [lldb-dap] Don't drop active sessions when another client disconnects. (PR #234292)

via lldb-commits lldb-commits at lists.llvm.org
Wed Oct 21 10:47:57 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-lldb

Author: Ebuka Ezike (da-viper)

<details>
<summary>Changes</summary>

When the connection-timeout is specified the server stop only after all clients have disconnected and the connection-timeout has passed,

Disconnecting a client should not trigger server shutdown

Fixes #<!-- -->234254

---
Full diff: https://github.com/llvm/llvm-project/pull/234292.diff


2 Files Affected:

- (modified) lldb/test/API/tools/lldb-dap/server/TestDAP_server.py (+15-5) 
- (modified) lldb/tools/lldb-dap/tool/lldb-dap.cpp (+61-51) 


``````````diff
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 34a09e80eec548..67d4f0c012de7e 100644
--- a/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py
+++ b/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py
@@ -112,21 +112,31 @@ def test_connection_timeout_long_debug_session(self):
     def test_connection_timeout_multiple_sessions(self):
         """
         Test launching lldb-dap in server mode with connection timeout and
-        terminating the server after the last debug session.
+        terminating the server only after the last debug session ends.
         """
         self.build()
+        connection_timeout = 1
         adapter = self.start_server(
             connection="listen://localhost:0",
-            connection_timeout=1,
+            connection_timeout=connection_timeout,
         )
         time.sleep(0.25)
         # Should be able to connect to the server.
         session1 = self.create_session(adapter, disconnect_automatically=False)
         self.run_debug_session(session1, "Alice")
-        time.sleep(0.25)
-        # Should be able to connect to the server, because it's still within the connection timeout.
+        # Connect while session1's countdown is running.
         session2 = self.create_session(adapter, disconnect_automatically=False)
-        self.run_debug_session(session2, "Bob")
+        self.launch_to_breakpoint(session2, "Bob")
+
+        # Another session connecting and disconnecting must not start the
+        # countdown while session2 is still active.
+        session3 = self.create_session(adapter, disconnect_automatically=False)
+        self.run_debug_session(session3, "Carol")
+
+        # Wait for longer than the connection timeout.
+        time.sleep(connection_timeout + 1)
+        self.assertTrue(adapter.is_alive, "expected the server to be running")
+        self.continue_to_exit_and_disconnect(session2, "Bob")
 
         # The server shuts down once the connection timeout passes after the
         # last session ends.
diff --git a/lldb/tools/lldb-dap/tool/lldb-dap.cpp b/lldb/tools/lldb-dap/tool/lldb-dap.cpp
index 9444f77ae31cae..86fcd50fdb3368 100644
--- a/lldb/tools/lldb-dap/tool/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/tool/lldb-dap.cpp
@@ -554,34 +554,55 @@ static int DuplicateFileDescriptor(int fd) {
 #endif
 }
 
-static void
-ResetConnectionTimeout(std::mutex &connection_timeout_mutex,
-                       MainLoopBase::TimePoint &conncetion_timeout_time_point) {
-  std::scoped_lock<std::mutex> lock(connection_timeout_mutex);
-  conncetion_timeout_time_point = MainLoopBase::TimePoint();
-}
+namespace {
+/// Shutdown the `loop` when there is no connected client and
+/// the timeout has passed. It runs forever if the timeout is zero.
+class IdleShutdownTimer {
+public:
+  IdleShutdownTimer(MainLoop &loop, std::chrono::seconds timeout)
+      : m_loop(loop), m_timeout(timeout) {
+    ScheduleShutdown();
+  }
 
-static void
-TrackConnectionTimeout(MainLoop &loop, std::mutex &connection_timeout_mutex,
-                       MainLoopBase::TimePoint &conncetion_timeout_time_point,
-                       std::chrono::seconds ttl_seconds) {
-  MainLoopBase::TimePoint next_checkpoint =
-      std::chrono::steady_clock::now() + std::chrono::seconds(ttl_seconds);
-  {
-    std::scoped_lock<std::mutex> lock(connection_timeout_mutex);
-    // We don't need to take the max of `ttl_time_point` and `next_checkpoint`,
-    // because `next_checkpoint` must be the latest.
-    conncetion_timeout_time_point = next_checkpoint;
+  void OnClientConnected() {
+    std::scoped_lock<std::mutex> lock(m_mutex);
+    ++m_clients;
   }
-  loop.AddCallback(
-      [&connection_timeout_mutex, &conncetion_timeout_time_point,
-       next_checkpoint](MainLoopBase &loop) {
-        std::scoped_lock<std::mutex> lock(connection_timeout_mutex);
-        if (conncetion_timeout_time_point == next_checkpoint)
-          loop.RequestTermination();
-      },
-      next_checkpoint);
-}
+
+  void OnClientDisconnected() {
+    std::scoped_lock<std::mutex> lock(m_mutex);
+    m_clients--;
+    if (m_clients == 0)
+      ScheduleShutdown();
+  }
+
+private:
+  void ScheduleShutdown() {
+    if (m_timeout.count() == 0)
+      return;
+
+    m_latest_callback_id++;
+    const uint32_t callback_id = m_latest_callback_id;
+
+    m_loop.AddCallback(
+        [this, callback_id](MainLoopBase &loop) {
+          std::scoped_lock<std::mutex> lock(m_mutex);
+
+          // A client may have connected, or a disconnected client added
+          // a callback.
+          if (m_clients == 0 && m_latest_callback_id == callback_id)
+            loop.RequestTermination();
+        },
+        std::chrono::steady_clock::now() + m_timeout);
+  }
+
+  MainLoop &m_loop;
+  std::mutex m_mutex;
+  const std::chrono::seconds m_timeout;
+  uint32_t m_latest_callback_id = 0;
+  uint32_t m_clients = 0;
+};
+} // namespace
 
 static llvm::Expected<std::pair<Socket::SocketProtocol, std::string>>
 validateConnection(llvm::StringRef conn) {
@@ -615,11 +636,11 @@ validateConnection(llvm::StringRef conn) {
   return make_error();
 }
 
-static llvm::Error serveConnection(
-    const Socket::SocketProtocol &protocol, llvm::StringRef name, Log &log,
-    const ReplMode default_repl_mode,
-    const std::vector<protocol::String> &pre_init_commands, bool no_lldbinit,
-    std::optional<std::chrono::seconds> connection_timeout_seconds) {
+static llvm::Error
+serveConnection(const Socket::SocketProtocol &protocol, llvm::StringRef name,
+                Log &log, const ReplMode default_repl_mode,
+                const std::vector<protocol::String> &pre_init_commands,
+                bool no_lldbinit, std::chrono::seconds connection_timeout) {
   Status status;
   static std::unique_ptr<Socket> listener = Socket::Create(protocol, status);
   if (status.Fail()) {
@@ -644,26 +665,21 @@ static llvm::Error serveConnection(
     g_loop.AddPendingCallback(
         [](MainLoopBase &loop) { loop.RequestTermination(); });
   });
-  static MainLoopBase::TimePoint g_connection_timeout_time_point;
-  static std::mutex g_connection_timeout_mutex;
-  if (connection_timeout_seconds)
-    TrackConnectionTimeout(g_loop, g_connection_timeout_mutex,
-                           g_connection_timeout_time_point,
-                           connection_timeout_seconds.value());
+  auto idle_timer =
+      std::make_shared<IdleShutdownTimer>(g_loop, connection_timeout);
   unsigned int clientCount = 0;
   auto handle = listener->Accept(g_loop, [=, &log, &clientCount](
                                              std::unique_ptr<Socket> sock) {
-    // Reset the keep alive timer, because we won't be killing the server
-    // while this connection is being served.
-    if (connection_timeout_seconds)
-      ResetConnectionTimeout(g_connection_timeout_mutex,
-                             g_connection_timeout_time_point);
+    // Keep the server alive while this connection is being served.
+    idle_timer->OnClientConnected();
     const std::string client_name = llvm::formatv("conn{0}", clientCount++);
     lldb::IOObjectSP io(std::move(sock));
 
     // Move the client into a background thread to unblock accepting the next
     // client.
     std::thread client([=, &log]() {
+      auto notify_disconnected =
+          llvm::scope_exit([&] { idle_timer->OnClientDisconnected(); });
       llvm::set_thread_name(client_name + ".runloop");
 
       Log client_log = log.WithPrefix("(" + client_name + ")");
@@ -695,11 +711,6 @@ static llvm::Error serveConnection(
       DAP_LOG(client_log, "client disconnected");
       // Unregister the DAP session from the global manager.
       DAPSessionManager::GetInstance().UnregisterSession(&loop);
-      // Start the countdown to kill the server at the end of each connection.
-      if (connection_timeout_seconds)
-        TrackConnectionTimeout(g_loop, g_connection_timeout_mutex,
-                               g_connection_timeout_time_point,
-                               connection_timeout_seconds.value());
     });
     client.detach();
   });
@@ -881,7 +892,7 @@ int main(int argc, char *argv[]) {
     connection.assign(path);
   }
 
-  std::optional<std::chrono::seconds> connection_timeout_seconds;
+  auto connection_timeout = std::chrono::seconds(0);
   if (llvm::opt::Arg *connection_timeout_arg =
           input_args.getLastArg(OPT_connection_timeout)) {
     if (!connection.empty()) {
@@ -896,8 +907,7 @@ int main(int argc, char *argv[]) {
       }
       // Ignore non-positive values.
       if (connection_timeout_int_value > 0)
-        connection_timeout_seconds =
-            std::chrono::seconds(connection_timeout_int_value);
+        connection_timeout = std::chrono::seconds(connection_timeout_int_value);
     } else {
       llvm::errs()
           << "\"--connection-timeout\" requires \"--connection\" to be "
@@ -977,7 +987,7 @@ int main(int argc, char *argv[]) {
     std::tie(protocol, name) = *maybeProtoclAndName;
     if (auto Err = serveConnection(protocol, name, log, default_repl_mode,
                                    pre_init_commands, no_lldbinit,
-                                   connection_timeout_seconds)) {
+                                   connection_timeout)) {
       llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(),
                                   "Connection failed: ");
       return EXIT_FAILURE;

``````````

</details>


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


More information about the lldb-commits mailing list