https://github.com/royitaqi updated https://github.com/llvm/llvm-project/pull/156803
>From 9af1b0029e3e19b521d472d8c94596709f990166 Mon Sep 17 00:00:00 2001 From: Roy Shi <roy...@meta.com> Date: Wed, 3 Sep 2025 22:23:20 -0700 Subject: [PATCH 1/7] [lldb-dap] Add optional TTL argument when using --connection --- lldb/tools/lldb-dap/Options.td | 7 ++++ lldb/tools/lldb-dap/tool/lldb-dap.cpp | 53 ++++++++++++++++++++++++++- 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/lldb/tools/lldb-dap/Options.td b/lldb/tools/lldb-dap/Options.td index 867753e9294a6..754b8c7d03568 100644 --- a/lldb/tools/lldb-dap/Options.td +++ b/lldb/tools/lldb-dap/Options.td @@ -61,3 +61,10 @@ def pre_init_command: S<"pre-init-command">, def: Separate<["-"], "c">, Alias<pre_init_command>, HelpText<"Alias for --pre-init-command">; + +def time_to_live: S<"time-to-live">, + MetaVarName<"<ttl>">, + HelpText<"When using --connection, the number of milliseconds to wait " + "for new connections at the beginning and after all clients have " + "disconnected. Not specifying this argument or specifying " + "non-positive values will wait indefinitely.">; diff --git a/lldb/tools/lldb-dap/tool/lldb-dap.cpp b/lldb/tools/lldb-dap/tool/lldb-dap.cpp index b74085f25f4e2..8b53e4d5cda83 100644 --- a/lldb/tools/lldb-dap/tool/lldb-dap.cpp +++ b/lldb/tools/lldb-dap/tool/lldb-dap.cpp @@ -258,7 +258,7 @@ validateConnection(llvm::StringRef conn) { static llvm::Error serveConnection(const Socket::SocketProtocol &protocol, const std::string &name, Log *log, const ReplMode default_repl_mode, - const std::vector<std::string> &pre_init_commands) { + const std::vector<std::string> &pre_init_commands, int ttl) { Status status; static std::unique_ptr<Socket> listener = Socket::Create(protocol, status); if (status.Fail()) { @@ -283,6 +283,21 @@ serveConnection(const Socket::SocketProtocol &protocol, const std::string &name, g_loop.AddPendingCallback( [](MainLoopBase &loop) { loop.RequestTermination(); }); }); + static MainLoopBase::TimePoint ttl_time_point; + static std::mutex ttl_mutex; + if (ttl > 0) { + std::scoped_lock<std::mutex> lock(ttl_mutex); + MainLoopBase::TimePoint future = + std::chrono::steady_clock::now() + std::chrono::milliseconds(ttl); + ttl_time_point = future; + g_loop.AddCallback( + [future](MainLoopBase &loop) { + if (ttl_time_point == future) { + loop.RequestTermination(); + } + }, + future); + } std::condition_variable dap_sessions_condition; std::mutex dap_sessions_mutex; std::map<MainLoop *, DAP *> dap_sessions; @@ -291,6 +306,12 @@ serveConnection(const Socket::SocketProtocol &protocol, const std::string &name, &dap_sessions_mutex, &dap_sessions, &clientCount]( std::unique_ptr<Socket> sock) { + if (ttl > 0) { + // Reset the keep alive timer, because we won't be killing the server + // while this connection is being served. + std::scoped_lock<std::mutex> lock(ttl_mutex); + ttl_time_point = MainLoopBase::TimePoint(); + } std::string client_name = llvm::formatv("client_{0}", clientCount++).str(); DAP_LOG(log, "({0}) client connected", client_name); @@ -327,6 +348,23 @@ serveConnection(const Socket::SocketProtocol &protocol, const std::string &name, std::unique_lock<std::mutex> lock(dap_sessions_mutex); dap_sessions.erase(&loop); std::notify_all_at_thread_exit(dap_sessions_condition, std::move(lock)); + + if (ttl > 0) { + // Start the countdown to kill the server at the end of each connection. + std::scoped_lock<std::mutex> lock(ttl_mutex); + MainLoopBase::TimePoint future = + std::chrono::steady_clock::now() + std::chrono::milliseconds(ttl); + // We don't need to take the max of `keep_alive_up_to` and `future`, + // because `future` must be the latest. + ttl_time_point = future; + g_loop.AddCallback( + [future](MainLoopBase &loop) { + if (ttl_time_point == future) { + loop.RequestTermination(); + } + }, + future); + } }); client.detach(); }); @@ -509,6 +547,17 @@ int main(int argc, char *argv[]) { } if (!connection.empty()) { + int ttl = 0; + llvm::opt::Arg *time_to_live = input_args.getLastArg(OPT_time_to_live); + if (time_to_live) { + llvm::StringRef time_to_live_value = time_to_live->getValue(); + if (time_to_live_value.getAsInteger(10, ttl)) { + llvm::errs() << "'" << time_to_live_value + << "' is not a valid time-to-live value\n"; + return EXIT_FAILURE; + } + } + auto maybeProtoclAndName = validateConnection(connection); if (auto Err = maybeProtoclAndName.takeError()) { llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), @@ -520,7 +569,7 @@ int main(int argc, char *argv[]) { std::string name; std::tie(protocol, name) = *maybeProtoclAndName; if (auto Err = serveConnection(protocol, name, log.get(), default_repl_mode, - pre_init_commands)) { + pre_init_commands, ttl)) { llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), "Connection failed: "); return EXIT_FAILURE; >From c78a38f0e5a10fdaa6859c4d6e49e73e16503969 Mon Sep 17 00:00:00 2001 From: Roy Shi <roy...@meta.com> Date: Thu, 4 Sep 2025 15:01:12 -0700 Subject: [PATCH 2/7] Address various comments about renaming and refactoring --- lldb/tools/lldb-dap/tool/lldb-dap.cpp | 98 +++++++++++++++------------ 1 file changed, 54 insertions(+), 44 deletions(-) diff --git a/lldb/tools/lldb-dap/tool/lldb-dap.cpp b/lldb/tools/lldb-dap/tool/lldb-dap.cpp index 8b53e4d5cda83..b12ee399ec7d3 100644 --- a/lldb/tools/lldb-dap/tool/lldb-dap.cpp +++ b/lldb/tools/lldb-dap/tool/lldb-dap.cpp @@ -223,6 +223,36 @@ static int DuplicateFileDescriptor(int fd) { #endif } +static void ResetTimeToLive(std::mutex &ttl_mutex, + MainLoopBase::TimePoint &ttl_time_point) { + std::scoped_lock<std::mutex> lock(ttl_mutex); + ttl_time_point = MainLoopBase::TimePoint(); +} + +static void TrackTimeToLive(MainLoop &loop, std::mutex &ttl_mutex, + MainLoopBase::TimePoint &ttl_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(ttl_mutex); + // We don't need to take the max of `ttl_time_point` and `next_checkpoint`, + // because `next_checkpoint` must be the latest. + ttl_time_point = next_checkpoint; + } + loop.AddCallback( + [&ttl_mutex, &ttl_time_point, next_checkpoint](MainLoopBase &loop) { + bool should_request_terimation; + { + std::scoped_lock<std::mutex> lock(ttl_mutex); + should_request_terimation = ttl_time_point == next_checkpoint; + } + if (should_request_terimation) + loop.RequestTermination(); + }, + next_checkpoint); +} + static llvm::Expected<std::pair<Socket::SocketProtocol, std::string>> validateConnection(llvm::StringRef conn) { auto uri = lldb_private::URI::Parse(conn); @@ -258,7 +288,8 @@ validateConnection(llvm::StringRef conn) { static llvm::Error serveConnection(const Socket::SocketProtocol &protocol, const std::string &name, Log *log, const ReplMode default_repl_mode, - const std::vector<std::string> &pre_init_commands, int ttl) { + const std::vector<std::string> &pre_init_commands, + std::optional<std::chrono::seconds> ttl_seconds) { Status status; static std::unique_ptr<Socket> listener = Socket::Create(protocol, status); if (status.Fail()) { @@ -283,21 +314,10 @@ serveConnection(const Socket::SocketProtocol &protocol, const std::string &name, g_loop.AddPendingCallback( [](MainLoopBase &loop) { loop.RequestTermination(); }); }); - static MainLoopBase::TimePoint ttl_time_point; - static std::mutex ttl_mutex; - if (ttl > 0) { - std::scoped_lock<std::mutex> lock(ttl_mutex); - MainLoopBase::TimePoint future = - std::chrono::steady_clock::now() + std::chrono::milliseconds(ttl); - ttl_time_point = future; - g_loop.AddCallback( - [future](MainLoopBase &loop) { - if (ttl_time_point == future) { - loop.RequestTermination(); - } - }, - future); - } + static MainLoopBase::TimePoint g_ttl_time_point; + static std::mutex g_ttl_mutex; + if (ttl_seconds) + TrackTimeToLive(g_loop, g_ttl_mutex, g_ttl_time_point, ttl_seconds.value()); std::condition_variable dap_sessions_condition; std::mutex dap_sessions_mutex; std::map<MainLoop *, DAP *> dap_sessions; @@ -306,12 +326,10 @@ serveConnection(const Socket::SocketProtocol &protocol, const std::string &name, &dap_sessions_mutex, &dap_sessions, &clientCount]( std::unique_ptr<Socket> sock) { - if (ttl > 0) { - // Reset the keep alive timer, because we won't be killing the server - // while this connection is being served. - std::scoped_lock<std::mutex> lock(ttl_mutex); - ttl_time_point = MainLoopBase::TimePoint(); - } + // Reset the keep alive timer, because we won't be killing the server + // while this connection is being served. + if (ttl_seconds) + ResetTimeToLive(g_ttl_mutex, g_ttl_time_point); std::string client_name = llvm::formatv("client_{0}", clientCount++).str(); DAP_LOG(log, "({0}) client connected", client_name); @@ -349,22 +367,10 @@ serveConnection(const Socket::SocketProtocol &protocol, const std::string &name, dap_sessions.erase(&loop); std::notify_all_at_thread_exit(dap_sessions_condition, std::move(lock)); - if (ttl > 0) { - // Start the countdown to kill the server at the end of each connection. - std::scoped_lock<std::mutex> lock(ttl_mutex); - MainLoopBase::TimePoint future = - std::chrono::steady_clock::now() + std::chrono::milliseconds(ttl); - // We don't need to take the max of `keep_alive_up_to` and `future`, - // because `future` must be the latest. - ttl_time_point = future; - g_loop.AddCallback( - [future](MainLoopBase &loop) { - if (ttl_time_point == future) { - loop.RequestTermination(); - } - }, - future); - } + // Start the countdown to kill the server at the end of each connection. + if (ttl_seconds) + TrackTimeToLive(g_loop, g_ttl_mutex, g_ttl_time_point, + ttl_seconds.value()); }); client.detach(); }); @@ -547,15 +553,19 @@ int main(int argc, char *argv[]) { } if (!connection.empty()) { - int ttl = 0; + std::optional<std::chrono::seconds> ttl_seconds; llvm::opt::Arg *time_to_live = input_args.getLastArg(OPT_time_to_live); if (time_to_live) { - llvm::StringRef time_to_live_value = time_to_live->getValue(); - if (time_to_live_value.getAsInteger(10, ttl)) { - llvm::errs() << "'" << time_to_live_value - << "' is not a valid time-to-live value\n"; + llvm::StringRef time_to_live_string_value = time_to_live->getValue(); + int time_to_live_int_value; + if (time_to_live_string_value.getAsInteger(10, time_to_live_int_value)) { + llvm::errs() << "'" << time_to_live_string_value + << "' is not a valid time-to-live value\n"; return EXIT_FAILURE; } + // Ignore non-positive values. + if (time_to_live_int_value > 0) + ttl_seconds = std::chrono::seconds(time_to_live_int_value); } auto maybeProtoclAndName = validateConnection(connection); @@ -569,7 +579,7 @@ int main(int argc, char *argv[]) { std::string name; std::tie(protocol, name) = *maybeProtoclAndName; if (auto Err = serveConnection(protocol, name, log.get(), default_repl_mode, - pre_init_commands, ttl)) { + pre_init_commands, ttl_seconds)) { llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), "Connection failed: "); return EXIT_FAILURE; >From 13b13583f8f14f0d8c094f8419cb77510cc214aa Mon Sep 17 00:00:00 2001 From: Roy Shi <roy...@meta.com> Date: Thu, 4 Sep 2025 15:20:58 -0700 Subject: [PATCH 3/7] Update text in Options.td --- lldb/tools/lldb-dap/Options.td | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lldb/tools/lldb-dap/Options.td b/lldb/tools/lldb-dap/Options.td index 754b8c7d03568..3d898a740e710 100644 --- a/lldb/tools/lldb-dap/Options.td +++ b/lldb/tools/lldb-dap/Options.td @@ -64,7 +64,9 @@ def: Separate<["-"], "c">, def time_to_live: S<"time-to-live">, MetaVarName<"<ttl>">, - HelpText<"When using --connection, the number of milliseconds to wait " - "for new connections at the beginning and after all clients have " - "disconnected. Not specifying this argument or specifying " - "non-positive values will wait indefinitely.">; + HelpText<"When using --connection, the number of seconds to wait for " + "new connections after the server has started and after all clients " + "have disconnected. New connections will reset the timer. When the " + "timer is reached, the server will be closed and the process will " + "exit. Not specifying this argument or specifying non-positive values " + "will cause the server to wait for new connections indefinitely.">; >From 3e927b02cf0b3466cc05a9dd891d79122d8926e7 Mon Sep 17 00:00:00 2001 From: Roy Shi <roy...@meta.com> Date: Mon, 8 Sep 2025 09:29:38 -0700 Subject: [PATCH 4/7] Rename to connection-timeout and print warning message --- lldb/tools/lldb-dap/Options.td | 16 ++-- lldb/tools/lldb-dap/tool/lldb-dap.cpp | 108 +++++++++++++++----------- 2 files changed, 69 insertions(+), 55 deletions(-) diff --git a/lldb/tools/lldb-dap/Options.td b/lldb/tools/lldb-dap/Options.td index 3d898a740e710..9fc7aad8ca9f7 100644 --- a/lldb/tools/lldb-dap/Options.td +++ b/lldb/tools/lldb-dap/Options.td @@ -62,11 +62,11 @@ def: Separate<["-"], "c">, Alias<pre_init_command>, HelpText<"Alias for --pre-init-command">; -def time_to_live: S<"time-to-live">, - MetaVarName<"<ttl>">, - HelpText<"When using --connection, the number of seconds to wait for " - "new connections after the server has started and after all clients " - "have disconnected. New connections will reset the timer. When the " - "timer is reached, the server will be closed and the process will " - "exit. Not specifying this argument or specifying non-positive values " - "will cause the server to wait for new connections indefinitely.">; +def connection_timeout: S<"connection-timeout">, + MetaVarName<"<timeout>">, + HelpText<"When using --connection, the number of seconds to wait for new " + "connections after the server has started and after all clients have " + "disconnected. Each new connection will reset the timeout. When the " + "timeout is reached, the server will be closed and the process will exit. " + "Not specifying this argument or specifying non-positive values will " + "cause the server to wait for new connections indefinitely.">; diff --git a/lldb/tools/lldb-dap/tool/lldb-dap.cpp b/lldb/tools/lldb-dap/tool/lldb-dap.cpp index b12ee399ec7d3..e79eca10c83a6 100644 --- a/lldb/tools/lldb-dap/tool/lldb-dap.cpp +++ b/lldb/tools/lldb-dap/tool/lldb-dap.cpp @@ -223,31 +223,30 @@ static int DuplicateFileDescriptor(int fd) { #endif } -static void ResetTimeToLive(std::mutex &ttl_mutex, - MainLoopBase::TimePoint &ttl_time_point) { - std::scoped_lock<std::mutex> lock(ttl_mutex); - ttl_time_point = MainLoopBase::TimePoint(); +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(); } -static void TrackTimeToLive(MainLoop &loop, std::mutex &ttl_mutex, - MainLoopBase::TimePoint &ttl_time_point, - std::chrono::seconds ttl_seconds) { +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(ttl_mutex); + 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. - ttl_time_point = next_checkpoint; + conncetion_timeout_time_point = next_checkpoint; } loop.AddCallback( - [&ttl_mutex, &ttl_time_point, next_checkpoint](MainLoopBase &loop) { - bool should_request_terimation; - { - std::scoped_lock<std::mutex> lock(ttl_mutex); - should_request_terimation = ttl_time_point == next_checkpoint; - } - if (should_request_terimation) + [&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); @@ -285,11 +284,11 @@ validateConnection(llvm::StringRef conn) { return make_error(); } -static llvm::Error -serveConnection(const Socket::SocketProtocol &protocol, const std::string &name, - Log *log, const ReplMode default_repl_mode, - const std::vector<std::string> &pre_init_commands, - std::optional<std::chrono::seconds> ttl_seconds) { +static llvm::Error serveConnection( + const Socket::SocketProtocol &protocol, const std::string &name, Log *log, + const ReplMode default_repl_mode, + const std::vector<std::string> &pre_init_commands, + std::optional<std::chrono::seconds> connection_timeout_seconds) { Status status; static std::unique_ptr<Socket> listener = Socket::Create(protocol, status); if (status.Fail()) { @@ -314,10 +313,12 @@ serveConnection(const Socket::SocketProtocol &protocol, const std::string &name, g_loop.AddPendingCallback( [](MainLoopBase &loop) { loop.RequestTermination(); }); }); - static MainLoopBase::TimePoint g_ttl_time_point; - static std::mutex g_ttl_mutex; - if (ttl_seconds) - TrackTimeToLive(g_loop, g_ttl_mutex, g_ttl_time_point, ttl_seconds.value()); + 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()); std::condition_variable dap_sessions_condition; std::mutex dap_sessions_mutex; std::map<MainLoop *, DAP *> dap_sessions; @@ -328,8 +329,9 @@ serveConnection(const Socket::SocketProtocol &protocol, const std::string &name, 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 (ttl_seconds) - ResetTimeToLive(g_ttl_mutex, g_ttl_time_point); + if (connection_timeout_seconds) + ResetConnectionTimeout(g_connection_timeout_mutex, + g_connection_timeout_time_point); std::string client_name = llvm::formatv("client_{0}", clientCount++).str(); DAP_LOG(log, "({0}) client connected", client_name); @@ -368,9 +370,10 @@ serveConnection(const Socket::SocketProtocol &protocol, const std::string &name, std::notify_all_at_thread_exit(dap_sessions_condition, std::move(lock)); // Start the countdown to kill the server at the end of each connection. - if (ttl_seconds) - TrackTimeToLive(g_loop, g_ttl_mutex, g_ttl_time_point, - ttl_seconds.value()); + if (connection_timeout_seconds) + TrackConnectionTimeout(g_loop, g_connection_timeout_mutex, + g_connection_timeout_time_point, + connection_timeout_seconds.value()); }); client.detach(); }); @@ -500,6 +503,31 @@ int main(int argc, char *argv[]) { connection.assign(path); } + std::optional<std::chrono::seconds> connection_timeout_seconds; + if (llvm::opt::Arg *connection_timeout_arg = + input_args.getLastArg(OPT_connection_timeout)) { + if (!connection.empty()) { + llvm::StringRef connection_timeout_string_value = + connection_timeout_arg->getValue(); + int connection_timeout_int_value; + if (connection_timeout_string_value.getAsInteger( + 10, connection_timeout_int_value)) { + llvm::errs() << "'" << connection_timeout_string_value + << "' is not a valid connection timeout value\n"; + return EXIT_FAILURE; + } + // Ignore non-positive values. + if (connection_timeout_int_value > 0) + connection_timeout_seconds = + std::chrono::seconds(connection_timeout_int_value); + } else { + llvm::errs() + << "\"--connection-timeout\" requires \"--connection\" to be " + "specified\n"; + return EXIT_FAILURE; + } + } + #if !defined(_WIN32) if (input_args.hasArg(OPT_wait_for_debugger)) { printf("Paused waiting for debugger to attach (pid = %i)...\n", getpid()); @@ -553,21 +581,6 @@ int main(int argc, char *argv[]) { } if (!connection.empty()) { - std::optional<std::chrono::seconds> ttl_seconds; - llvm::opt::Arg *time_to_live = input_args.getLastArg(OPT_time_to_live); - if (time_to_live) { - llvm::StringRef time_to_live_string_value = time_to_live->getValue(); - int time_to_live_int_value; - if (time_to_live_string_value.getAsInteger(10, time_to_live_int_value)) { - llvm::errs() << "'" << time_to_live_string_value - << "' is not a valid time-to-live value\n"; - return EXIT_FAILURE; - } - // Ignore non-positive values. - if (time_to_live_int_value > 0) - ttl_seconds = std::chrono::seconds(time_to_live_int_value); - } - auto maybeProtoclAndName = validateConnection(connection); if (auto Err = maybeProtoclAndName.takeError()) { llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), @@ -578,8 +591,9 @@ int main(int argc, char *argv[]) { Socket::SocketProtocol protocol; std::string name; std::tie(protocol, name) = *maybeProtoclAndName; - if (auto Err = serveConnection(protocol, name, log.get(), default_repl_mode, - pre_init_commands, ttl_seconds)) { + if (auto Err = + serveConnection(protocol, name, log.get(), default_repl_mode, + pre_init_commands, connection_timeout_seconds)) { llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), "Connection failed: "); return EXIT_FAILURE; >From b185ae3a5cf84809acb83f9af0301d87502fa1a1 Mon Sep 17 00:00:00 2001 From: Roy Shi <roy...@meta.com> Date: Mon, 8 Sep 2025 10:39:18 -0700 Subject: [PATCH 5/7] Add api tests --- .../test/tools/lldb-dap/dap_server.py | 5 ++ .../tools/lldb-dap/server/TestDAP_server.py | 59 ++++++++++++++++++- 2 files changed, 61 insertions(+), 3 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 0608ac3fd83be..b7ff1ffe1e018 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 @@ -1528,6 +1528,7 @@ def launch( env: Optional[dict[str, str]] = None, log_file: Optional[TextIO] = None, connection: Optional[str] = None, + connection_timeout: Optional[int] = None, ) -> tuple[subprocess.Popen, Optional[str]]: adapter_env = os.environ.copy() if env is not None: @@ -1541,6 +1542,10 @@ def launch( args.append("--connection") args.append(connection) + if connection_timeout is not None: + args.append("--connection-timeout") + args.append(str(connection_timeout)) + process = subprocess.Popen( args, stdin=subprocess.PIPE, 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 e01320c25b155..2ab6c7ed24710 100644 --- a/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py +++ b/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py @@ -5,6 +5,7 @@ import os import signal import tempfile +import time import dap_server from lldbsuite.test.decorators import * @@ -13,22 +14,28 @@ class TestDAP_server(lldbdap_testcase.DAPTestCaseBase): - def start_server(self, connection): + def start_server( + self, connection, connection_timeout=None, wait_seconds_for_termination=None + ): log_file_path = self.getBuildArtifact("dap.txt") (process, connection) = dap_server.DebugAdapterServer.launch( executable=self.lldbDAPExec, connection=connection, + connection_timeout=connection_timeout, log_file=log_file_path, ) def cleanup(): - process.terminate() + if wait_seconds_for_termination is not None: + process.wait(wait_seconds_for_termination) + else: + process.terminate() self.addTearDownHook(cleanup) return (process, connection) - def run_debug_session(self, connection, name): + def run_debug_session(self, connection, name, sleep_seconds_in_middle=None): self.dap_server = dap_server.DebugAdapterServer( connection=connection, ) @@ -41,6 +48,8 @@ def run_debug_session(self, connection, name): args=[name], disconnectAutomatically=False, ) + if sleep_seconds_in_middle is not None: + time.sleep(sleep_seconds_in_middle) self.set_source_breakpoints(source, [breakpoint_line]) self.continue_to_next_stop() self.continue_to_exit() @@ -108,3 +117,47 @@ def test_server_interrupt(self): self.dap_server.exit_status, "Process exited before interrupting lldb-dap server", ) + + @skipIfWindows + def test_connection_timeout_at_server_start(self): + """ + Test launching lldb-dap in server mode with connection timeout and waiting for it to terminate automatically when no client connects. + """ + self.build() + self.start_server( + connection="listen://localhost:0", + connection_timeout=1, + wait_seconds_for_termination=2, + ) + + @skipIfWindows + def test_connection_timeout_long_debug_session(self): + """ + Test launching lldb-dap in server mode with connection timeout and terminating the server after the a long debug session. + """ + self.build() + (_, connection) = self.start_server( + connection="listen://localhost:0", + connection_timeout=1, + wait_seconds_for_termination=2, + ) + # The connection timeout should not cut off the debug session + self.run_debug_session(connection, "Alice", 1.5) + + @skipIfWindows + 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. + """ + self.build() + (_, connection) = self.start_server( + connection="listen://localhost:0", + connection_timeout=1, + wait_seconds_for_termination=2, + ) + time.sleep(0.5) + # Should be able to connect to the server. + self.run_debug_session(connection, "Alice") + time.sleep(0.5) + # Should be able to connect to the server, because it's still within the connection timeout. + self.run_debug_session(connection, "Bob") >From c2862d6f77d3e05a323ead4cb820a083bd6f04ce Mon Sep 17 00:00:00 2001 From: Roy Shi <roy...@meta.com> Date: Mon, 8 Sep 2025 11:24:52 -0700 Subject: [PATCH 6/7] Add extension setting --- lldb/tools/lldb-dap/package.json | 7 +++++++ .../lldb-dap/src-ts/debug-configuration-provider.ts | 5 +++++ lldb/tools/lldb-dap/src-ts/lldb-dap-server.ts | 13 ++++++++++++- 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/lldb/tools/lldb-dap/package.json b/lldb/tools/lldb-dap/package.json index 8c6c1b4ae6ebb..9cc653cee405b 100644 --- a/lldb/tools/lldb-dap/package.json +++ b/lldb/tools/lldb-dap/package.json @@ -106,6 +106,13 @@ "markdownDescription": "Run lldb-dap in server mode.\n\nWhen enabled, lldb-dap will start a background server that will be reused between debug sessions. This allows caching of debug symbols between sessions and improves launch performance.", "default": false }, + "lldb-dap.connectionTimeout": { + "order": 0, + "scope": "resource", + "type": "number", + "markdownDescription": "When running lldb-dap in server mode, the time in seconds to wait for new connections after the server has started and after all clients have disconnected. Each new connection will reset the timeout. When the timeout is reached, the server will be closed and the process will exit. Specifying non-positive values will cause the server to wait for new connections indefinitely.", + "default": 0 + }, "lldb-dap.arguments": { "scope": "resource", "type": "array", diff --git a/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts b/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts index 1ae87116141f1..d35460ab68f00 100644 --- a/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts +++ b/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts @@ -207,10 +207,15 @@ export class LLDBDapConfigurationProvider config.get<boolean>("serverMode", false) && (await isServerModeSupported(executable.command)) ) { + const connectionTimeoutSeconds = config.get<number | undefined>( + "connectionTimeout", + undefined, + ); const serverInfo = await this.server.start( executable.command, executable.args, executable.options, + connectionTimeoutSeconds, ); if (!serverInfo) { return undefined; diff --git a/lldb/tools/lldb-dap/src-ts/lldb-dap-server.ts b/lldb/tools/lldb-dap/src-ts/lldb-dap-server.ts index 300b12d1cce1b..774be50053a17 100644 --- a/lldb/tools/lldb-dap/src-ts/lldb-dap-server.ts +++ b/lldb/tools/lldb-dap/src-ts/lldb-dap-server.ts @@ -33,8 +33,19 @@ export class LLDBDapServer implements vscode.Disposable { dapPath: string, args: string[], options?: child_process.SpawnOptionsWithoutStdio, + connectionTimeoutSeconds?: number, ): Promise<{ host: string; port: number } | undefined> { - const dapArgs = [...args, "--connection", "listen://localhost:0"]; + // Both the --connection and --connection-timeout arguments are subject to the shouldContinueStartup() check. + const connectionTimeoutArgs = + connectionTimeoutSeconds && connectionTimeoutSeconds > 0 + ? ["--connection-timeout", `${connectionTimeoutSeconds}`] + : []; + const dapArgs = [ + ...args, + "--connection", + "listen://localhost:0", + ...connectionTimeoutArgs, + ]; if (!(await this.shouldContinueStartup(dapPath, dapArgs, options?.env))) { return undefined; } >From bf0df0b9f575db5e3eb2dc77996938a54a0f4d24 Mon Sep 17 00:00:00 2001 From: Roy Shi <roy...@meta.com> Date: Tue, 9 Sep 2025 12:03:10 -0700 Subject: [PATCH 7/7] Add documentation into lldb/tools/lldb-dap/README.md --- lldb/tools/lldb-dap/README.md | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/lldb/tools/lldb-dap/README.md b/lldb/tools/lldb-dap/README.md index f88f3ced6f25f..39dabcc1342c8 100644 --- a/lldb/tools/lldb-dap/README.md +++ b/lldb/tools/lldb-dap/README.md @@ -275,9 +275,9 @@ User settings can set the default value for the following supported | **exitCommands** | [string] | `[]` | | **terminateCommands** | [string] | `[]` | -To adjust your settings, open the Settings editor via the -`File > Preferences > Settings` menu or press `Ctrl+`, on Windows/Linux and -`Cmd+`, on Mac. +To adjust your settings, open the Settings editor +via the `File > Preferences > Settings` menu or press `Ctrl+,` on Windows/Linux, +and the `VS Code > Settings... > Settings` menu or press `Cmd+,` on Mac. ## Debug Console @@ -372,6 +372,19 @@ for more details on Debug Adapter Protocol events and the VS Code [debug.onDidReceiveDebugSessionCustomEvent](https://code.visualstudio.com/api/references/vscode-api#debug.onDidReceiveDebugSessionCustomEvent) API for handling a custom event from an extension. +## Server Mode + +lldb-dap supports a server mode that can be enabled via the following user settings. + +| Setting | Type | Default | | +| -------------------------- | -------- | :-----: | --------- | +| **Server Mode** | string | `False` | Run lldb-dap in server mode. When enabled, lldb-dap will start a background server that will be reused between debug sessions. This allows caching of debug symbols between sessions and improves launch performance. +| **Connection Timeout** | number | `0` | When running lldb-dap in server mode, the time in seconds to wait for new connections after the server has started and after all clients have disconnected. Each new connection will reset the timeout. When the timeout is reached, the server will be closed and the process will exit. Specifying non-positive values will cause the server to wait for new connections indefinitely. + +To adjust your settings, open the Settings editor +via the `File > Preferences > Settings` menu or press `Ctrl+,` on Windows/Linux, +and the `VS Code > Settings... > Settings` menu or press `Cmd+,` on Mac. + ## Contributing `lldb-dap` and `lldb` are developed under the umbrella of the [LLVM project](https://llvm.org/). _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits