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-exception
6 //
7 //===----------------------------------------------------------------------===//
8
9 #include <grpc++/grpc++.h>
10
11 #include "Client.h"
12 #include "Service.grpc.pb.h"
13 #include "index/Index.h"
14 #include "marshalling/Marshalling.h"
15 #include "support/Logger.h"
16 #include "support/Trace.h"
17 #include "clang/Basic/Version.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
26 namespace clang {
27 namespace clangd {
28 namespace remote {
29 namespace {
30
toString(const grpc_connectivity_state & State)31 llvm::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
47 class IndexClient : public clangd::SymbolIndex {
updateConnectionStatus() const48 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>
streamRPC(ClangdRequestT Request,StreamingCall<RequestT,ReplyT> RPCCall,CallbackT Callback) const63 bool streamRPC(ClangdRequestT Request,
64 StreamingCall<RequestT, ReplyT> RPCCall,
65 CallbackT Callback) const {
66 updateConnectionStatus();
67 bool FinalResult = false;
68 trace::Span Tracer(RequestT::descriptor()->name());
69 const auto RPCRequest = ProtobufMarshaller->toProtobuf(Request);
70 SPAN_ATTACH(Tracer, "Request", RPCRequest.DebugString());
71 grpc::ClientContext Context;
72 Context.AddMetadata("version", clang::getClangToolFullVersion("clangd"));
73 std::chrono::system_clock::time_point StartTime =
74 std::chrono::system_clock::now();
75 auto Deadline = StartTime + DeadlineWaitingTime;
76 Context.set_deadline(Deadline);
77 auto Reader = (Stub.get()->*RPCCall)(&Context, RPCRequest);
78 dlog("Sending {0}: {1}", RequestT::descriptor()->name(),
79 RPCRequest.DebugString());
80 ReplyT Reply;
81 unsigned Successful = 0;
82 unsigned FailedToParse = 0;
83 while (Reader->Read(&Reply)) {
84 if (!Reply.has_stream_result()) {
85 FinalResult = Reply.final_result().has_more();
86 continue;
87 }
88 auto Response = ProtobufMarshaller->fromProtobuf(Reply.stream_result());
89 if (!Response) {
90 elog("Received invalid {0}: {1}. Reason: {2}",
91 ReplyT::descriptor()->name(), Reply.stream_result().DebugString(),
92 Response.takeError());
93 ++FailedToParse;
94 continue;
95 }
96 Callback(*Response);
97 ++Successful;
98 }
99 auto Millis = std::chrono::duration_cast<std::chrono::milliseconds>(
100 std::chrono::system_clock::now() - StartTime)
101 .count();
102 vlog("Remote index [{0}]: {1} => {2} results in {3}ms.", ServerAddress,
103 RequestT::descriptor()->name(), Successful, Millis);
104 SPAN_ATTACH(Tracer, "Status", Reader->Finish().ok());
105 SPAN_ATTACH(Tracer, "Successful", Successful);
106 SPAN_ATTACH(Tracer, "Failed to parse", FailedToParse);
107 updateConnectionStatus();
108 return FinalResult;
109 }
110
111 public:
IndexClient(std::shared_ptr<grpc::Channel> Channel,llvm::StringRef Address,llvm::StringRef ProjectRoot,std::chrono::milliseconds DeadlineTime=std::chrono::milliseconds (1000))112 IndexClient(
113 std::shared_ptr<grpc::Channel> Channel, llvm::StringRef Address,
114 llvm::StringRef ProjectRoot,
115 std::chrono::milliseconds DeadlineTime = std::chrono::milliseconds(1000))
116 : Stub(remote::v1::SymbolIndex::NewStub(Channel)), Channel(Channel),
117 ServerAddress(Address),
118 ConnectionStatus(Channel->GetState(/*try_to_connect=*/true)),
119 ProtobufMarshaller(new Marshaller(/*RemoteIndexRoot=*/"",
120 /*LocalIndexRoot=*/ProjectRoot)),
121 DeadlineWaitingTime(DeadlineTime) {
122 assert(!ProjectRoot.empty());
123 }
124
lookup(const clangd::LookupRequest & Request,llvm::function_ref<void (const clangd::Symbol &)> Callback) const125 void lookup(const clangd::LookupRequest &Request,
126 llvm::function_ref<void(const clangd::Symbol &)> Callback)
127 const override {
128 streamRPC(Request, &remote::v1::SymbolIndex::Stub::Lookup, Callback);
129 }
130
fuzzyFind(const clangd::FuzzyFindRequest & Request,llvm::function_ref<void (const clangd::Symbol &)> Callback) const131 bool fuzzyFind(const clangd::FuzzyFindRequest &Request,
132 llvm::function_ref<void(const clangd::Symbol &)> Callback)
133 const override {
134 return streamRPC(Request, &remote::v1::SymbolIndex::Stub::FuzzyFind,
135 Callback);
136 }
137
138 bool
refs(const clangd::RefsRequest & Request,llvm::function_ref<void (const clangd::Ref &)> Callback) const139 refs(const clangd::RefsRequest &Request,
140 llvm::function_ref<void(const clangd::Ref &)> Callback) const override {
141 return streamRPC(Request, &remote::v1::SymbolIndex::Stub::Refs, Callback);
142 }
143
144 void
relations(const clangd::RelationsRequest & Request,llvm::function_ref<void (const SymbolID &,const clangd::Symbol &)> Callback) const145 relations(const clangd::RelationsRequest &Request,
146 llvm::function_ref<void(const SymbolID &, const clangd::Symbol &)>
147 Callback) const override {
148 streamRPC(Request, &remote::v1::SymbolIndex::Stub::Relations,
149 // Unpack protobuf Relation.
150 [&](std::pair<SymbolID, clangd::Symbol> SubjectAndObject) {
151 Callback(SubjectAndObject.first, SubjectAndObject.second);
152 });
153 }
154
155 llvm::unique_function<bool(llvm::StringRef) const>
indexedFiles() const156 indexedFiles() const override {
157 // FIXME: For now we always return "false" regardless of whether the file
158 // was indexed or not. A possible implementation could be based on
159 // the idea that we do not want to send a request at every
160 // call of a function returned by IndexClient::indexedFiles().
161 return [](llvm::StringRef) { return false; };
162 }
163
164 // IndexClient does not take any space since the data is stored on the
165 // server.
estimateMemoryUsage() const166 size_t estimateMemoryUsage() const override { return 0; }
167
168 private:
169 std::unique_ptr<remote::v1::SymbolIndex::Stub> Stub;
170 std::shared_ptr<grpc::Channel> Channel;
171 llvm::SmallString<256> ServerAddress;
172 mutable std::atomic<grpc_connectivity_state> ConnectionStatus;
173 std::unique_ptr<Marshaller> ProtobufMarshaller;
174 // Each request will be terminated if it takes too long.
175 std::chrono::milliseconds DeadlineWaitingTime;
176 };
177
178 } // namespace
179
getClient(llvm::StringRef Address,llvm::StringRef ProjectRoot)180 std::unique_ptr<clangd::SymbolIndex> getClient(llvm::StringRef Address,
181 llvm::StringRef ProjectRoot) {
182 const auto Channel =
183 grpc::CreateChannel(Address.str(), grpc::InsecureChannelCredentials());
184 return std::unique_ptr<clangd::SymbolIndex>(
185 new IndexClient(Channel, Address, ProjectRoot));
186 }
187
188 } // namespace remote
189 } // namespace clangd
190 } // namespace clang
191