[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