================ @@ -54,112 +50,220 @@ 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; + + // Called by transport to send outgoing messages. + virtual void Event(const Evt &) = 0; + virtual void Request(const Req &) = 0; + virtual void Response(const Resp &) = 0; + + /// Implemented to handle incoming messages. (See Run() below). + class MessageHandler { + public: + virtual ~MessageHandler() = default; + virtual void OnEvent(const Evt &) = 0; + virtual void OnRequest(const Req &) = 0; + virtual void OnResponse(const Resp &) = 0; + }; + + /// Called by server or client to receive messages from the connection. + /// The transport should in turn invoke the handler to process messages. + /// The MainLoop is used to handle reading from the incoming connection and + /// will run until the loop is terminated. + virtual llvm::Error Run(MainLoop &, MessageHandler &) = 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; + + JSONTransport(lldb::IOObjectSP in, lldb::IOObjectSP out) + : m_in(in), m_out(out) {} + + void Event(const Evt &evt) override { Write(evt); } + void Request(const Req &req) override { Write(req); } + void Response(const Resp &resp) override { Write(resp); } + + /// Run registers the transport with the given MainLoop and handles any + /// incoming messages using the given MessageHandler. + llvm::Error + Run(MainLoop &loop, + typename Transport<Req, Resp, Evt>::MessageHandler &handler) override { + llvm::Error error = llvm::Error::success(); + Status status; + auto read_handle = loop.RegisterReadObject( + m_in, + std::bind(&JSONTransport::OnRead, this, &error, std::placeholders::_1, + std::ref(handler)), + status); + if (status.Fail()) { + // This error is only set if the read object handler is invoked, mark it + // as consumed if registration of the handler failed. + llvm::consumeError(std::move(error)); + return status.takeError(); + } + + status = loop.Run(); + if (status.Fail()) + return status.takeError(); + return error; } - /// 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; + void Write(const llvm::json::Value &message) { + this->Logv("<-- {0}", message); + std::string output = Encode(message); + size_t bytes_written = output.size(); + Status status = m_out->Write(output.data(), bytes_written); + if (status.Fail()) { + this->Logv("writing failed: s{0}", status.AsCString()); + } ---------------- JDevlieghere wrote:
```suggestion if (status.Fail()) this->Logv("writing failed: s{0}", status.AsCString()); ``` 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