//===--- Client.cpp ----------------------------------------------*- C++-*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include #include "Client.h" #include "Feature.h" #include "Service.grpc.pb.h" #include "index/Index.h" #include "marshalling/Marshalling.h" #include "support/Logger.h" #include "support/Trace.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Error.h" #include #include #include namespace clang { namespace clangd { namespace remote { namespace { llvm::StringRef toString(const grpc_connectivity_state &State) { switch (State) { case GRPC_CHANNEL_IDLE: return "idle"; case GRPC_CHANNEL_CONNECTING: return "connecting"; case GRPC_CHANNEL_READY: return "ready"; case GRPC_CHANNEL_TRANSIENT_FAILURE: return "transient failure"; case GRPC_CHANNEL_SHUTDOWN: return "shutdown"; } llvm_unreachable("Not a valid grpc_connectivity_state."); } class IndexClient : public clangd::SymbolIndex { void updateConnectionStatus() const { auto NewStatus = Channel->GetState(/*try_to_connect=*/false); auto OldStatus = ConnectionStatus.exchange(NewStatus); if (OldStatus != NewStatus) vlog("Remote index connection [{0}]: {1} => {2}", ServerAddress, toString(OldStatus), toString(NewStatus)); } template using StreamingCall = std::unique_ptr> ( remote::v1::SymbolIndex::Stub::*)(grpc::ClientContext *, const RequestT &); template bool streamRPC(ClangdRequestT Request, StreamingCall RPCCall, CallbackT Callback) const { updateConnectionStatus(); // We initialize to true because stream might be broken before we see the // final message. In such a case there are actually more results on the // stream, but we couldn't get to them. bool HasMore = true; trace::Span Tracer(RequestT::descriptor()->name()); const auto RPCRequest = ProtobufMarshaller->toProtobuf(Request); SPAN_ATTACH(Tracer, "Request", RPCRequest.DebugString()); grpc::ClientContext Context; Context.AddMetadata("version", versionString()); Context.AddMetadata("features", featureString()); Context.AddMetadata("platform", platformString()); std::chrono::system_clock::time_point StartTime = std::chrono::system_clock::now(); auto Deadline = StartTime + DeadlineWaitingTime; Context.set_deadline(Deadline); auto Reader = (Stub.get()->*RPCCall)(&Context, RPCRequest); dlog("Sending {0}: {1}", RequestT::descriptor()->name(), RPCRequest.DebugString()); ReplyT Reply; unsigned Successful = 0; unsigned FailedToParse = 0; while (Reader->Read(&Reply)) { if (!Reply.has_stream_result()) { HasMore = Reply.final_result().has_more(); continue; } auto Response = ProtobufMarshaller->fromProtobuf(Reply.stream_result()); if (!Response) { elog("Received invalid {0}: {1}. Reason: {2}", ReplyT::descriptor()->name(), Reply.stream_result().DebugString(), Response.takeError()); ++FailedToParse; continue; } Callback(*Response); ++Successful; } auto Millis = std::chrono::duration_cast( std::chrono::system_clock::now() - StartTime) .count(); vlog("Remote index [{0}]: {1} => {2} results in {3}ms.", ServerAddress, RequestT::descriptor()->name(), Successful, Millis); SPAN_ATTACH(Tracer, "Status", Reader->Finish().ok()); SPAN_ATTACH(Tracer, "Successful", Successful); SPAN_ATTACH(Tracer, "Failed to parse", FailedToParse); updateConnectionStatus(); return HasMore; } public: IndexClient( std::shared_ptr Channel, llvm::StringRef Address, llvm::StringRef ProjectRoot, std::chrono::milliseconds DeadlineTime = std::chrono::milliseconds(1000)) : Stub(remote::v1::SymbolIndex::NewStub(Channel)), Channel(Channel), ServerAddress(Address), ConnectionStatus(Channel->GetState(/*try_to_connect=*/true)), ProtobufMarshaller(new Marshaller(/*RemoteIndexRoot=*/"", /*LocalIndexRoot=*/ProjectRoot)), DeadlineWaitingTime(DeadlineTime) { assert(!ProjectRoot.empty()); } void lookup(const clangd::LookupRequest &Request, llvm::function_ref Callback) const override { streamRPC(Request, &remote::v1::SymbolIndex::Stub::Lookup, Callback); } bool fuzzyFind(const clangd::FuzzyFindRequest &Request, llvm::function_ref Callback) const override { return streamRPC(Request, &remote::v1::SymbolIndex::Stub::FuzzyFind, Callback); } bool refs(const clangd::RefsRequest &Request, llvm::function_ref Callback) const override { return streamRPC(Request, &remote::v1::SymbolIndex::Stub::Refs, Callback); } void relations(const clangd::RelationsRequest &Request, llvm::function_ref Callback) const override { streamRPC(Request, &remote::v1::SymbolIndex::Stub::Relations, // Unpack protobuf Relation. [&](std::pair SubjectAndObject) { Callback(SubjectAndObject.first, SubjectAndObject.second); }); } llvm::unique_function indexedFiles() const override { // FIXME: For now we always return IndexContents::None regardless of whether // the file was indexed or not. A possible implementation could be // based on the idea that we do not want to send a request at every // call of a function returned by IndexClient::indexedFiles(). return [](llvm::StringRef) { return IndexContents::None; }; } // IndexClient does not take any space since the data is stored on the // server. size_t estimateMemoryUsage() const override { return 0; } private: std::unique_ptr Stub; std::shared_ptr Channel; llvm::SmallString<256> ServerAddress; mutable std::atomic ConnectionStatus; std::unique_ptr ProtobufMarshaller; // Each request will be terminated if it takes too long. std::chrono::milliseconds DeadlineWaitingTime; }; } // namespace std::unique_ptr getClient(llvm::StringRef Address, llvm::StringRef ProjectRoot) { const auto Channel = grpc::CreateChannel(Address.str(), grpc::InsecureChannelCredentials()); return std::unique_ptr( new IndexClient(Channel, Address, ProjectRoot)); } } // namespace remote } // namespace clangd } // namespace clang