|
39 | 39 | #include <algorithm> |
40 | 40 | #include <cassert> |
41 | 41 | #include <chrono> |
| 42 | +#include <condition_variable> |
42 | 43 | #include <cstdarg> |
43 | 44 | #include <cstdio> |
44 | 45 | #include <fstream> |
| 46 | +#include <future> |
45 | 47 | #include <memory> |
46 | 48 | #include <mutex> |
47 | 49 | #include <string> |
@@ -240,6 +242,21 @@ void DAP::SendJSON(const llvm::json::Value &json) { |
240 | 242 | } |
241 | 243 |
|
242 | 244 | void DAP::Send(const protocol::Message &message) { |
| 245 | + if (auto *resp = std::get_if<protocol::Response>(&message); |
| 246 | + resp && debugger.InterruptRequested()) { |
| 247 | + // If the debugger was interrupted, convert this response into a 'cancelled' |
| 248 | + // response. |
| 249 | + protocol::Response cancelled; |
| 250 | + cancelled.command = resp->command; |
| 251 | + cancelled.request_seq = resp->request_seq; |
| 252 | + cancelled.success = false; |
| 253 | + cancelled.message = protocol::Response::Message::cancelled; |
| 254 | + if (llvm::Error err = transport.Write(cancelled)) |
| 255 | + DAP_LOG_ERROR(log, std::move(err), "({1}) write failed: {0}", |
| 256 | + transport.GetClientName()); |
| 257 | + return; |
| 258 | + } |
| 259 | + |
243 | 260 | if (llvm::Error err = transport.Write(message)) |
244 | 261 | DAP_LOG_ERROR(log, std::move(err), "({1}) write failed: {0}", |
245 | 262 | transport.GetClientName()); |
@@ -673,6 +690,10 @@ void DAP::SetTarget(const lldb::SBTarget target) { |
673 | 690 |
|
674 | 691 | bool DAP::HandleObject(const protocol::Message &M) { |
675 | 692 | if (const auto *req = std::get_if<protocol::Request>(&M)) { |
| 693 | + // Clear interrupt marker prior to handling the next request. |
| 694 | + if (debugger.InterruptRequested()) |
| 695 | + debugger.CancelInterruptRequest(); |
| 696 | + |
676 | 697 | auto handler_pos = request_handlers.find(req->command); |
677 | 698 | if (handler_pos != request_handlers.end()) { |
678 | 699 | (*handler_pos->second)(*req); |
@@ -777,28 +798,134 @@ llvm::Error DAP::Disconnect(bool terminateDebuggee) { |
777 | 798 | return ToError(error); |
778 | 799 | } |
779 | 800 |
|
| 801 | +template <typename T> |
| 802 | +static std::optional<T> getArgumentsIfRequest(const protocol::Message &pm, |
| 803 | + llvm::StringLiteral command) { |
| 804 | + auto *const req = std::get_if<protocol::Request>(&pm); |
| 805 | + if (!req || req->command != command) |
| 806 | + return std::nullopt; |
| 807 | + |
| 808 | + T args; |
| 809 | + llvm::json::Path::Root root; |
| 810 | + if (!fromJSON(req->arguments, args, root)) { |
| 811 | + return std::nullopt; |
| 812 | + } |
| 813 | + |
| 814 | + return std::move(args); |
| 815 | +} |
| 816 | + |
780 | 817 | llvm::Error DAP::Loop() { |
781 | | - auto cleanup = llvm::make_scope_exit([this]() { |
| 818 | + std::deque<protocol::Message> queue; |
| 819 | + std::condition_variable queue_cv; |
| 820 | + std::mutex queue_mutex; |
| 821 | + std::future<llvm::Error> queue_reader = std::async([&]() -> llvm::Error { |
| 822 | + llvm::set_thread_name(transport.GetClientName() + ".transport_handler"); |
| 823 | + auto cleanup = llvm::make_scope_exit([&]() { |
| 824 | + // Ensure we're marked as disconnecting when the reader exits. |
| 825 | + disconnecting = true; |
| 826 | + queue_cv.notify_all(); |
| 827 | + }); |
| 828 | + |
| 829 | + while (!disconnecting) { |
| 830 | + llvm::Expected<std::optional<protocol::Message>> next = |
| 831 | + transport.Read(std::chrono::seconds(1)); |
| 832 | + bool timeout = false; |
| 833 | + if (llvm::Error Err = llvm::handleErrors( |
| 834 | + next.takeError(), |
| 835 | + [&](std::unique_ptr<llvm::StringError> Err) -> llvm::Error { |
| 836 | + if (Err->convertToErrorCode() == std::errc::timed_out) { |
| 837 | + timeout = true; |
| 838 | + return llvm::Error::success(); |
| 839 | + } |
| 840 | + return llvm::Error(std::move(Err)); |
| 841 | + })) |
| 842 | + return Err; |
| 843 | + |
| 844 | + // If the read timed out, continue to check if we should disconnect. |
| 845 | + if (timeout) |
| 846 | + continue; |
| 847 | + |
| 848 | + // nullopt is returned on EOF. |
| 849 | + if (!*next) |
| 850 | + break; |
| 851 | + |
| 852 | + { |
| 853 | + std::lock_guard<std::mutex> lock(queue_mutex); |
| 854 | + |
| 855 | + // If a cancel is requested for the active request, make a best |
| 856 | + // effort attempt to interrupt. |
| 857 | + if (const auto cancel_args = |
| 858 | + getArgumentsIfRequest<protocol::CancelArguments>(**next, |
| 859 | + "cancel"); |
| 860 | + cancel_args && active_seq == cancel_args->requestId) { |
| 861 | + DAP_LOG(log, "({0}) interrupting inflight request {1}", |
| 862 | + transport.GetClientName(), active_seq); |
| 863 | + debugger.RequestInterrupt(); |
| 864 | + debugger.GetCommandInterpreter().InterruptCommand(); |
| 865 | + } |
| 866 | + |
| 867 | + queue.push_back(std::move(**next)); |
| 868 | + } |
| 869 | + queue_cv.notify_one(); |
| 870 | + } |
| 871 | + |
| 872 | + return llvm::Error::success(); |
| 873 | + }); |
| 874 | + |
| 875 | + auto cleanup = llvm::make_scope_exit([&]() { |
782 | 876 | out.Stop(); |
783 | 877 | err.Stop(); |
784 | 878 | StopEventHandlers(); |
785 | 879 | }); |
| 880 | + |
786 | 881 | while (!disconnecting) { |
787 | | - llvm::Expected<std::optional<protocol::Message>> next = transport.Read(); |
788 | | - if (!next) |
789 | | - return next.takeError(); |
| 882 | + protocol::Message next; |
| 883 | + { |
| 884 | + std::unique_lock<std::mutex> lock(queue_mutex); |
| 885 | + queue_cv.wait(lock, [&] { return disconnecting || !queue.empty(); }); |
790 | 886 |
|
791 | | - // nullopt on EOF |
792 | | - if (!*next) |
793 | | - break; |
| 887 | + if (queue.empty()) |
| 888 | + break; |
| 889 | + |
| 890 | + next = queue.front(); |
| 891 | + queue.pop_front(); |
| 892 | + |
| 893 | + if (protocol::Request *req = std::get_if<protocol::Request>(&next)) { |
| 894 | + active_seq = req->seq; |
| 895 | + |
| 896 | + // Check if we should preempt this request from a queued cancel. |
| 897 | + bool cancelled = false; |
| 898 | + for (const auto &message : queue) { |
| 899 | + if (const auto args = |
| 900 | + getArgumentsIfRequest<protocol::CancelArguments>(message, |
| 901 | + "cancel"); |
| 902 | + args && args->requestId == req->seq) { |
| 903 | + cancelled = true; |
| 904 | + break; |
| 905 | + } |
| 906 | + } |
794 | 907 |
|
795 | | - if (!HandleObject(**next)) { |
| 908 | + // Preempt the request and immeidately respond with cancelled. |
| 909 | + if (cancelled) { |
| 910 | + protocol::Response response; |
| 911 | + response.request_seq = req->seq; |
| 912 | + response.command = req->command; |
| 913 | + response.success = false; |
| 914 | + response.message = protocol::Response::Message::cancelled; |
| 915 | + Send(response); |
| 916 | + continue; |
| 917 | + } |
| 918 | + } else |
| 919 | + active_seq = 0; |
| 920 | + } |
| 921 | + |
| 922 | + if (!HandleObject(next)) { |
796 | 923 | return llvm::createStringError(llvm::inconvertibleErrorCode(), |
797 | 924 | "unhandled packet"); |
798 | 925 | } |
799 | 926 | } |
800 | 927 |
|
801 | | - return llvm::Error::success(); |
| 928 | + return queue_reader.get(); |
802 | 929 | } |
803 | 930 |
|
804 | 931 | lldb::SBError DAP::WaitForProcessToStop(uint32_t seconds) { |
|
0 commit comments