214 lines · cpp
1//===--- Client.cpp ----------------------------------------------*- C++-*-===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include <grpc++/grpc++.h>10 11#include "Client.h"12#include "Feature.h"13#include "Service.grpc.pb.h"14#include "index/Index.h"15#include "marshalling/Marshalling.h"16#include "support/Logger.h"17#include "support/Trace.h"18#include "llvm/ADT/SmallString.h"19#include "llvm/ADT/StringRef.h"20#include "llvm/Support/Error.h"21 22#include <atomic>23#include <chrono>24#include <memory>25 26namespace clang {27namespace clangd {28namespace remote {29namespace {30 31llvm::StringRef toString(const grpc_connectivity_state &State) {32 switch (State) {33 case GRPC_CHANNEL_IDLE:34 return "idle";35 case GRPC_CHANNEL_CONNECTING:36 return "connecting";37 case GRPC_CHANNEL_READY:38 return "ready";39 case GRPC_CHANNEL_TRANSIENT_FAILURE:40 return "transient failure";41 case GRPC_CHANNEL_SHUTDOWN:42 return "shutdown";43 }44 llvm_unreachable("Not a valid grpc_connectivity_state.");45}46 47class IndexClient : public clangd::SymbolIndex {48 void updateConnectionStatus() const {49 auto NewStatus = Channel->GetState(/*try_to_connect=*/false);50 auto OldStatus = ConnectionStatus.exchange(NewStatus);51 if (OldStatus != NewStatus)52 vlog("Remote index connection [{0}]: {1} => {2}", ServerAddress,53 toString(OldStatus), toString(NewStatus));54 }55 56 template <typename RequestT, typename ReplyT>57 using StreamingCall = std::unique_ptr<grpc::ClientReader<ReplyT>> (58 remote::v1::SymbolIndex::Stub::*)(grpc::ClientContext *,59 const RequestT &);60 61 template <typename RequestT, typename ReplyT, typename ClangdRequestT,62 typename CallbackT>63 bool streamRPC(ClangdRequestT Request,64 StreamingCall<RequestT, ReplyT> RPCCall,65 CallbackT Callback) const {66 updateConnectionStatus();67 // We initialize to true because stream might be broken before we see the68 // final message. In such a case there are actually more results on the69 // stream, but we couldn't get to them.70 bool HasMore = true;71 trace::Span Tracer(RequestT::descriptor()->name());72 const auto RPCRequest = ProtobufMarshaller->toProtobuf(Request);73 SPAN_ATTACH(Tracer, "Request", RPCRequest.DebugString());74 grpc::ClientContext Context;75 Context.AddMetadata("version", versionString());76 Context.AddMetadata("features", featureString());77 Context.AddMetadata("platform", platformString());78 std::chrono::system_clock::time_point StartTime =79 std::chrono::system_clock::now();80 auto Deadline = StartTime + DeadlineWaitingTime;81 Context.set_deadline(Deadline);82 auto Reader = (Stub.get()->*RPCCall)(&Context, RPCRequest);83 dlog("Sending {0}: {1}", RequestT::descriptor()->name(),84 RPCRequest.DebugString());85 ReplyT Reply;86 unsigned Successful = 0;87 unsigned FailedToParse = 0;88 while (Reader->Read(&Reply)) {89 if (!Reply.has_stream_result()) {90 HasMore = Reply.final_result().has_more();91 continue;92 }93 auto Response = ProtobufMarshaller->fromProtobuf(Reply.stream_result());94 if (!Response) {95 elog("Received invalid {0}: {1}. Reason: {2}",96 ReplyT::descriptor()->name(), Reply.stream_result().DebugString(),97 Response.takeError());98 ++FailedToParse;99 continue;100 }101 Callback(*Response);102 ++Successful;103 }104 auto Millis = std::chrono::duration_cast<std::chrono::milliseconds>(105 std::chrono::system_clock::now() - StartTime)106 .count();107 vlog("Remote index [{0}]: {1} => {2} results in {3}ms.", ServerAddress,108 RequestT::descriptor()->name(), Successful, Millis);109 SPAN_ATTACH(Tracer, "Status", Reader->Finish().ok());110 SPAN_ATTACH(Tracer, "Successful", Successful);111 SPAN_ATTACH(Tracer, "Failed to parse", FailedToParse);112 updateConnectionStatus();113 return HasMore;114 }115 116public:117 IndexClient(118 std::shared_ptr<grpc::Channel> Channel, llvm::StringRef Address,119 llvm::StringRef ProjectRoot,120 std::chrono::milliseconds DeadlineTime = std::chrono::milliseconds(1000))121 : Stub(remote::v1::SymbolIndex::NewStub(Channel)), Channel(Channel),122 ServerAddress(Address),123 ConnectionStatus(Channel->GetState(/*try_to_connect=*/true)),124 ProtobufMarshaller(new Marshaller(/*RemoteIndexRoot=*/"",125 /*LocalIndexRoot=*/ProjectRoot)),126 DeadlineWaitingTime(DeadlineTime) {127 assert(!ProjectRoot.empty());128 }129 130 void lookup(const clangd::LookupRequest &Request,131 llvm::function_ref<void(const clangd::Symbol &)> Callback)132 const override {133 streamRPC(Request, &remote::v1::SymbolIndex::Stub::Lookup, Callback);134 }135 136 bool fuzzyFind(const clangd::FuzzyFindRequest &Request,137 llvm::function_ref<void(const clangd::Symbol &)> Callback)138 const override {139 return streamRPC(Request, &remote::v1::SymbolIndex::Stub::FuzzyFind,140 Callback);141 }142 143 bool144 refs(const clangd::RefsRequest &Request,145 llvm::function_ref<void(const clangd::Ref &)> Callback) const override {146 return streamRPC(Request, &remote::v1::SymbolIndex::Stub::Refs, Callback);147 }148 149 bool containedRefs(const clangd::ContainedRefsRequest &Request,150 llvm::function_ref<void(const ContainedRefsResult &)>151 Callback) const override {152 return streamRPC(Request, &remote::v1::SymbolIndex::Stub::ContainedRefs,153 Callback);154 }155 156 void157 relations(const clangd::RelationsRequest &Request,158 llvm::function_ref<void(const SymbolID &, const clangd::Symbol &)>159 Callback) const override {160 streamRPC(Request, &remote::v1::SymbolIndex::Stub::Relations,161 // Unpack protobuf Relation.162 [&](std::pair<SymbolID, clangd::Symbol> SubjectAndObject) {163 Callback(SubjectAndObject.first, SubjectAndObject.second);164 });165 }166 167 void reverseRelations(168 const clangd::RelationsRequest &Request,169 llvm::function_ref<void(const SymbolID &, const clangd::Symbol &)>170 Callback) const override {171 streamRPC(Request, &remote::v1::SymbolIndex::Stub::ReverseRelations,172 // Unpack protobuf Relation.173 [&](std::pair<SymbolID, clangd::Symbol> SubjectAndObject) {174 Callback(SubjectAndObject.first, SubjectAndObject.second);175 });176 }177 178 llvm::unique_function<IndexContents(llvm::StringRef) const>179 indexedFiles() const override {180 // FIXME: For now we always return IndexContents::None regardless of whether181 // the file was indexed or not. A possible implementation could be182 // based on the idea that we do not want to send a request at every183 // call of a function returned by IndexClient::indexedFiles().184 return [](llvm::StringRef) { return IndexContents::None; };185 }186 187 // IndexClient does not take any space since the data is stored on the188 // server.189 size_t estimateMemoryUsage() const override { return 0; }190 191private:192 std::unique_ptr<remote::v1::SymbolIndex::Stub> Stub;193 std::shared_ptr<grpc::Channel> Channel;194 llvm::SmallString<256> ServerAddress;195 mutable std::atomic<grpc_connectivity_state> ConnectionStatus;196 std::unique_ptr<Marshaller> ProtobufMarshaller;197 // Each request will be terminated if it takes too long.198 std::chrono::milliseconds DeadlineWaitingTime;199};200 201} // namespace202 203std::unique_ptr<clangd::SymbolIndex> getClient(llvm::StringRef Address,204 llvm::StringRef ProjectRoot) {205 const auto Channel =206 grpc::CreateChannel(Address.str(), grpc::InsecureChannelCredentials());207 return std::unique_ptr<clangd::SymbolIndex>(208 new IndexClient(Channel, Address, ProjectRoot));209}210 211} // namespace remote212} // namespace clangd213} // namespace clang214