================ @@ -100,22 +175,301 @@ template <typename Req, typename Resp, typename Evt> class Transport { virtual llvm::Expected<MainLoop::ReadHandleUP> RegisterMessageHandler(MainLoop &loop, MessageHandler &handler) = 0; - // FIXME: Refactor mcp::Server to not directly access log on the transport. - // protected: +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; + + /// Function object to reply to a call. + /// Each instance must be called exactly once, otherwise: + /// - the bug is logged, and (in debug mode) an assert will fire + /// - if there was no reply, an error reply is sent + /// - if there were multiple replies, only the first is sent + class ReplyOnce { + std::atomic<bool> replied = {false}; + const Req req; + JSONTransport *transport; // Null when moved-from. + JSONTransport::MessageHandler *handler; // Null when moved-from. + + public: + ReplyOnce(const Req req, JSONTransport *transport, + JSONTransport::MessageHandler *handler) + : req(req), transport(transport), handler(handler) { + assert(handler); + } + ReplyOnce(ReplyOnce &&other) + : replied(other.replied.load()), req(other.req), + transport(other.transport), handler(other.handler) { + other.transport = nullptr; + other.handler = nullptr; + } + ReplyOnce &operator=(ReplyOnce &&) = delete; + ReplyOnce(const ReplyOnce &) = delete; + ReplyOnce &operator=(const ReplyOnce &) = delete; + + ~ReplyOnce() { + if (transport && handler && !replied) { + assert(false && "must reply to all calls!"); + (*this)(MakeResponse<Req, Resp>( + req, llvm::createStringError("failed to reply"))); + } + } + + void operator()(const Resp &resp) { + assert(transport && handler && "moved-from!"); + if (replied.exchange(true)) { + assert(false && "must reply to each call only once!"); + return; + } + + if (llvm::Error error = transport->Send(resp)) + handler->OnError(std::move(error)); + } + }; + +public: + class Binder; + using BinderUP = std::unique_ptr<Binder>; + + /// Binder collects a table of functions that handle calls. ---------------- ashgti wrote:
Also, another benefit of the binder not being part of the Server implementation here specifically is that the Server can choose if it wants to handle a single client or multiple clients per instance. We can bind the methods with additional context parameters to make the server independent of the transport. https://github.com/llvm/llvm-project/pull/159160 _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits