Skip to content

[lldb-dap] Adding support for cancelling a request. #130169

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Apr 14, 2025

Conversation

ashgti
Copy link
Contributor

@ashgti ashgti commented Mar 6, 2025

Adding support for cancelling requests.

There are two forms of request cancellation.

  • Preemptively cancelling a request that is in the queue.
  • Actively cancelling the in progress request as a best effort attempt using SBDebugger.RequestInterrupt().

Copy link
Member

@vogelsgesang vogelsgesang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just a very cursory review. I will review this more closely as soon as the preparatory commits landed. Those stacked PRs are a bit hard to review

// },
llvm::Expected<CancelResponseBody>
CancelRequestHandler::Run(const CancelArguments &arguments) const {
/* no-op, simple ack of the request. */
Copy link
Member

@vogelsgesang vogelsgesang Mar 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would have expected that we do something here. Or at least a comment which explains me, why we not don't have to do anything here...

This comment currently explains the "what?" (which is kind of obvious from the code). I would prefer a comment explaining the "why?" (which is much harder to reconstruct by reading the code)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated the comment to explain that this is handled in the DAP::Loop instead.

@ashgti ashgti requested a review from labath March 18, 2025 19:45
@ashgti ashgti force-pushed the cancel-request branch 2 times, most recently from 6bb322f to 8b71292 Compare March 18, 2025 21:08
@ashgti ashgti marked this pull request as ready for review March 18, 2025 21:09
@ashgti ashgti requested a review from JDevlieghere as a code owner March 18, 2025 21:09
@llvmbot llvmbot added the lldb label Mar 18, 2025
@llvmbot
Copy link
Member

llvmbot commented Mar 18, 2025

@llvm/pr-subscribers-lldb

Author: John Harrison (ashgti)

Changes

Adding support for cancelling requests.

There are two forms of request cancellation.

  • Preemptively cancelling a request that is in the queue.
  • Actively cancelling the in progress request as a best effort attempt using SBDebugger.RequestInterrupt().

Patch is 24.20 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/130169.diff

15 Files Affected:

  • (added) lldb/test/API/tools/lldb-dap/cancel/Makefile (+3)
  • (added) lldb/test/API/tools/lldb-dap/cancel/TestDAP_cancel.py (+101)
  • (added) lldb/test/API/tools/lldb-dap/cancel/main.c (+6)
  • (modified) lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py (+1)
  • (modified) lldb/tools/lldb-dap/CMakeLists.txt (+1)
  • (modified) lldb/tools/lldb-dap/DAP.cpp (+136-9)
  • (modified) lldb/tools/lldb-dap/DAP.h (+3)
  • (added) lldb/tools/lldb-dap/Handler/CancelRequestHandler.cpp (+55)
  • (modified) lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp (+2)
  • (modified) lldb/tools/lldb-dap/Handler/RequestHandler.h (+10)
  • (modified) lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp (+7)
  • (modified) lldb/tools/lldb-dap/Protocol/ProtocolRequests.h (+20)
  • (modified) lldb/tools/lldb-dap/Transport.cpp (+28-9)
  • (modified) lldb/tools/lldb-dap/Transport.h (+2-1)
  • (modified) lldb/tools/lldb-dap/lldb-dap.cpp (+4-1)
diff --git a/lldb/test/API/tools/lldb-dap/cancel/Makefile b/lldb/test/API/tools/lldb-dap/cancel/Makefile
new file mode 100644
index 0000000000000..10495940055b6
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/cancel/Makefile
@@ -0,0 +1,3 @@
+C_SOURCES := main.c
+
+include Makefile.rules
diff --git a/lldb/test/API/tools/lldb-dap/cancel/TestDAP_cancel.py b/lldb/test/API/tools/lldb-dap/cancel/TestDAP_cancel.py
new file mode 100644
index 0000000000000..f3b2f9fcb7a92
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/cancel/TestDAP_cancel.py
@@ -0,0 +1,101 @@
+"""
+Test lldb-dap cancel request
+"""
+
+import time
+
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+import lldbdap_testcase
+
+
+class TestDAP_launch(lldbdap_testcase.DAPTestCaseBase):
+    def send_async_req(self, command: str, arguments={}) -> int:
+        seq = self.dap_server.sequence
+        self.dap_server.send_packet(
+            {
+                "type": "request",
+                "command": command,
+                "arguments": arguments,
+            }
+        )
+        return seq
+
+    def async_blocking_request(self, duration: float) -> int:
+        """
+        Sends an evaluate request that will sleep for the specified duration to
+        block the request handling thread.
+        """
+        return self.send_async_req(
+            command="evaluate",
+            arguments={
+                "expression": '`script import time; print("starting sleep", file=lldb.debugger.GetOutputFileHandle()); time.sleep({})'.format(
+                    duration
+                ),
+                "context": "repl",
+            },
+        )
+
+    def async_cancel(self, requestId: int) -> int:
+        return self.send_async_req(command="cancel", arguments={"requestId": requestId})
+
+    def test_pending_request(self):
+        """
+        Tests cancelling a pending request.
+        """
+        program = self.getBuildArtifact("a.out")
+        self.build_and_launch(program, stopOnEntry=True)
+        self.continue_to_next_stop()
+
+        # Use a relatively short timeout since this is only to ensure the
+        # following request is queued.
+        blocking_seq = self.async_blocking_request(duration=1.0)
+        # Use a longer timeout to ensure we catch if the request was interrupted
+        # properly.
+        pending_seq = self.async_blocking_request(duration=self.timeoutval)
+        cancel_seq = self.async_cancel(requestId=pending_seq)
+
+        blocking_resp = self.dap_server.recv_packet(filter_type=["response"])
+        self.assertEqual(blocking_resp["request_seq"], blocking_seq)
+        self.assertEqual(blocking_resp["command"], "evaluate")
+        self.assertEqual(blocking_resp["success"], True)
+
+        pending_resp = self.dap_server.recv_packet(filter_type=["response"])
+        self.assertEqual(pending_resp["request_seq"], pending_seq)
+        self.assertEqual(pending_resp["command"], "evaluate")
+        self.assertEqual(pending_resp["success"], False)
+        self.assertEqual(pending_resp["message"], "cancelled")
+
+        cancel_resp = self.dap_server.recv_packet(filter_type=["response"])
+        self.assertEqual(cancel_resp["request_seq"], cancel_seq)
+        self.assertEqual(cancel_resp["command"], "cancel")
+        self.assertEqual(cancel_resp["success"], True)
+        self.continue_to_exit()
+
+    def test_inflight_request(self):
+        """
+        Tests cancelling an inflight request.
+        """
+        program = self.getBuildArtifact("a.out")
+        self.build_and_launch(program, stopOnEntry=True)
+        self.continue_to_next_stop()
+
+        blocking_seq = self.async_blocking_request(duration=self.timeoutval / 2)
+        # Wait for the sleep to start to cancel the inflight request.
+        self.collect_stdout(
+            timeout_secs=self.timeoutval,
+            pattern="starting sleep",
+        )
+        cancel_seq = self.async_cancel(requestId=blocking_seq)
+
+        blocking_resp = self.dap_server.recv_packet(filter_type=["response"])
+        self.assertEqual(blocking_resp["request_seq"], blocking_seq)
+        self.assertEqual(blocking_resp["command"], "evaluate")
+        self.assertEqual(blocking_resp["success"], False)
+        self.assertEqual(blocking_resp["message"], "cancelled")
+
+        cancel_resp = self.dap_server.recv_packet(filter_type=["response"])
+        self.assertEqual(cancel_resp["request_seq"], cancel_seq)
+        self.assertEqual(cancel_resp["command"], "cancel")
+        self.assertEqual(cancel_resp["success"], True)
+        self.continue_to_exit()
diff --git a/lldb/test/API/tools/lldb-dap/cancel/main.c b/lldb/test/API/tools/lldb-dap/cancel/main.c
new file mode 100644
index 0000000000000..ecc0d99ec8db7
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/cancel/main.c
@@ -0,0 +1,6 @@
+#include <stdio.h>
+
+int main(int argc, char const *argv[]) {
+  printf("Hello world!\n");
+  return 0;
+}
diff --git a/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py b/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py
index 0c92e5bff07c6..5fb088dc51cbb 100644
--- a/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py
+++ b/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py
@@ -27,6 +27,7 @@ def test_default(self):
         lines = output.splitlines()
         self.assertIn(program, lines[0], "make sure program path is in first argument")
 
+    @skipIfWindows
     def test_termination(self):
         """
         Tests the correct termination of lldb-dap upon a 'disconnect'
diff --git a/lldb/tools/lldb-dap/CMakeLists.txt b/lldb/tools/lldb-dap/CMakeLists.txt
index adad75a79fa7a..1e87b28c6d1bd 100644
--- a/lldb/tools/lldb-dap/CMakeLists.txt
+++ b/lldb/tools/lldb-dap/CMakeLists.txt
@@ -40,6 +40,7 @@ add_lldb_tool(lldb-dap
   Handler/ResponseHandler.cpp
   Handler/AttachRequestHandler.cpp
   Handler/BreakpointLocationsHandler.cpp
+  Handler/CancelRequestHandler.cpp
   Handler/CompileUnitsRequestHandler.cpp
   Handler/CompletionsHandler.cpp
   Handler/ConfigurationDoneRequestHandler.cpp
diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp
index a1e2187288768..ebdaa4949eb77 100644
--- a/lldb/tools/lldb-dap/DAP.cpp
+++ b/lldb/tools/lldb-dap/DAP.cpp
@@ -39,9 +39,11 @@
 #include <algorithm>
 #include <cassert>
 #include <chrono>
+#include <condition_variable>
 #include <cstdarg>
 #include <cstdio>
 #include <fstream>
+#include <future>
 #include <memory>
 #include <mutex>
 #include <string>
@@ -241,6 +243,21 @@ void DAP::SendJSON(const llvm::json::Value &json) {
 }
 
 void DAP::Send(const protocol::Message &message) {
+  if (auto *resp = std::get_if<protocol::Response>(&message);
+      resp && debugger.InterruptRequested()) {
+    // If the debugger was interrupted, convert this response into a 'cancelled'
+    // response.
+    protocol::Response cancelled;
+    cancelled.command = resp->command;
+    cancelled.request_seq = resp->request_seq;
+    cancelled.success = false;
+    cancelled.message = protocol::Response::Message::cancelled;
+    if (llvm::Error err = transport.Write(cancelled))
+      DAP_LOG_ERROR(log, std::move(err), "({1}) write failed: {0}",
+                    transport.GetClientName());
+    return;
+  }
+
   if (llvm::Error err = transport.Write(message))
     DAP_LOG_ERROR(log, std::move(err), "({1}) write failed: {0}",
                   transport.GetClientName());
@@ -674,6 +691,10 @@ void DAP::SetTarget(const lldb::SBTarget target) {
 
 bool DAP::HandleObject(const protocol::Message &M) {
   if (const auto *req = std::get_if<protocol::Request>(&M)) {
+    // Clear interrupt marker prior to handling the next request.
+    if (debugger.InterruptRequested())
+      debugger.CancelInterruptRequest();
+
     auto handler_pos = request_handlers.find(req->command);
     if (handler_pos != request_handlers.end()) {
       (*handler_pos->second)(*req);
@@ -778,28 +799,134 @@ llvm::Error DAP::Disconnect(bool terminateDebuggee) {
   return ToError(error);
 }
 
+template <typename T>
+static std::optional<T> getArgumentsIfRequest(const protocol::Message &pm,
+                                              llvm::StringLiteral command) {
+  auto *const req = std::get_if<protocol::Request>(&pm);
+  if (!req || req->command != command)
+    return std::nullopt;
+
+  T args;
+  llvm::json::Path::Root root;
+  if (!fromJSON(req->arguments, args, root)) {
+    return std::nullopt;
+  }
+
+  return std::move(args);
+}
+
 llvm::Error DAP::Loop() {
-  auto cleanup = llvm::make_scope_exit([this]() {
+  std::deque<protocol::Message> queue;
+  std::condition_variable queue_cv;
+  std::mutex queue_mutex;
+  std::future<llvm::Error> queue_reader = std::async([&]() -> llvm::Error {
+    llvm::set_thread_name(transport.GetClientName() + ".transport_handler");
+    auto cleanup = llvm::make_scope_exit([&]() {
+      // Ensure we're marked as disconnecting when the reader exits.
+      disconnecting = true;
+      queue_cv.notify_all();
+    });
+
+    while (!disconnecting) {
+      llvm::Expected<std::optional<protocol::Message>> next =
+          transport.Read(std::chrono::seconds(1));
+      bool timeout = false;
+      if (llvm::Error Err = llvm::handleErrors(
+              next.takeError(),
+              [&](std::unique_ptr<llvm::StringError> Err) -> llvm::Error {
+                if (Err->convertToErrorCode() == std::errc::timed_out) {
+                  timeout = true;
+                  return llvm::Error::success();
+                }
+                return llvm::Error(std::move(Err));
+              }))
+        return Err;
+
+      // If the read timed out, continue to check if we should disconnect.
+      if (timeout)
+        continue;
+
+      // nullopt is returned on EOF.
+      if (!*next)
+        break;
+
+      {
+        std::lock_guard<std::mutex> lock(queue_mutex);
+
+        // If a cancel is requested for the active request, make a best
+        // effort attempt to interrupt.
+        if (const auto cancel_args =
+                getArgumentsIfRequest<protocol::CancelArguments>(**next,
+                                                                 "cancel");
+            cancel_args && active_seq == cancel_args->requestId) {
+          DAP_LOG(log, "({0}) interrupting inflight request {1}",
+                  transport.GetClientName(), active_seq);
+          debugger.RequestInterrupt();
+          debugger.GetCommandInterpreter().InterruptCommand();
+        }
+
+        queue.push_back(std::move(**next));
+      }
+      queue_cv.notify_one();
+    }
+
+    return llvm::Error::success();
+  });
+
+  auto cleanup = llvm::make_scope_exit([&]() {
     out.Stop();
     err.Stop();
     StopEventHandlers();
   });
+
   while (!disconnecting) {
-    llvm::Expected<std::optional<protocol::Message>> next = transport.Read();
-    if (!next)
-      return next.takeError();
+    protocol::Message next;
+    {
+      std::unique_lock<std::mutex> lock(queue_mutex);
+      queue_cv.wait(lock, [&] { return disconnecting || !queue.empty(); });
 
-    // nullopt on EOF
-    if (!*next)
-      break;
+      if (queue.empty())
+        break;
+
+      next = queue.front();
+      queue.pop_front();
+
+      if (protocol::Request *req = std::get_if<protocol::Request>(&next)) {
+        active_seq = req->seq;
+
+        // Check if we should preempt this request from a queued cancel.
+        bool cancelled = false;
+        for (const auto &message : queue) {
+          if (const auto args =
+                  getArgumentsIfRequest<protocol::CancelArguments>(message,
+                                                                   "cancel");
+              args && args->requestId == req->seq) {
+            cancelled = true;
+            break;
+          }
+        }
 
-    if (!HandleObject(**next)) {
+        // Preempt the request and immeidately respond with cancelled.
+        if (cancelled) {
+          protocol::Response response;
+          response.request_seq = req->seq;
+          response.command = req->command;
+          response.success = false;
+          response.message = protocol::Response::Message::cancelled;
+          Send(response);
+          continue;
+        }
+      } else
+        active_seq = 0;
+    }
+
+    if (!HandleObject(next)) {
       return llvm::createStringError(llvm::inconvertibleErrorCode(),
                                      "unhandled packet");
     }
   }
 
-  return llvm::Error::success();
+  return queue_reader.get();
 }
 
 lldb::SBError DAP::WaitForProcessToStop(uint32_t seconds) {
diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h
index 4c57f9fef3d89..17c5ec2bfee9f 100644
--- a/lldb/tools/lldb-dap/DAP.h
+++ b/lldb/tools/lldb-dap/DAP.h
@@ -395,6 +395,9 @@ struct DAP {
   InstructionBreakpoint *GetInstructionBreakpoint(const lldb::break_id_t bp_id);
 
   InstructionBreakpoint *GetInstructionBPFromStopReason(lldb::SBThread &thread);
+
+private:
+  std::atomic<int64_t> active_seq;
 };
 
 } // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/Handler/CancelRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/CancelRequestHandler.cpp
new file mode 100644
index 0000000000000..e6746316e6e74
--- /dev/null
+++ b/lldb/tools/lldb-dap/Handler/CancelRequestHandler.cpp
@@ -0,0 +1,55 @@
+//===-- CancelRequestHandler.cpp ------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "Handler/RequestHandler.h"
+#include "Protocol/ProtocolRequests.h"
+#include "llvm/Support/Error.h"
+
+using namespace lldb_dap;
+using namespace lldb_dap::protocol;
+
+namespace lldb_dap {
+
+/// The `cancel` request is used by the client in two situations:
+///
+/// - to indicate that it is no longer interested in the result produced by a
+/// specific request issued earlier
+/// - to cancel a progress sequence.
+///
+/// Clients should only call this request if the corresponding capability
+/// `supportsCancelRequest` is true.
+///
+/// This request has a hint characteristic: a debug adapter can only be
+/// expected to make a 'best effort' in honoring this request but there are no
+/// guarantees.
+///
+/// The `cancel` request may return an error if it could not cancel
+/// an operation but a client should refrain from presenting this error to end
+/// users.
+///
+/// The request that got cancelled still needs to send a response back.
+/// This can either be a normal result (`success` attribute true) or an error
+/// response (`success` attribute false and the `message` set to `cancelled`).
+///
+/// Returning partial results from a cancelled request is possible but please
+/// note that a client has no generic way for detecting that a response is
+/// partial or not.
+///
+/// The progress that got cancelled still needs to send a `progressEnd` event
+/// back.
+///
+/// A client should not assume that progress just got cancelled after sending
+/// the `cancel` request.
+llvm::Expected<CancelResponseBody>
+CancelRequestHandler::Run(const CancelArguments &arguments) const {
+  // Cancel support is built into the DAP::Loop handler for detecting
+  // cancellations of pending or inflight requests.
+  return CancelResponseBody();
+}
+
+} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp
index 3262b70042a0e..4373c59798b75 100644
--- a/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp
@@ -463,6 +463,8 @@ void InitializeRequestHandler::operator()(
   body.try_emplace("supportsDataBreakpoints", true);
   // The debug adapter supports the `readMemory` request.
   body.try_emplace("supportsReadMemoryRequest", true);
+  // The debug adapter supports the `cancel` request.
+  body.try_emplace("supportsCancelRequest", true);
 
   // Put in non-DAP specification lldb specific information.
   llvm::json::Object lldb_json;
diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.h b/lldb/tools/lldb-dap/Handler/RequestHandler.h
index c9bcf15933c33..32c3199f4904b 100644
--- a/lldb/tools/lldb-dap/Handler/RequestHandler.h
+++ b/lldb/tools/lldb-dap/Handler/RequestHandler.h
@@ -381,6 +381,16 @@ class ReadMemoryRequestHandler : public LegacyRequestHandler {
   void operator()(const llvm::json::Object &request) const override;
 };
 
+class CancelRequestHandler
+    : public RequestHandler<protocol::CancelArguments,
+                            protocol::CancelResponseBody> {
+public:
+  using RequestHandler::RequestHandler;
+  static llvm::StringLiteral getCommand() { return "cancel"; }
+  llvm::Expected<protocol::CancelResponseBody>
+  Run(const protocol::CancelArguments &args) const override;
+};
+
 /// A request used in testing to get the details on all breakpoints that are
 /// currently set in the target. This helps us to test "setBreakpoints" and
 /// "setFunctionBreakpoints" requests to verify we have the correct set of
diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp
index 5cc5429227439..9b36c8329b0fc 100644
--- a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp
+++ b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp
@@ -15,6 +15,13 @@ using namespace llvm;
 
 namespace lldb_dap::protocol {
 
+bool fromJSON(const llvm::json::Value &Params, CancelArguments &CA,
+              llvm::json::Path P) {
+  llvm::json::ObjectMapper O(Params, P);
+  return O && O.mapOptional("requestId", CA.requestId) &&
+         O.mapOptional("progressId", CA.progressId);
+}
+
 bool fromJSON(const json::Value &Params, DisconnectArguments &DA,
               json::Path P) {
   json::ObjectMapper O(Params, P);
diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h
index 5dc4a589178d2..8acdcd322f526 100644
--- a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h
+++ b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h
@@ -29,6 +29,26 @@
 
 namespace lldb_dap::protocol {
 
+/// Arguments for `cancel` request.
+struct CancelArguments {
+  /// The ID (attribute `seq`) of the request to cancel. If missing no request
+  /// is cancelled.
+  ///
+  /// Both a `requestId` and a `progressId` can be specified in one request.
+  std::optional<int64_t> requestId;
+
+  /// The ID (attribute `progressId`) of the progress to cancel. If missing no
+  /// progress is cancelled.
+  ///
+  /// Both a `requestId` and a `progressId` can be specified in one request.
+  std::optional<int64_t> progressId;
+};
+bool fromJSON(const llvm::json::Value &, CancelArguments &, llvm::json::Path);
+
+/// Response to `cancel` request. This is just an acknowledgement, so no body
+/// field is required.
+using CancelResponseBody = VoidResponse;
+
 /// Arguments for `disconnect` request.
 struct DisconnectArguments {
   /// A value of true indicates that this `disconnect` request is part of a
diff --git a/lldb/tools/lldb-dap/Transport.cpp b/lldb/tools/lldb-dap/Transport.cpp
index 4500e7cf909ba..2241ea586c19a 100644
--- a/lldb/tools/lldb-dap/Transport.cpp
+++ b/lldb/tools/lldb-dap/Transport.cpp
@@ -10,6 +10,7 @@
 #include "DAPLog.h"
 #include "Protocol/ProtocolBase.h"
 #include "lldb/Utility/IOObject.h"
+#include "lldb/Utility/SelectHelper.h"
 #include "lldb/Utility/Status.h"
 #include "lldb/lldb-forward.h"
 #include "llvm/ADT/StringExtras.h"
@@ -27,23 +28,39 @@ using namespace lldb_dap::protocol;
 
 /// ReadFull attempts to read the specified number of bytes. If EOF is
 /// encountered, an empty string is returned.
-static Expected<std::string> ReadFull(IOObject &descriptor, size_t length) {
+static Expected<std::string>
+ReadFull(IOObject &descriptor, size_t length,
+         const std::chrono::microseconds &timeout) {
+  if (!descriptor.IsValid())
+    return createStringError("transport output is closed");
+
+#ifndef _WIN32
+  // FIXME: SelectHelper does not work with NativeFile on Win32.
+  SelectHelper sh;
+  sh.SetTimeout(timeout);
+  sh.FDSetRead(descriptor.GetWaitableHandle());
+  Status status = sh.Select();
+  if (status.Fail())
+    return status.takeError();
+#endif
+
   std::string data;
   data.resize(length);
-  auto status = descript...
[truncated]

@@ -27,6 +27,7 @@ def test_default(self):
lines = output.splitlines()
self.assertIn(program, lines[0], "make sure program path is in first argument")

@skipIfWindows
Copy link
Member

@vogelsgesang vogelsgesang Mar 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why? Did this test case work previously and was now broken by implementing cancellation support?

Comment on lines 466 to 467
// The debug adapter supports the `cancel` request.
body.try_emplace("supportsCancelRequest", true);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move to CancelRequestHandler (#131943)

Comment on lines 117 to 99

Expected<std::string> raw_json = ReadFull(*input, length);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should probably only apply a timeout before receiving the first byte of a message.

Otherwise, we might run into hard-to-debug issues where the client sends

Content-Length: 123
\r\n\r\n
<wait for 2 seconds>
actual request body

With the current logic, we would first consume the Content-Length: 123\r\n\rn\n header, then run into the timeout. Upon retrying the read in DAP::Loop() we would the find the request body without the required Content-Length header.

The client would be compliant with the Debug Adapter Protocol specification, yet lldb-dap would choke on this message.

It seems we are only using the timeout such that the disconnecting flag is checked regularly in DAP::Loop. Instead of using a timeout to wake up the reader-thread, would it maybe make sense to instead call Transport::Close when we want to shut down the reader? That should also cancel any outstanding reads, doesn't it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should probably only apply a timeout before receiving the first byte of a message.

Otherwise, we might run into hard-to-debug issues where the client sends

Yea, thats a good idea, I'll only apply the timeout to the message header.

It seems we are only using the timeout such that the disconnecting flag is checked regularly in DAP::Loop. Instead of using a timeout to wake up the reader-thread, would it maybe make sense to instead call Transport::Close when we want to shut down the reader? That should also cancel any outstanding reads, doesn't it?

Closing a file descriptor does not interrupt in progress reads, I think its platform dependent on what happens.

Copy link
Contributor Author

@ashgti ashgti Mar 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, frustrating on Windows stdin is an anonymous pipe and anonymous pipes do not support async reads (https://learn.microsoft.com/en-us/windows/win32/ipc/anonymous-pipe-operations) which is why I disabled the one test on Windows, since we can't read with a timeout on stdin. Which does mean we'd end up blocking the full shutdown of lldb-dap on VSCode closing the input side of the pipe after it calls 'disconnect', but that should be fine, IMO.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Closing a file descriptor does not interrupt in progress reads, I think its platform dependent on what happens.

Very much so. It's also basically impossible to do this in race-free manner,.

Also, frustrating on Windows stdin is an anonymous pipe and anonymous pipes do not support

I don't know if this helps you (since you still have to use a dedicated API instead of select(2)), but lldb_private::Pipe works around this by creating a named pipe with an random name (from what I've read, that's actually how windows implements anonymous pipes internally).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if this helps you (since you still have to use a dedicated API instead of select(2)), but lldb_private::Pipe works around this by creating a named pipe with an random name (from what I've read, that's actually how windows implements anonymous pipes internally).

The trouble is that when we run the debugger as an DebugAdapterExecutable the input stream is stdin. Unless we want to make Windows always use a local socket DebugAdapterServer or named pipes DebugAdapterNamedPipeServer we'd need to have some other mechanism for supporting reading with a timeout on Windows.

But I'm also not sure if that actually matters. VSCode never closes stdin, so it never gets an EOF. When the debug session ends, it sends a kill -15 https://github.com/microsoft/vscode/blob/main/src/vs/workbench/contrib/debug/node/debugAdapter.ts#L290-L303 so I'm not sure if we need to ensure the Transport returns, but we do have an existing test that we shutdown after the disconnect request is sent.

ashgti added 3 commits March 19, 2025 09:58
Adding support for cancelling requests.

There are two forms of request cancellation.

* Preemptively cancelling a request that is in the queue.
* Actively cancelling the in progress request as a best effort attempt using `SBDebugger.RequestInterrupt()`.
…der, once we have the header we should read the full message.
Copy link

github-actions bot commented Mar 19, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@JDevlieghere
Copy link
Member

I left a few small comments but overall this looks good.

The approach is slightly different from how I expected you to implement this. Note that I don't know if it's actually better, it's more of a "if I had to implement this, that's how I would have started." You've been working on this for a bit, so I'm trusting your judgement.

Anyway, what I had in mind when I read your RFC and was doing the RequestHandler work, triggered by a question from @labath, is that instead of having one RequestHandler instance per command (which is what we have today), we could instantiate a RequestHandler per incoming command (or a new object that has a reference to the request hander and stores the arguments).

The read thread could decode its arguments and store away the protocol in the RequestHandler/new object and add it to the queue. Beyond that, things work pretty much how you've implemented them here, but with the logic that checks if the request has been cancelled in the RequestHandler. More concretely the operator doesn't take arguments anymore, and it's implemented something like this:

void operator() {
  if (IsCancelled()) // Check if our request ID is in the cancelled request set.
    RespondCancelled();
  
  llvm::Expected<Body> body = Run(); 
  if (!body) 
    // Handle error

  if (IsInterrupted()) // Check if the SBDebugger was interrupted. 
  	RespondCancelled(); 
}

If we go with the new object (PendingRequest?) the request handlers can remain what they are today, or if we want to store the data in them we'll need to find a way to dispatch to them (similar to my comment in yesterday's PR).

Anyway, like I said, not sure if this is actually better, just sharing my thoughts.

Copy link
Collaborator

@labath labath left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's a race in the cancellation handling:

  1. request handling thread sets active_seq (but doesn't start executing the request yet)
  2. cancellation request comes in, queue_reader sets the interrupt flag
  3. request handling thread begins handling the request, starts by clearing the flag
  4. request is not cancelled

I think this would be easier to guarantee correctness (and review) if active_seq was not an atomic and its modification, as well as the acts of setting and clearing the interrupt flag were done under a mutex

Copy link
Member

@JDevlieghere JDevlieghere left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(looks like I didn't submit my comments yesterday)

/// specific request issued earlier
/// - to cancel a progress sequence.
///
/// Clients should only call this request if the corresponding capability
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation contains some wording with recommendations (i.e. "should"). Do we expect clients to read this? Is this coming from the DAP documentation? Otherwise it seems like this should be aimed at us, server implementers.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its the DAP documentation for the cancel request, its mostly verbatim with small formatting tweaks. See https://microsoft.github.io/debug-adapter-protocol/specification#Base_Protocol_Cancel

Comment on lines 247 to 248
// If the debugger was interrupted, convert this response into a 'cancelled'
// response.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// If the debugger was interrupted, convert this response into a 'cancelled'
// response.
// If the debugger was interrupted, convert this response into a 'cancelled'
// response because we might have a partial result.

@ashgti
Copy link
Contributor Author

ashgti commented Mar 20, 2025

Anyway, what I had in mind when I read your RFC and was doing the RequestHandler work, triggered by a question from @labath, is that instead of having one RequestHandler instance per command (which is what we have today), we could instantiate a RequestHandler per incoming command (or a new object that has a reference to the request hander and stores the arguments).

The read thread could decode its arguments and store away the protocol in the RequestHandler/new object and add it to the queue. Beyond that, things work pretty much how you've implemented them here, but with the logic that checks if the request has been cancelled in the RequestHandler. More concretely the operator doesn't take arguments anymore, and it's implemented something like this:

void operator() {
  if (IsCancelled()) // Check if our request ID is in the cancelled request set.
    RespondCancelled();
  
  llvm::Expected<Body> body = Run(); 
  if (!body) 
    // Handle error

  if (IsInterrupted()) // Check if the SBDebugger was interrupted. 
  	RespondCancelled(); 
}

If we go with the new object (PendingRequest?) the request handlers can remain what they are today, or if we want to store the data in them we'll need to find a way to dispatch to them (similar to my comment in yesterday's PR).

Anyway, like I said, not sure if this is actually better, just sharing my thoughts.

I do think this is an interesting approach. I can take a look at this as trying to wrap some of this into some helpers or maybe making the RequestHandler allocate a new instance per request.

My logic behind the lldb_dap::protocol types was to keep them as POD data and to not have much (if any) logic in them so that if we need to move the data between threads or queues we can be relatively confident that we're not dealing data races. In swift terms (I'm more familiar with swift than c++), the lldb_dap::protocol types would be struct's that are all Sendable.

That may not be as idiomatic in c++ though.

I do like the more simplified operator() idea to help deal with the cancelled / interrupted, my existing solution was done to support the LegacyRequestHandlers since they're sending the responses as raw json::Value's still, but with the more unified approach we could handle the cancellation in the operator() body.

I'll try to see if I can come up with a PendingRequest or maybe take a look at allocating the RequestHandler's in the queue.

@JDevlieghere
Copy link
Member

JDevlieghere commented Mar 20, 2025

My logic behind the lldb_dap::protocol types was to keep them as POD data and to not have much (if any) logic in them so that if we need to move the data between threads or queues we can be relatively confident that we're not dealing data races. In swift terms (I'm more familiar with swift than c++), the lldb_dap::protocol types would be struct's that are all Sendable.

Totally, and I think that's a great design, and not something that would change with my suggestion, right?

I do like the more simplified operator() idea to help deal with the cancelled / interrupted, my existing solution was done to support the LegacyRequestHandlers since they're sending the responses as raw json::Value's still, but with the more unified approach we could handle the cancellation in the operator() body.

If we want to support cancellation before moving all the request over to the protocol class, can we temporarily store both the JSON and the Protocol in the class we instantiate per request (whether that's the RequestHandler itself, or a class wrapping it) and use whichever one we need for that given request?

@ashgti
Copy link
Contributor Author

ashgti commented Mar 20, 2025

If we want to support cancellation before moving all the request over to the protocol class, can we temporarily store both the JSON and the Protocol in the class we instantiate per request (whether that's the RequestHandler itself, or a class wrapping it) and use whichever one we need for that given request?

For the LegacyRequestHandler, its more to do with the response than the request. We can store the request as a protocol::Message and in the LegacyRequestHandler we convert it back into a json::Value before we pass it to the handler. But the LegacyRequestHandler's all tend to call dap.SendJSON directly with a json::Value. In my current impl of the cancel request, I check for the cancelled requests prior to sending them

if (auto *resp = std::get_if<protocol::Response>(&message);
resp && debugger.InterruptRequested()) {
// If the debugger was interrupted, convert this response into a 'cancelled'
// response.
protocol::Response cancelled;
cancelled.command = resp->command;
cancelled.request_seq = resp->request_seq;
cancelled.success = false;
cancelled.message = protocol::Response::Message::cancelled;
if (llvm::Error err = transport.Write(cancelled))
DAP_LOG_ERROR(log, std::move(err), "({1}) write failed: {0}",
transport.GetClientName());
return;
}

@ashgti
Copy link
Contributor Author

ashgti commented Mar 20, 2025

I think there's a race in the cancellation handling:

  1. request handling thread sets active_seq (but doesn't start executing the request yet)
  2. cancellation request comes in, queue_reader sets the interrupt flag
  3. request handling thread begins handling the request, starts by clearing the flag
  4. request is not cancelled

I think this would be easier to guarantee correctness (and review) if active_seq was not an atomic and its modification, as well as the acts of setting and clearing the interrupt flag were done under a mutex

Switched to a mutex for tracking the active request.

Comment on lines 721 to 723
// Clear interrupt marker prior to handling the next request.
if (debugger.InterruptRequested())
debugger.CancelInterruptRequest();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You still have the same race here. The request could be cancelled between this line and when the active request field is set on line 702. (My complaint wasn't so much about the exact std::atomic type as it was about treating this flag as an atomic -- a mutex surrounding just a variable assignment is morally equivalent to an atomic). For this to work, I think the flag needs to be checked in the same critical section that handles sets m_active_request. I believe it will be fine if you move this code to line 702, just before setting the active request.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved this up to 702

@ashgti
Copy link
Contributor Author

ashgti commented Mar 21, 2025

@JDevlieghere I moved some of the cancel checking logic into the BaseRequestHandler to try to consolidate things. There are some FIXME's around cleaning it up once all the requests have moved off the LegacyRequestHandler.

@ashgti
Copy link
Contributor Author

ashgti commented Mar 31, 2025

Anyone have any other comments on supporting 'cancel'?

@ashgti
Copy link
Contributor Author

ashgti commented Apr 1, 2025

Wanted to also double check with @labath as well, any additional thoughts?

Copy link
Collaborator

@labath labath left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't look at the code all too closely but it looks okay to me. I believe my earlier concerns about cancellation races are resolved now.

@ashgti ashgti merged commit 2d30a60 into llvm:main Apr 14, 2025
10 checks passed
@llvm-ci
Copy link
Collaborator

llvm-ci commented Apr 14, 2025

LLVM Buildbot has detected a new failure on builder lldb-remote-linux-win running on as-builder-10 while building lldb at step 8 "build-default".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/197/builds/3983

Here is the relevant piece of the build log for the reference
Step 8 (build-default) failure: cmake (failure)
...
65.993 [210/75/5142]Linking CXX executable bin\llvm-cgdata.exe
66.009 [209/75/5143]Building CXX object tools\clang\tools\clang-refactor\CMakeFiles\clang-refactor.dir\ClangRefactor.cpp.obj
66.053 [209/74/5144]Building CXX object tools\clang\lib\StaticAnalyzer\Checkers\CMakeFiles\obj.clangStaticAnalyzerCheckers.dir\WebKit\RefCntblBaseVirtualDtorChecker.cpp.obj
66.053 [209/73/5145]Linking CXX executable bin\llvm-cfi-verify.exe
66.069 [208/73/5146]Linking CXX static library lib\lldbPluginSymbolFileNativePDB.lib
66.078 [207/73/5147]Building CXX object tools\clang\lib\StaticAnalyzer\Checkers\CMakeFiles\obj.clangStaticAnalyzerCheckers.dir\WebKit\RawPtrRefLocalVarsChecker.cpp.obj
66.150 [207/72/5148]Linking CXX executable bin\llc.exe
66.263 [206/72/5149]Linking CXX static library lib\lldbPluginSymbolFileDWARF.lib
66.398 [205/72/5150]Building CXX object tools\clang\tools\libclang\CMakeFiles\libclang.dir\CIndex.cpp.obj
66.427 [205/71/5151]Building CXX object tools\lldb\tools\lldb-dap\CMakeFiles\lldb-dap.dir\Transport.cpp.obj
FAILED: tools/lldb/tools/lldb-dap/CMakeFiles/lldb-dap.dir/Transport.cpp.obj 
ccache C:\PROGRA~1\MICROS~1\2022\COMMUN~1\VC\Tools\MSVC\1441~1.341\bin\Hostx64\x64\cl.exe  /nologo /TP -DGTEST_HAS_RTTI=0 -DLLVM_BUILD_STATIC -DUNICODE -D_CRT_NONSTDC_NO_DEPRECATE -D_CRT_NONSTDC_NO_WARNINGS -D_CRT_SECURE_NO_DEPRECATE -D_CRT_SECURE_NO_WARNINGS -D_ENABLE_EXTENDED_ALIGNED_STORAGE -D_GLIBCXX_ASSERTIONS -D_HAS_EXCEPTIONS=0 -D_SCL_SECURE_NO_DEPRECATE -D_SCL_SECURE_NO_WARNINGS -D_UNICODE -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS -IC:\buildbot\as-builder-10\lldb-x-aarch64\build\tools\lldb\tools\lldb-dap -IC:\buildbot\as-builder-10\lldb-x-aarch64\llvm-project\lldb\tools\lldb-dap -IC:\buildbot\as-builder-10\lldb-x-aarch64\llvm-project\lldb\include -IC:\buildbot\as-builder-10\lldb-x-aarch64\build\tools\lldb\include -IC:\buildbot\as-builder-10\lldb-x-aarch64\build\include -IC:\buildbot\as-builder-10\lldb-x-aarch64\llvm-project\llvm\include -IC:\Python312\include -IC:\buildbot\as-builder-10\lldb-x-aarch64\llvm-project\llvm\..\clang\include -IC:\buildbot\as-builder-10\lldb-x-aarch64\build\tools\lldb\..\clang\include -D__OPTIMIZE__ /Zc:inline /Zc:preprocessor /Zc:__cplusplus /Oi /bigobj /permissive- /W4 -wd4141 -wd4146 -wd4244 -wd4267 -wd4291 -wd4351 -wd4456 -wd4457 -wd4458 -wd4459 -wd4503 -wd4624 -wd4722 -wd4100 -wd4127 -wd4512 -wd4505 -wd4610 -wd4510 -wd4702 -wd4245 -wd4706 -wd4310 -wd4701 -wd4703 -wd4389 -wd4611 -wd4805 -wd4204 -wd4577 -wd4091 -wd4592 -wd4319 -wd4709 -wd5105 -wd4324 -wd4251 -wd4275 -w14062 -we4238 /Gw /O2 /Ob2  -MD   -wd4018 -wd4068 -wd4150 -wd4201 -wd4251 -wd4521 -wd4530 -wd4589  /EHs-c- /GR- -UNDEBUG -std:c++17 /showIncludes /Fotools\lldb\tools\lldb-dap\CMakeFiles\lldb-dap.dir\Transport.cpp.obj /Fdtools\lldb\tools\lldb-dap\CMakeFiles\lldb-dap.dir\ /FS -c C:\buildbot\as-builder-10\lldb-x-aarch64\llvm-project\lldb\tools\lldb-dap\Transport.cpp
C:\buildbot\as-builder-10\lldb-x-aarch64\llvm-project\lldb\tools\lldb-dap\Transport.cpp(41): error C2065: 'eFDTypeSocket': undeclared identifier
66.435 [205/70/5152]Linking CXX executable bin\llvm-cvtres.exe
66.551 [205/69/5153]Linking CXX executable bin\llvm-cxxfilt.exe
66.566 [205/68/5154]Building CXX object tools\lldb\source\Commands\CMakeFiles\lldbCommands.dir\CommandObjectTarget.cpp.obj
66.588 [205/67/5155]Building CXX object tools\clang\lib\StaticAnalyzer\Checkers\CMakeFiles\obj.clangStaticAnalyzerCheckers.dir\WebKit\RetainPtrCtorAdoptChecker.cpp.obj
66.600 [205/66/5156]Building CXX object tools\clang\tools\libclang\CMakeFiles\libclang.dir\Indexing.cpp.obj
66.697 [205/65/5157]Linking CXX executable bin\llvm-cov.exe
66.723 [205/64/5158]Building CXX object tools\clang\tools\libclang\CMakeFiles\libclang.dir\CXExtractAPI.cpp.obj
66.831 [205/63/5159]Building CXX object tools\lldb\tools\lldb-instr\CMakeFiles\lldb-instr.dir\Instrument.cpp.obj
66.947 [205/62/5160]Building CXX object tools\clang\tools\clang-installapi\CMakeFiles\clang-installapi.dir\ClangInstallAPI.cpp.obj
66.971 [205/61/5161]Building CXX object tools\lldb\source\Plugins\ExpressionParser\Clang\CMakeFiles\lldbPluginExpressionParserClang.dir\ASTResultSynthesizer.cpp.obj
67.000 [205/60/5162]Building CXX object tools\lldb\source\Plugins\ExpressionParser\Clang\CMakeFiles\lldbPluginExpressionParserClang.dir\ClangModulesDeclVendor.cpp.obj
67.046 [205/59/5163]Building CXX object tools\lldb\source\Plugins\ExpressionParser\Clang\CMakeFiles\lldbPluginExpressionParserClang.dir\ASTStructExtractor.cpp.obj
67.107 [205/58/5164]Building CXX object tools\clang\tools\clang-check\CMakeFiles\clang-check.dir\ClangCheck.cpp.obj
67.154 [205/57/5165]Building CXX object tools\clang\tools\clang-scan-deps\CMakeFiles\clang-scan-deps.dir\ClangScanDeps.cpp.obj
67.181 [205/56/5166]Building CXX object tools\lldb\tools\lldb-dap\CMakeFiles\lldb-dap.dir\Breakpoint.cpp.obj
67.189 [205/55/5167]Building CXX object tools\lldb\tools\lldb-dap\CMakeFiles\lldb-dap.dir\ExceptionBreakpoint.cpp.obj
67.206 [205/54/5168]Linking CXX executable bin\llvm-cxxdump.exe
67.253 [205/53/5169]Building CXX object tools\lldb\source\Plugins\ExpressionParser\Clang\CMakeFiles\lldbPluginExpressionParserClang.dir\ClangFunctionCaller.cpp.obj
67.293 [205/52/5170]Building CXX object tools\lldb\source\Plugins\ExpressionParser\Clang\CMakeFiles\lldbPluginExpressionParserClang.dir\ClangExpressionParser.cpp.obj
67.450 [205/51/5171]Building CXX object tools\lldb\tools\lldb-dap\CMakeFiles\lldb-dap.dir\FunctionBreakpoint.cpp.obj
67.676 [205/50/5172]Building CXX object tools\lldb\tools\lldb-dap\CMakeFiles\lldb-dap.dir\InstructionBreakpoint.cpp.obj
67.719 [205/49/5173]Building CXX object tools\lldb\tools\lldb-dap\CMakeFiles\lldb-dap.dir\OutputRedirector.cpp.obj
67.756 [205/48/5174]Building CXX object tools\lldb\tools\lldb-dap\CMakeFiles\lldb-dap.dir\LLDBUtils.cpp.obj
68.090 [205/47/5175]Building CXX object tools\lldb\tools\lldb-dap\CMakeFiles\lldb-dap.dir\SourceBreakpoint.cpp.obj
68.216 [205/46/5176]Building CXX object tools\lldb\tools\lldb-dap\CMakeFiles\lldb-dap.dir\Handler\EvaluateRequestHandler.cpp.obj
68.233 [205/45/5177]Building CXX object tools\lldb\tools\lldb-dap\CMakeFiles\lldb-dap.dir\Handler\CancelRequestHandler.cpp.obj
68.249 [205/44/5178]Building CXX object tools\lldb\tools\lldb-dap\CMakeFiles\lldb-dap.dir\Watchpoint.cpp.obj
68.326 [205/43/5179]Building CXX object tools\lldb\tools\lldb-dap\CMakeFiles\lldb-dap.dir\Handler\ConfigurationDoneRequestHandler.cpp.obj
68.640 [205/42/5180]Building CXX object tools\lldb\tools\lldb-dap\CMakeFiles\lldb-dap.dir\Handler\BreakpointLocationsHandler.cpp.obj
68.647 [205/41/5181]Building CXX object tools\lldb\tools\lldb-dap\CMakeFiles\lldb-dap.dir\Handler\DisconnectRequestHandler.cpp.obj
68.662 [205/40/5182]Building CXX object tools\lldb\tools\lldb-dap\CMakeFiles\lldb-dap.dir\Handler\ContinueRequestHandler.cpp.obj
68.723 [205/39/5183]Building CXX object tools\lldb\tools\lldb-dap\CMakeFiles\lldb-dap.dir\Handler\ExceptionInfoRequestHandler.cpp.obj
68.724 [205/38/5184]Building CXX object tools\lldb\tools\lldb-dap\CMakeFiles\lldb-dap.dir\Handler\CompileUnitsRequestHandler.cpp.obj
68.769 [205/37/5185]Building CXX object tools\lldb\tools\lldb-dap\CMakeFiles\lldb-dap.dir\Handler\ModulesRequestHandler.cpp.obj
68.810 [205/36/5186]Building CXX object tools\lldb\tools\lldb-dap\CMakeFiles\lldb-dap.dir\Handler\AttachRequestHandler.cpp.obj
69.027 [205/35/5187]Building CXX object tools\lldb\tools\lldb-dap\CMakeFiles\lldb-dap.dir\Handler\NextRequestHandler.cpp.obj

@ashgti
Copy link
Contributor Author

ashgti commented Apr 14, 2025

PR #135638 should fix the build failure.

@llvm-ci
Copy link
Collaborator

llvm-ci commented Apr 14, 2025

LLVM Buildbot has detected a new failure on builder lldb-arm-ubuntu running on linaro-lldb-arm-ubuntu while building lldb at step 6 "test".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/18/builds/14470

Here is the relevant piece of the build log for the reference
Step 6 (test) failure: build (failure)
...
UNSUPPORTED: lldb-api :: tools/lldb-dap/exception/objc/TestDAP_exception_objc.py (1167 of 2954)
UNSUPPORTED: lldb-api :: tools/lldb-dap/extendedStackTrace/TestDAP_extendedStackTrace.py (1168 of 2954)
PASS: lldb-api :: tools/lldb-dap/exception/cpp/TestDAP_exception_cpp.py (1169 of 2954)
PASS: lldb-api :: tools/lldb-dap/instruction-breakpoint/TestDAP_instruction_breakpoint.py (1170 of 2954)
PASS: lldb-api :: tools/lldb-dap/disconnect/TestDAP_disconnect.py (1171 of 2954)
PASS: lldb-api :: tools/lldb-dap/io/TestDAP_io.py (1172 of 2954)
PASS: lldb-api :: tools/lldb-dap/locations/TestDAP_locations.py (1173 of 2954)
PASS: lldb-api :: tools/lldb-dap/memory/TestDAP_memory.py (1174 of 2954)
PASS: lldb-api :: tools/lldb-dap/optimized/TestDAP_optimized.py (1175 of 2954)
UNRESOLVED: lldb-api :: tools/lldb-dap/evaluate/TestDAP_evaluate.py (1176 of 2954)
******************** TEST 'lldb-api :: tools/lldb-dap/evaluate/TestDAP_evaluate.py' FAILED ********************
Script:
--
/usr/bin/python3.10 /home/tcwg-buildbot/worker/lldb-arm-ubuntu/llvm-project/lldb/test/API/dotest.py -u CXXFLAGS -u CFLAGS --env LLVM_LIBS_DIR=/home/tcwg-buildbot/worker/lldb-arm-ubuntu/build/./lib --env LLVM_INCLUDE_DIR=/home/tcwg-buildbot/worker/lldb-arm-ubuntu/build/include --env LLVM_TOOLS_DIR=/home/tcwg-buildbot/worker/lldb-arm-ubuntu/build/./bin --arch armv8l --build-dir /home/tcwg-buildbot/worker/lldb-arm-ubuntu/build/lldb-test-build.noindex --lldb-module-cache-dir /home/tcwg-buildbot/worker/lldb-arm-ubuntu/build/lldb-test-build.noindex/module-cache-lldb/lldb-api --clang-module-cache-dir /home/tcwg-buildbot/worker/lldb-arm-ubuntu/build/lldb-test-build.noindex/module-cache-clang/lldb-api --executable /home/tcwg-buildbot/worker/lldb-arm-ubuntu/build/./bin/lldb --compiler /home/tcwg-buildbot/worker/lldb-arm-ubuntu/build/./bin/clang --dsymutil /home/tcwg-buildbot/worker/lldb-arm-ubuntu/build/./bin/dsymutil --make /usr/bin/gmake --llvm-tools-dir /home/tcwg-buildbot/worker/lldb-arm-ubuntu/build/./bin --lldb-obj-root /home/tcwg-buildbot/worker/lldb-arm-ubuntu/build/tools/lldb --lldb-libs-dir /home/tcwg-buildbot/worker/lldb-arm-ubuntu/build/./lib /home/tcwg-buildbot/worker/lldb-arm-ubuntu/llvm-project/lldb/test/API/tools/lldb-dap/evaluate -p TestDAP_evaluate.py
--
Exit Code: 1

Command Output (stdout):
--
lldb version 21.0.0git (https://github.com/llvm/llvm-project.git revision 2d30a60e9ff8b22f7e07ca5360fe1582f96be1ac)
  clang revision 2d30a60e9ff8b22f7e07ca5360fe1582f96be1ac
  llvm revision 2d30a60e9ff8b22f7e07ca5360fe1582f96be1ac
Skipping the following test categories: ['libc++', 'dsym', 'gmodules', 'debugserver', 'objc']

--
Command Output (stderr):
--
========= DEBUG ADAPTER PROTOCOL LOGS =========
1744647501.012513399 --> (stdin/stdout) {"command":"initialize","type":"request","arguments":{"adapterID":"lldb-native","clientID":"vscode","columnsStartAt1":true,"linesStartAt1":true,"locale":"en-us","pathFormat":"path","supportsRunInTerminalRequest":true,"supportsVariablePaging":true,"supportsVariableType":true,"supportsStartDebuggingRequest":true,"supportsProgressReporting":true,"$__lldb_sourceInitFile":false},"seq":1}
1744647501.016715050 <-- (stdin/stdout) {"body":{"$__lldb_version":"lldb version 21.0.0git (https://github.com/llvm/llvm-project.git revision 2d30a60e9ff8b22f7e07ca5360fe1582f96be1ac)\n  clang revision 2d30a60e9ff8b22f7e07ca5360fe1582f96be1ac\n  llvm revision 2d30a60e9ff8b22f7e07ca5360fe1582f96be1ac","completionTriggerCharacters":["."," ","\t"],"exceptionBreakpointFilters":[{"default":false,"filter":"cpp_catch","label":"C++ Catch"},{"default":false,"filter":"cpp_throw","label":"C++ Throw"},{"default":false,"filter":"objc_catch","label":"Objective-C Catch"},{"default":false,"filter":"objc_throw","label":"Objective-C Throw"}],"supportTerminateDebuggee":true,"supportsBreakpointLocationsRequest":true,"supportsCancelRequest":true,"supportsCompletionsRequest":true,"supportsConditionalBreakpoints":true,"supportsConfigurationDoneRequest":true,"supportsDataBreakpoints":true,"supportsDelayedStackTraceLoading":true,"supportsDisassembleRequest":true,"supportsEvaluateForHovers":true,"supportsExceptionInfoRequest":true,"supportsExceptionOptions":true,"supportsFunctionBreakpoints":true,"supportsHitConditionalBreakpoints":true,"supportsInstructionBreakpoints":true,"supportsLogPoints":true,"supportsModulesRequest":true,"supportsReadMemoryRequest":true,"supportsRestartRequest":true,"supportsSetVariable":true,"supportsStepInTargetsRequest":true,"supportsSteppingGranularity":true,"supportsValueFormattingOptions":true},"command":"initialize","request_seq":1,"seq":0,"success":true,"type":"response"}
1744647501.017248631 --> (stdin/stdout) {"command":"launch","type":"request","arguments":{"program":"/home/tcwg-buildbot/worker/lldb-arm-ubuntu/build/lldb-test-build.noindex/tools/lldb-dap/evaluate/TestDAP_evaluate.test_generic_evaluate_expressions/a.out","initCommands":["settings clear -all","settings set symbols.enable-external-lookup false","settings set target.inherit-tcc true","settings set target.disable-aslr false","settings set target.detach-on-error false","settings set target.auto-apply-fixits false","settings set plugin.process.gdb-remote.packet-timeout 60","settings set symbols.clang-modules-cache-path \"/home/tcwg-buildbot/worker/lldb-arm-ubuntu/build/lldb-test-build.noindex/module-cache-lldb/lldb-api\"","settings set use-color false","settings set show-statusline false"],"disableASLR":false,"enableAutoVariableSummaries":false,"enableSyntheticChildDebugging":false,"displayExtendedBacktrace":false,"commandEscapePrefix":null},"seq":2}
1744647501.017791986 <-- (stdin/stdout) {"body":{"category":"console","output":"Running initCommands:\n"},"event":"output","seq":0,"type":"event"}
1744647501.017868042 <-- (stdin/stdout) {"body":{"category":"console","output":"(lldb) settings clear -all\n"},"event":"output","seq":0,"type":"event"}
1744647501.017883301 <-- (stdin/stdout) {"body":{"category":"console","output":"(lldb) settings set symbols.enable-external-lookup false\n"},"event":"output","seq":0,"type":"event"}
1744647501.017900705 <-- (stdin/stdout) {"body":{"category":"console","output":"(lldb) settings set target.inherit-tcc true\n"},"event":"output","seq":0,"type":"event"}
1744647501.017913580 <-- (stdin/stdout) {"body":{"category":"console","output":"(lldb) settings set target.disable-aslr false\n"},"event":"output","seq":0,"type":"event"}
1744647501.017925739 <-- (stdin/stdout) {"body":{"category":"console","output":"(lldb) settings set target.detach-on-error false\n"},"event":"output","seq":0,"type":"event"}
1744647501.017937183 <-- (stdin/stdout) {"body":{"category":"console","output":"(lldb) settings set target.auto-apply-fixits false\n"},"event":"output","seq":0,"type":"event"}
1744647501.017948866 <-- (stdin/stdout) {"body":{"category":"console","output":"(lldb) settings set plugin.process.gdb-remote.packet-timeout 60\n"},"event":"output","seq":0,"type":"event"}
1744647501.018002033 <-- (stdin/stdout) {"body":{"category":"console","output":"(lldb) settings set symbols.clang-modules-cache-path \"/home/tcwg-buildbot/worker/lldb-arm-ubuntu/build/lldb-test-build.noindex/module-cache-lldb/lldb-api\"\n"},"event":"output","seq":0,"type":"event"}
1744647501.018014193 <-- (stdin/stdout) {"body":{"category":"console","output":"(lldb) settings set use-color false\n"},"event":"output","seq":0,"type":"event"}
1744647501.018183470 <-- (stdin/stdout) {"body":{"category":"console","output":"(lldb) settings set show-statusline false\n"},"event":"output","seq":0,"type":"event"}
1744647501.258568287 <-- (stdin/stdout) {"command":"launch","request_seq":2,"seq":0,"success":true,"type":"response"}
1744647501.260461807 <-- (stdin/stdout) {"body":{"isLocalProcess":true,"name":"/home/tcwg-buildbot/worker/lldb-arm-ubuntu/build/lldb-test-build.noindex/tools/lldb-dap/evaluate/TestDAP_evaluate.test_generic_evaluate_expressions/a.out","startMethod":"launch","systemProcessId":951888},"event":"process","seq":0,"type":"event"}
1744647501.260587931 <-- (stdin/stdout) {"event":"initialized","seq":0,"type":"event"}
1744647501.261456490 --> (stdin/stdout) {"command":"setBreakpoints","type":"request","arguments":{"source":{"name":"main.cpp","path":"main.cpp"},"sourceModified":false,"lines":[25,29,12,37,42,46,47,50],"breakpoints":[{"line":25},{"line":29},{"line":12},{"line":37},{"line":42},{"line":46},{"line":47},{"line":50}]},"seq":3}
1744647501.272146463 <-- (stdin/stdout) {"body":{"breakpoint":{"column":14,"id":1,"instructionReference":"0x841110","line":25,"verified":true},"reason":"changed"},"event":"breakpoint","seq":0,"type":"event"}
1744647501.272959471 <-- (stdin/stdout) {"body":{"breakpoint":{"column":16,"id":2,"instructionReference":"0x84112C","line":29,"verified":true},"reason":"changed"},"event":"breakpoint","seq":0,"type":"event"}
1744647501.273711443 <-- (stdin/stdout) {"body":{"breakpoint":{"column":10,"id":3,"instructionReference":"0x8410AC","line":12,"verified":true},"reason":"changed"},"event":"breakpoint","seq":0,"type":"event"}

@llvm-ci
Copy link
Collaborator

llvm-ci commented Apr 14, 2025

LLVM Buildbot has detected a new failure on builder lldb-aarch64-windows running on linaro-armv8-windows-msvc-05 while building lldb at step 4 "build".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/141/builds/7879

Here is the relevant piece of the build log for the reference
Step 4 (build) failure: build (failure)
...
525.346 [559/10/6144] Building CXX object tools\lldb\tools\lldb-dap\CMakeFiles\lldb-dap.dir\FunctionBreakpoint.cpp.obj
525.379 [558/10/6145] Building CXX object tools\lldb\tools\lldb-dap\CMakeFiles\lldb-dap.dir\Breakpoint.cpp.obj
525.579 [557/10/6146] Building CXX object tools\lldb\tools\lldb-dap\CMakeFiles\lldb-dap.dir\ProgressEvent.cpp.obj
525.617 [556/10/6147] Building CXX object tools\lldb\tools\lldb-dap\CMakeFiles\lldb-dap.dir\RunInTerminal.cpp.obj
525.668 [555/10/6148] Building CXX object tools\lldb\tools\lldb-dap\CMakeFiles\lldb-dap.dir\InstructionBreakpoint.cpp.obj
525.978 [554/10/6149] Building CXX object tools\lldb\tools\lldb-dap\CMakeFiles\lldb-dap.dir\Handler\ResponseHandler.cpp.obj
526.558 [553/10/6150] Building CXX object tools\lldb\tools\lldb-dap\CMakeFiles\lldb-dap.dir\LLDBUtils.cpp.obj
527.696 [552/10/6151] Building CXX object tools\lldb\tools\lldb-dap\CMakeFiles\lldb-dap.dir\OutputRedirector.cpp.obj
528.731 [551/10/6152] Building CXX object tools\lldb\tools\lldb-dap\CMakeFiles\lldb-dap.dir\EventHelper.cpp.obj
531.089 [550/10/6153] Building CXX object tools\lldb\tools\lldb-dap\CMakeFiles\lldb-dap.dir\Transport.cpp.obj
FAILED: tools/lldb/tools/lldb-dap/CMakeFiles/lldb-dap.dir/Transport.cpp.obj 
ccache C:\Users\tcwg\scoop\apps\llvm-arm64\current\bin\clang-cl.exe  /nologo -TP -DGTEST_HAS_RTTI=0 -DLLVM_BUILD_STATIC -DUNICODE -D_CRT_NONSTDC_NO_DEPRECATE -D_CRT_NONSTDC_NO_WARNINGS -D_CRT_SECURE_NO_DEPRECATE -D_CRT_SECURE_NO_WARNINGS -D_ENABLE_EXTENDED_ALIGNED_STORAGE -D_HAS_EXCEPTIONS=0 -D_SCL_SECURE_NO_DEPRECATE -D_SCL_SECURE_NO_WARNINGS -D_UNICODE -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS -IC:\Users\tcwg\llvm-worker\lldb-aarch64-windows\build\tools\lldb\tools\lldb-dap -IC:\Users\tcwg\llvm-worker\lldb-aarch64-windows\llvm-project\lldb\tools\lldb-dap -IC:\Users\tcwg\llvm-worker\lldb-aarch64-windows\llvm-project\lldb\include -IC:\Users\tcwg\llvm-worker\lldb-aarch64-windows\build\tools\lldb\include -IC:\Users\tcwg\llvm-worker\lldb-aarch64-windows\build\include -IC:\Users\tcwg\llvm-worker\lldb-aarch64-windows\llvm-project\llvm\include -IC:\Users\tcwg\scoop\apps\python\current\include -IC:\Users\tcwg\llvm-worker\lldb-aarch64-windows\llvm-project\llvm\..\clang\include -IC:\Users\tcwg\llvm-worker\lldb-aarch64-windows\build\tools\lldb\..\clang\include /DWIN32 /D_WINDOWS   /Zc:inline /Zc:__cplusplus /Oi /Brepro /bigobj /permissive- -Werror=unguarded-availability-new /W4  -Wextra -Wno-unused-parameter -Wwrite-strings -Wcast-qual -Wmissing-field-initializers -Wimplicit-fallthrough -Wcovered-switch-default -Wno-noexcept-type -Wnon-virtual-dtor -Wdelete-non-virtual-dtor -Wsuggest-override -Wstring-conversion -Wmisleading-indentation -Wctad-maybe-unsupported /Gw -Wno-vla-extension /O2 /Ob2 /DNDEBUG -std:c++17 -MD   -wd4018 -wd4068 -wd4150 -wd4201 -wd4251 -wd4521 -wd4530 -wd4589  /EHs-c- /GR- /showIncludes /Fotools\lldb\tools\lldb-dap\CMakeFiles\lldb-dap.dir\Transport.cpp.obj /Fdtools\lldb\tools\lldb-dap\CMakeFiles\lldb-dap.dir\ -c -- C:\Users\tcwg\llvm-worker\lldb-aarch64-windows\llvm-project\lldb\tools\lldb-dap\Transport.cpp
C:\Users\tcwg\llvm-worker\lldb-aarch64-windows\llvm-project\lldb\tools\lldb-dap\Transport.cpp(41,49): error: use of undeclared identifier 'eFDTypeSocket'
   41 |   timeout_supported = descriptor.GetFdType() == eFDTypeSocket;
      |                                                 ^
1 error generated.
531.532 [550/9/6154] Building CXX object tools\lldb\tools\lldb-dap\CMakeFiles\lldb-dap.dir\JSONUtils.cpp.obj
531.659 [550/8/6155] Building CXX object tools\lldb\tools\lldb-dap\CMakeFiles\lldb-dap.dir\Watchpoint.cpp.obj
531.814 [550/7/6156] Building CXX object tools\lldb\tools\lldb-dap\CMakeFiles\lldb-dap.dir\SourceBreakpoint.cpp.obj
532.888 [550/6/6157] Building CXX object tools\lldb\tools\lldb-dap\CMakeFiles\lldb-dap.dir\Handler\AttachRequestHandler.cpp.obj
533.063 [550/5/6158] Building CXX object tools\lldb\tools\lldb-dap\CMakeFiles\lldb-dap.dir\Handler\BreakpointLocationsHandler.cpp.obj
533.127 [550/4/6159] Building CXX object tools\lldb\tools\lldb-dap\CMakeFiles\lldb-dap.dir\Handler\CancelRequestHandler.cpp.obj
533.229 [550/3/6160] Building CXX object tools\lldb\tools\lldb-dap\CMakeFiles\lldb-dap.dir\lldb-dap.cpp.obj
533.606 [550/2/6161] Building CXX object tools\lldb\tools\lldb-dap\CMakeFiles\lldb-dap.dir\Handler\CompileUnitsRequestHandler.cpp.obj
534.693 [550/1/6162] Building CXX object tools\lldb\tools\lldb-dap\CMakeFiles\lldb-dap.dir\DAP.cpp.obj
ninja: build stopped: subcommand failed.

var-const pushed a commit to ldionne/llvm-project that referenced this pull request Apr 17, 2025
Adding support for cancelling requests.

There are two forms of request cancellation.

* Preemptively cancelling a request that is in the queue.
* Actively cancelling the in progress request as a best effort attempt
using `SBDebugger.RequestInterrupt()`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants