Index: clangd/ClangdLSPServer.cpp =================================================================== --- clangd/ClangdLSPServer.cpp +++ clangd/ClangdLSPServer.cpp @@ -106,11 +106,14 @@ bool onCall(StringRef Method, json::Value Params, json::Value ID) override { log("<-- {0}({1})", Method, ID); + // Calls can be canceled by the client. Add cancellation context. + WithContext WithCancel(cancelableRequestContext(ID)); + ReplyOnce Reply(ID, Method, &Server); if (auto Handler = Calls.lookup(Method)) - Handler(std::move(Params), std::move(ID)); + Handler(std::move(Params), std::move(Reply)); else - Server.reply(ID, llvm::make_error("method not found", - ErrorCode::MethodNotFound)); + Reply(llvm::make_error("method not found", + ErrorCode::MethodNotFound)); return true; } @@ -124,34 +127,31 @@ } // Bind an LSP method name to a call. - template + template void bind(const char *Method, - void (ClangdLSPServer::*Handler)(const Param &, Callback)) { + void (ClangdLSPServer::*Handler)(const Param &, Callback)) { Calls[Method] = [Method, Handler, this](json::Value RawParams, - json::Value ID) { + ReplyOnce Reply) { Param P; if (!fromJSON(RawParams, P)) { elog("Failed to decode {0} request.", Method); - Server.reply(ID, make_error("failed to decode request", - ErrorCode::InvalidRequest)); + Reply(make_error("failed to decode request", + ErrorCode::InvalidRequest)); return; } trace::Span Tracer(Method); SPAN_ATTACH(Tracer, "Params", RawParams); auto *Trace = Tracer.Args; // We attach reply from another thread. - // Calls can be canceled by the client. Add cancellation context. - WithContext WithCancel(cancelableRequestContext(ID)); - // FIXME: this function should assert it's called exactly once. - (Server.*Handler)(P, [this, ID, Trace](llvm::Expected Result) { - if (Result) { + (Server.*Handler)(P, [Trace, Reply](llvm::Expected Res) { + if (Res) { if (Trace) - (*Trace)["Reply"] = *Result; - Server.reply(ID, json::Value(std::move(*Result))); + (*Trace)["Reply"] = *Res; + Reply(json::Value(std::move(*Res))); } else { - auto Err = Result.takeError(); + auto Err = Res.takeError(); if (Trace) (*Trace)["Error"] = llvm::to_string(Err); - Server.reply(ID, std::move(Err)); + Reply(std::move(Err)); } }); }; @@ -174,8 +174,47 @@ } private: + // Function object to reply to an LSP 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 { + struct State { + std::atomic Replied = {false}; + json::Value ID; + std::string Method; + ClangdLSPServer *Server; + + State(const json::Value &ID, StringRef Method, ClangdLSPServer *Server) + : ID(ID), Method(Method), Server(Server) {} + ~State() { + if (!Replied) { + elog("No reply to message {0}({1})", Method, ID); + assert(false && "must reply to all calls!"); + Server->reply(ID, make_error("server failed to reply", + ErrorCode::InternalError)); + } + } + }; + std::shared_ptr MyState; + + public: + ReplyOnce(const json::Value &ID, StringRef Method, ClangdLSPServer *Server) + : MyState(std::make_shared(ID, Method, Server)) {} + + void operator()(llvm::Expected Reply) const { + if (MyState->Replied.exchange(true)) { + elog("Replied twice to message {0}({1})", MyState->Method, MyState->ID); + assert(false && "must reply to each call only once!"); + return; + } + MyState->Server->reply(MyState->ID, std::move(Reply)); + } + }; + llvm::StringMap> Notifications; - llvm::StringMap> Calls; + llvm::StringMap> Calls; // Method calls may be cancelled by ID, so keep track of their state. // This needs a mutex: handlers may finish on a different thread, and that's