================ @@ -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 _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits