[Lldb-commits] [lldb] [lldb] Refactoring JSONTransport into an abstract RPC Message Handler and transport layer. (PR #153121)
Pavel Labath via lldb-commits
lldb-commits at lists.llvm.org
Mon Aug 18 05:01:56 PDT 2025
================
@@ -54,112 +50,226 @@ class TransportUnhandledContentsError
std::string m_unhandled_contents;
};
-class TransportInvalidError : public llvm::ErrorInfo<TransportInvalidError> {
+/// A transport is responsible for maintaining the connection to a client
+/// application, and reading/writing structured messages to it.
+///
+/// Transports have limited thread safety requirements:
+/// - Messages will not be sent concurrently.
+/// - Messages MAY be sent while Run() is reading, or its callback is active.
+template <typename Req, typename Resp, typename Evt> class Transport {
public:
- static char ID;
-
- TransportInvalidError() = default;
+ using Message = std::variant<Req, Resp, Evt>;
+
+ virtual ~Transport() = default;
+
+ /// Sends an event, a message that does not require a response.
+ virtual llvm::Error Event(const Evt &) = 0;
+ /// Sends a request, a message that expects a response.
+ virtual llvm::Error Request(const Req &) = 0;
+ /// Sends a response to a specific request.
+ virtual llvm::Error Response(const Resp &) = 0;
+
+ /// Implemented to handle incoming messages. (See Run() below).
+ class MessageHandler {
+ public:
+ virtual ~MessageHandler() = default;
+ /// Called when an event is received.
+ virtual void OnEvent(const Evt &) = 0;
+ /// Called when a request is received.
+ virtual void OnRequest(const Req &) = 0;
+ /// Called when a response is received.
+ virtual void OnResponse(const Resp &) = 0;
+
+ /// Called when an error occurs while reading from the transport.
+ ///
+ /// NOTE: This does *NOT* indicate that a specific request failed, but that
+ /// there was an error in the underlying transport.
+ virtual void OnError(MainLoopBase &, llvm::Error) = 0;
+
+ /// Called on EOF or disconnect.
+ virtual void OnEOF() = 0;
+ };
+
+ using MessageHandlerSP = std::shared_ptr<MessageHandler>;
+
+ /// RegisterMessageHandler registers the Transport with the given MainLoop and
+ /// handles any incoming messages using the given MessageHandler.
+ ///
+ /// If an unexpected error occurs, the MainLoop will be terminated and a log
+ /// message will include additional information about the termination reason.
+ virtual llvm::Expected<MainLoop::ReadHandleUP>
+ RegisterMessageHandler(MainLoop &loop, MessageHandler &handler) = 0;
- void log(llvm::raw_ostream &OS) const override;
- std::error_code convertToErrorCode() const override;
+protected:
+ template <typename... Ts> inline auto Logv(const char *Fmt, Ts &&...Vals) {
+ Log(llvm::formatv(Fmt, std::forward<Ts>(Vals)...).str());
+ }
+ virtual void Log(llvm::StringRef message) = 0;
};
-/// A transport class that uses JSON for communication.
-class JSONTransport {
+/// A JSONTransport will encode and decode messages using JSON.
+template <typename Req, typename Resp, typename Evt>
+class JSONTransport : public Transport<Req, Resp, Evt> {
public:
- using ReadHandleUP = MainLoopBase::ReadHandleUP;
- template <typename T>
- using Callback = std::function<void(MainLoopBase &, const llvm::Expected<T>)>;
-
- JSONTransport(lldb::IOObjectSP input, lldb::IOObjectSP output);
- virtual ~JSONTransport() = default;
-
- /// Transport is not copyable.
- /// @{
- JSONTransport(const JSONTransport &rhs) = delete;
- void operator=(const JSONTransport &rhs) = delete;
- /// @}
-
- /// Writes a message to the output stream.
- template <typename T> llvm::Error Write(const T &t) {
- const std::string message = llvm::formatv("{0}", toJSON(t)).str();
- return WriteImpl(message);
+ using Transport<Req, Resp, Evt>::Transport;
+ using MessageHandler = typename Transport<Req, Resp, Evt>::MessageHandler;
+
+ JSONTransport(lldb::IOObjectSP in, lldb::IOObjectSP out)
+ : m_in(in), m_out(out) {}
+
+ llvm::Error Event(const Evt &evt) override { return Write(evt); }
+ llvm::Error Request(const Req &req) override { return Write(req); }
+ llvm::Error Response(const Resp &resp) override { return Write(resp); }
+
+ llvm::Expected<MainLoop::ReadHandleUP>
+ RegisterMessageHandler(MainLoop &loop, MessageHandler &handler) override {
+ Status status;
+ MainLoop::ReadHandleUP read_handle = loop.RegisterReadObject(
+ m_in,
+ std::bind(&JSONTransport::OnRead, this, std::placeholders::_1,
+ std::ref(handler)),
+ status);
+ if (status.Fail()) {
+ return status.takeError();
+ }
+ return read_handle;
}
- /// Registers the transport with the MainLoop.
- template <typename T>
- llvm::Expected<ReadHandleUP> RegisterReadObject(MainLoopBase &loop,
- Callback<T> read_cb) {
- Status error;
- ReadHandleUP handle = loop.RegisterReadObject(
- m_input,
- [read_cb, this](MainLoopBase &loop) {
- char buf[kReadBufferSize];
- size_t num_bytes = sizeof(buf);
- if (llvm::Error error = m_input->Read(buf, num_bytes).takeError()) {
- read_cb(loop, std::move(error));
- return;
- }
- if (num_bytes)
- m_buffer.append(std::string(buf, num_bytes));
-
- // If the buffer has contents, try parsing any pending messages.
- if (!m_buffer.empty()) {
- llvm::Expected<std::vector<std::string>> messages = Parse();
- if (llvm::Error error = messages.takeError()) {
- read_cb(loop, std::move(error));
- return;
- }
-
- for (const auto &message : *messages)
- if constexpr (std::is_same<T, std::string>::value)
- read_cb(loop, message);
- else
- read_cb(loop, llvm::json::parse<T>(message));
- }
-
- // On EOF, notify the callback after the remaining messages were
- // handled.
- if (num_bytes == 0) {
- if (m_buffer.empty())
- read_cb(loop, llvm::make_error<TransportEOFError>());
- else
- read_cb(loop, llvm::make_error<TransportUnhandledContentsError>(
- std::string(m_buffer)));
- }
- },
- error);
- if (error.Fail())
- return error.takeError();
- return handle;
- }
+ /// Public for testing purposes, otherwise this should be an implementation
+ /// detail.
+ static constexpr size_t kReadBufferSize = 1024;
protected:
- template <typename... Ts> inline auto Logv(const char *Fmt, Ts &&...Vals) {
- Log(llvm::formatv(Fmt, std::forward<Ts>(Vals)...).str());
+ virtual llvm::Expected<std::vector<std::string>> Parse() = 0;
+ virtual std::string Encode(const llvm::json::Value &message) = 0;
+ llvm::Error Write(const llvm::json::Value &message) {
+ this->Logv("<-- {0}", message);
+ std::string output = Encode(message);
+ size_t bytes_written = output.size();
+ return m_out->Write(output.data(), bytes_written).takeError();
}
- virtual void Log(llvm::StringRef message);
- virtual llvm::Error WriteImpl(const std::string &message) = 0;
- virtual llvm::Expected<std::vector<std::string>> Parse() = 0;
+ llvm::SmallString<kReadBufferSize> m_buffer;
- static constexpr size_t kReadBufferSize = 1024;
+private:
+ void OnRead(MainLoopBase &loop, MessageHandler &handler) {
+ char buf[kReadBufferSize];
+ size_t num_bytes = sizeof(buf);
+ if (Status status = m_in->Read(buf, num_bytes); status.Fail()) {
+ handler.OnError(loop, status.takeError());
+ return;
+ }
+
+ if (num_bytes)
+ m_buffer.append(llvm::StringRef(buf, num_bytes));
+
+ // If the buffer has contents, try parsing any pending messages.
+ if (!m_buffer.empty()) {
+ llvm::Expected<std::vector<std::string>> raw_messages = Parse();
+ if (llvm::Error error = raw_messages.takeError()) {
+ handler.OnError(loop, std::move(error));
+ return;
+ }
+
+ for (const std::string &raw_message : *raw_messages) {
+ llvm::Expected<typename Transport<Req, Resp, Evt>::Message> message =
+ llvm::json::parse<typename Transport<Req, Resp, Evt>::Message>(
+ raw_message);
+ if (!message) {
+ handler.OnError(loop, message.takeError());
+ continue;
+ }
+
+ if (Evt *evt = std::get_if<Evt>(&*message)) {
----------------
labath wrote:
I'm tempted to use std::visit here as well, although that would mean using the same name for all of these functions (`Handle`? `operator()` ?), so 🤷 ...
https://github.com/llvm/llvm-project/pull/153121
More information about the lldb-commits
mailing list