Coverage Report

Created: 2026-08-04 01:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/util/brpc_client_cache.h
Line
Count
Source
1
// Licensed to the Apache Software Foundation (ASF) under one
2
// or more contributor license agreements.  See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership.  The ASF licenses this file
5
// to you under the Apache License, Version 2.0 (the
6
// "License"); you may not use this file except in compliance
7
// with the License.  You may obtain a copy of the License at
8
//
9
//   http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing,
12
// software distributed under the License is distributed on an
13
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
// KIND, either express or implied.  See the License for the
15
// specific language governing permissions and limitations
16
// under the License.
17
18
#pragma once
19
20
#include <brpc/adaptive_connection_type.h>
21
#include <brpc/adaptive_protocol_type.h>
22
#include <brpc/channel.h>
23
#include <brpc/controller.h>
24
#include <butil/endpoint.h>
25
#include <fmt/format.h>
26
#include <gen_cpp/Types_types.h>
27
#include <gen_cpp/types.pb.h>
28
#include <glog/logging.h>
29
#include <google/protobuf/service.h>
30
#include <parallel_hashmap/phmap.h>
31
#include <stddef.h>
32
33
#include <functional>
34
#include <memory>
35
#include <mutex>
36
#include <ostream>
37
#include <string>
38
#include <utility>
39
#include <vector>
40
41
#include "common/compiler_util.h" // IWYU pragma: keep
42
#include "common/config.h"
43
#include "common/status.h"
44
#include "runtime/exec_env.h"
45
#include "service/backend_options.h"
46
#include "util/client_connection_provider.h"
47
#include "util/dns_cache.h"
48
#include "util/network_util.h"
49
50
namespace doris {
51
class PBackendService_Stub;
52
class PFunctionService_Stub;
53
} // namespace doris
54
55
// Entry that holds both resolved IP and stub, similar to Java's BackendServiceClientExtIp
56
template <typename T>
57
struct StubEntry {
58
    std::string real_ip;
59
    std::shared_ptr<T> stub;
60
};
61
62
template <typename T>
63
using StubMap = phmap::parallel_flat_hash_map<
64
        std::string, StubEntry<T>, std::hash<std::string>, std::equal_to<std::string>,
65
        std::allocator<std::pair<const std::string, StubEntry<T>>>, 8, std::mutex>;
66
67
namespace doris {
68
class FailureDetectClosure : public ::google::protobuf::Closure {
69
public:
70
    FailureDetectClosure(std::shared_ptr<AtomicStatus>& channel_st,
71
                         ::google::protobuf::RpcController* controller,
72
                         ::google::protobuf::Closure* done)
73
1
            : _channel_st(channel_st), _controller(controller), _done(done) {}
74
75
1
    void Run() override {
76
1
        Defer defer {[&]() { delete this; }};
77
        // All brpc related API will use brpc::Controller, so that it is safe
78
        // to do static cast here.
79
1
        auto* cntl = static_cast<brpc::Controller*>(_controller);
80
1
        if (cntl->Failed() && cntl->ErrorCode() == EHOSTDOWN) {
81
1
            Status error_st = Status::NetworkError(
82
1
                    "Failed to send brpc, error={}, error_text={}, client: {}, latency = {}",
83
1
                    berror(cntl->ErrorCode()), cntl->ErrorText(), BackendOptions::get_localhost(),
84
1
                    cntl->latency_us());
85
1
            LOG(WARNING) << error_st;
86
1
            _channel_st->update(error_st);
87
1
        }
88
        // Sometimes done == nullptr, for example hand_shake API.
89
1
        if (_done != nullptr) {
90
1
            _done->Run();
91
1
        }
92
        // _done->Run may throw exception, so that move delete this to Defer.
93
        // delete this;
94
1
    }
95
96
private:
97
    std::shared_ptr<AtomicStatus> _channel_st;
98
    ::google::protobuf::RpcController* _controller;
99
    ::google::protobuf::Closure* _done;
100
};
101
102
// This channel will use FailureDetectClosure to wrap the original closure
103
// If some non-recoverable rpc failure happens, it will save the error status in
104
// _channel_st.
105
// And brpc client cache will depend on it to detect if the client is health.
106
class FailureDetectChannel : public ::brpc::Channel {
107
public:
108
45
    FailureDetectChannel() : ::brpc::Channel() {
109
45
        _channel_st = std::make_shared<AtomicStatus>(); // default OK
110
45
    }
111
    void CallMethod(const google::protobuf::MethodDescriptor* method,
112
                    google::protobuf::RpcController* controller,
113
                    const google::protobuf::Message* request, google::protobuf::Message* response,
114
41
                    google::protobuf::Closure* done) override {
115
41
        FailureDetectClosure* failure_detect_closure = nullptr;
116
41
        if (done != nullptr) {
117
            // If done == nullptr, then it means the call is sync call, so that should not
118
            // gen a failure detect closure for it. Or it will core.
119
1
            failure_detect_closure = new FailureDetectClosure(_channel_st, controller, done);
120
1
        }
121
41
        ::brpc::Channel::CallMethod(method, controller, request, response, failure_detect_closure);
122
        // Done == nullptr, it is a sync call, should also deal with the bad channel.
123
41
        if (done == nullptr) {
124
40
            auto* cntl = static_cast<brpc::Controller*>(controller);
125
40
            if (cntl->Failed() && cntl->ErrorCode() == EHOSTDOWN) {
126
2
                Status error_st = Status::NetworkError(
127
2
                        "Failed to send brpc, error={}, error_text={}, client: {}, latency = {}",
128
2
                        berror(cntl->ErrorCode()), cntl->ErrorText(),
129
2
                        BackendOptions::get_localhost(), cntl->latency_us());
130
2
                LOG(WARNING) << error_st;
131
2
                _channel_st->update(error_st);
132
2
            }
133
40
        }
134
41
    }
135
136
11
    std::shared_ptr<AtomicStatus> channel_status() { return _channel_st; }
137
138
private:
139
    std::shared_ptr<AtomicStatus> _channel_st;
140
};
141
142
template <class T>
143
class BrpcClientCache {
144
public:
145
    BrpcClientCache(std::string protocol = "baidu_std", std::string connection_type = "",
146
                    std::string connection_group = "");
147
    virtual ~BrpcClientCache();
148
149
    std::shared_ptr<T> get_client(const butil::EndPoint& endpoint) {
150
        return get_client(butil::endpoint2str(endpoint).c_str());
151
    }
152
153
#ifdef BE_TEST
154
8
    virtual std::shared_ptr<T> get_client(const TNetworkAddress& taddr) {
155
8
        std::string host_port = fmt::format("{}:{}", taddr.hostname, taddr.port);
156
8
        return get_client(host_port);
157
8
    }
_ZN5doris15BrpcClientCacheINS_20PBackendService_StubEE10get_clientERKNS_15TNetworkAddressE
Line
Count
Source
154
8
    virtual std::shared_ptr<T> get_client(const TNetworkAddress& taddr) {
155
8
        std::string host_port = fmt::format("{}:{}", taddr.hostname, taddr.port);
156
8
        return get_client(host_port);
157
8
    }
Unexecuted instantiation: _ZN5doris15BrpcClientCacheINS_21PFunctionService_StubEE10get_clientERKNS_15TNetworkAddressE
158
#else
159
    std::shared_ptr<T> get_client(const TNetworkAddress& taddr) {
160
        return get_client(taddr.hostname, taddr.port);
161
    }
162
#endif
163
164
0
    std::shared_ptr<T> get_client(const PNetworkAddress& paddr) {
165
0
        return get_client(paddr.hostname(), paddr.port());
166
0
    }
167
168
8
    std::shared_ptr<T> get_client(const std::string& host, int port) {
169
8
        std::string realhost = host;
170
8
        auto dns_cache = ExecEnv::GetInstance()->dns_cache();
171
8
        if (dns_cache == nullptr) {
172
8
            LOG(WARNING) << "DNS cache is not initialized, skipping hostname resolve";
173
8
        } else if (!is_valid_ip(host)) {
174
0
            Status status = dns_cache->get(host, &realhost);
175
0
            if (!status.ok()) {
176
0
                LOG(WARNING) << "failed to get ip from host:" << status.to_string();
177
                // The hostname is no longer resolvable, which normally means the backend
178
                // was dropped from the cluster. Returning early is not enough: any stub
179
                // cached under this host:port still holds a brpc Channel bound to the last
180
                // resolved (now dead) IP, and brpc keeps health-checking that socket
181
                // forever, which is the source of the endless
182
                // "Fail to wait EPOLLOUT ... Connection timed out" warnings. Drop it here
183
                // so the socket is closed along with the last reference to the stub.
184
0
                _stub_map.erase(fmt::format("{}:{}", host, port));
185
0
                return nullptr;
186
0
            }
187
0
        }
188
189
        // Use original host:port as key (like Java's TNetworkAddress address)
190
        // This allows us to detect IP changes when DNS resolution changes
191
8
        std::string host_port = fmt::format("{}:{}", host, port);
192
193
8
        std::shared_ptr<T> stub_ptr;
194
8
        bool need_remove = false;
195
196
8
        auto check_entry = [&](const auto& v) {
197
4
            const StubEntry<T>& entry = v.second;
198
            // Check if cached IP matches current resolved IP
199
4
            if (entry.real_ip != realhost) {
200
                // IP changed (DNS resolution changed)
201
0
                LOG(WARNING) << "Cached ip changed for " << host << ", before ip: " << entry.real_ip
202
0
                             << ", current ip: " << realhost;
203
0
                need_remove = true;
204
4
            } else if (!static_cast<FailureDetectChannel*>(entry.stub->channel())
205
4
                                ->channel_status()
206
4
                                ->ok()) {
207
                // Client is not in normal state, need to recreate
208
                // At this point we cannot judge the progress of reconnecting the underlying channel.
209
                // In the worst case, it may take two minutes. But we can't stand the connection refused
210
                // for two minutes, so rebuild the channel directly.
211
2
                need_remove = true;
212
2
            } else {
213
                // Cache hit: IP matches and client is healthy
214
2
                stub_ptr = entry.stub;
215
2
            }
216
4
        };
_ZZN5doris15BrpcClientCacheINS_20PBackendService_StubEE10get_clientERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEiENKUlRKT_E_clISt4pairIS9_9StubEntryIS1_EEEEDaSD_
Line
Count
Source
196
4
        auto check_entry = [&](const auto& v) {
197
4
            const StubEntry<T>& entry = v.second;
198
            // Check if cached IP matches current resolved IP
199
4
            if (entry.real_ip != realhost) {
200
                // IP changed (DNS resolution changed)
201
0
                LOG(WARNING) << "Cached ip changed for " << host << ", before ip: " << entry.real_ip
202
0
                             << ", current ip: " << realhost;
203
0
                need_remove = true;
204
4
            } else if (!static_cast<FailureDetectChannel*>(entry.stub->channel())
205
4
                                ->channel_status()
206
4
                                ->ok()) {
207
                // Client is not in normal state, need to recreate
208
                // At this point we cannot judge the progress of reconnecting the underlying channel.
209
                // In the worst case, it may take two minutes. But we can't stand the connection refused
210
                // for two minutes, so rebuild the channel directly.
211
2
                need_remove = true;
212
2
            } else {
213
                // Cache hit: IP matches and client is healthy
214
2
                stub_ptr = entry.stub;
215
2
            }
216
4
        };
Unexecuted instantiation: _ZZN5doris15BrpcClientCacheINS_21PFunctionService_StubEE10get_clientERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEiENKUlRKT_E_clISt4pairIS9_9StubEntryIS1_EEEEDaSD_
217
218
8
        if (LIKELY(_stub_map.if_contains(host_port, check_entry))) {
219
4
            if (stub_ptr != nullptr) {
220
2
                return stub_ptr;
221
2
            }
222
            // IP changed or client unhealthy, need to remove old entry
223
2
            if (need_remove) {
224
2
                _stub_map.erase(host_port);
225
2
            }
226
2
        }
227
228
        // Create new stub using resolved IP for actual connection
229
6
        std::string real_host_port = get_host_port(realhost, port);
230
6
        auto stub = get_new_client_no_cache(real_host_port);
231
6
        if (stub != nullptr) {
232
5
            StubEntry<T> entry {realhost, stub};
233
5
            _stub_map.try_emplace_l(
234
5
                    host_port, [&stub](const auto& v) { stub = v.second.stub; }, entry);
Unexecuted instantiation: _ZZN5doris15BrpcClientCacheINS_20PBackendService_StubEE10get_clientERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEiENKUlRKT_E0_clISt4pairIS9_9StubEntryIS1_EEEEDaSD_
Unexecuted instantiation: _ZZN5doris15BrpcClientCacheINS_21PFunctionService_StubEE10get_clientERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEiENKUlRKT_E0_clISt4pairIS9_9StubEntryIS1_EEEEDaSD_
235
5
        }
236
6
        return stub;
237
8
    }
_ZN5doris15BrpcClientCacheINS_20PBackendService_StubEE10get_clientERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEi
Line
Count
Source
168
8
    std::shared_ptr<T> get_client(const std::string& host, int port) {
169
8
        std::string realhost = host;
170
8
        auto dns_cache = ExecEnv::GetInstance()->dns_cache();
171
8
        if (dns_cache == nullptr) {
172
8
            LOG(WARNING) << "DNS cache is not initialized, skipping hostname resolve";
173
8
        } else if (!is_valid_ip(host)) {
174
0
            Status status = dns_cache->get(host, &realhost);
175
0
            if (!status.ok()) {
176
0
                LOG(WARNING) << "failed to get ip from host:" << status.to_string();
177
                // The hostname is no longer resolvable, which normally means the backend
178
                // was dropped from the cluster. Returning early is not enough: any stub
179
                // cached under this host:port still holds a brpc Channel bound to the last
180
                // resolved (now dead) IP, and brpc keeps health-checking that socket
181
                // forever, which is the source of the endless
182
                // "Fail to wait EPOLLOUT ... Connection timed out" warnings. Drop it here
183
                // so the socket is closed along with the last reference to the stub.
184
0
                _stub_map.erase(fmt::format("{}:{}", host, port));
185
0
                return nullptr;
186
0
            }
187
0
        }
188
189
        // Use original host:port as key (like Java's TNetworkAddress address)
190
        // This allows us to detect IP changes when DNS resolution changes
191
8
        std::string host_port = fmt::format("{}:{}", host, port);
192
193
8
        std::shared_ptr<T> stub_ptr;
194
8
        bool need_remove = false;
195
196
8
        auto check_entry = [&](const auto& v) {
197
8
            const StubEntry<T>& entry = v.second;
198
            // Check if cached IP matches current resolved IP
199
8
            if (entry.real_ip != realhost) {
200
                // IP changed (DNS resolution changed)
201
8
                LOG(WARNING) << "Cached ip changed for " << host << ", before ip: " << entry.real_ip
202
8
                             << ", current ip: " << realhost;
203
8
                need_remove = true;
204
8
            } else if (!static_cast<FailureDetectChannel*>(entry.stub->channel())
205
8
                                ->channel_status()
206
8
                                ->ok()) {
207
                // Client is not in normal state, need to recreate
208
                // At this point we cannot judge the progress of reconnecting the underlying channel.
209
                // In the worst case, it may take two minutes. But we can't stand the connection refused
210
                // for two minutes, so rebuild the channel directly.
211
8
                need_remove = true;
212
8
            } else {
213
                // Cache hit: IP matches and client is healthy
214
8
                stub_ptr = entry.stub;
215
8
            }
216
8
        };
217
218
8
        if (LIKELY(_stub_map.if_contains(host_port, check_entry))) {
219
4
            if (stub_ptr != nullptr) {
220
2
                return stub_ptr;
221
2
            }
222
            // IP changed or client unhealthy, need to remove old entry
223
2
            if (need_remove) {
224
2
                _stub_map.erase(host_port);
225
2
            }
226
2
        }
227
228
        // Create new stub using resolved IP for actual connection
229
6
        std::string real_host_port = get_host_port(realhost, port);
230
6
        auto stub = get_new_client_no_cache(real_host_port);
231
6
        if (stub != nullptr) {
232
5
            StubEntry<T> entry {realhost, stub};
233
5
            _stub_map.try_emplace_l(
234
5
                    host_port, [&stub](const auto& v) { stub = v.second.stub; }, entry);
235
5
        }
236
6
        return stub;
237
8
    }
Unexecuted instantiation: _ZN5doris15BrpcClientCacheINS_21PFunctionService_StubEE10get_clientERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEi
238
239
8
    std::shared_ptr<T> get_client(const std::string& host_port) {
240
8
        const auto pos = host_port.rfind(':');
241
8
        std::string host = host_port.substr(0, pos);
242
8
        int port = 0;
243
8
        try {
244
8
            port = stoi(host_port.substr(pos + 1));
245
8
        } catch (const std::exception& err) {
246
0
            LOG(WARNING) << "failed to parse port from " << host_port << ": " << err.what();
247
0
            return nullptr;
248
0
        }
249
8
        return get_client(host, port);
250
8
    }
_ZN5doris15BrpcClientCacheINS_20PBackendService_StubEE10get_clientERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
Line
Count
Source
239
8
    std::shared_ptr<T> get_client(const std::string& host_port) {
240
8
        const auto pos = host_port.rfind(':');
241
8
        std::string host = host_port.substr(0, pos);
242
8
        int port = 0;
243
8
        try {
244
8
            port = stoi(host_port.substr(pos + 1));
245
8
        } catch (const std::exception& err) {
246
0
            LOG(WARNING) << "failed to parse port from " << host_port << ": " << err.what();
247
0
            return nullptr;
248
0
        }
249
8
        return get_client(host, port);
250
8
    }
Unexecuted instantiation: _ZN5doris15BrpcClientCacheINS_21PFunctionService_StubEE10get_clientERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
251
252
    std::shared_ptr<T> get_new_client_no_cache(const std::string& host_port,
253
                                               const std::string& protocol = "",
254
                                               const std::string& connection_type = "",
255
45
                                               const std::string& connection_group = "") {
256
45
        brpc::ChannelOptions options;
257
45
        Status status = doris::client::configure_brpc_channel_options(&options);
258
45
        if (!status.ok()) {
259
0
            throw status;
260
0
        }
261
45
        if (protocol != "") {
262
0
            options.protocol = protocol;
263
45
        } else if (_protocol != "") {
264
45
            options.protocol = _protocol;
265
45
        }
266
45
        if (connection_type != "") {
267
0
            options.connection_type = connection_type;
268
45
        } else if (_connection_type != "") {
269
0
            options.connection_type = _connection_type;
270
0
        }
271
45
        if (connection_group != "") {
272
0
            options.connection_group = connection_group;
273
45
        } else if (_connection_group != "") {
274
0
            options.connection_group = _connection_group;
275
0
        }
276
        // Add random connection id to connection_group to make sure use new socket
277
45
        options.connection_group += std::to_string(_connection_id.fetch_add(1));
278
45
        options.connect_timeout_ms = 2000;
279
45
        options.timeout_ms = 2000;
280
45
        options.max_retry = 10;
281
282
45
        std::unique_ptr<FailureDetectChannel> channel(new FailureDetectChannel());
283
45
        int ret_code = 0;
284
45
        if (host_port.find("://") == std::string::npos) {
285
45
            ret_code = channel->Init(host_port.c_str(), &options);
286
45
        } else {
287
0
            ret_code =
288
0
                    channel->Init(host_port.c_str(), config::rpc_load_balancer.c_str(), &options);
289
0
        }
290
45
        if (ret_code) {
291
1
            LOG(WARNING) << "Failed to initialize brpc Channel to " << host_port;
292
1
            return nullptr;
293
1
        }
294
44
        return std::make_shared<T>(channel.release(), google::protobuf::Service::STUB_OWNS_CHANNEL);
295
45
    }
_ZN5doris15BrpcClientCacheINS_20PBackendService_StubEE23get_new_client_no_cacheERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESA_SA_SA_
Line
Count
Source
255
45
                                               const std::string& connection_group = "") {
256
45
        brpc::ChannelOptions options;
257
45
        Status status = doris::client::configure_brpc_channel_options(&options);
258
45
        if (!status.ok()) {
259
0
            throw status;
260
0
        }
261
45
        if (protocol != "") {
262
0
            options.protocol = protocol;
263
45
        } else if (_protocol != "") {
264
45
            options.protocol = _protocol;
265
45
        }
266
45
        if (connection_type != "") {
267
0
            options.connection_type = connection_type;
268
45
        } else if (_connection_type != "") {
269
0
            options.connection_type = _connection_type;
270
0
        }
271
45
        if (connection_group != "") {
272
0
            options.connection_group = connection_group;
273
45
        } else if (_connection_group != "") {
274
0
            options.connection_group = _connection_group;
275
0
        }
276
        // Add random connection id to connection_group to make sure use new socket
277
45
        options.connection_group += std::to_string(_connection_id.fetch_add(1));
278
45
        options.connect_timeout_ms = 2000;
279
45
        options.timeout_ms = 2000;
280
45
        options.max_retry = 10;
281
282
45
        std::unique_ptr<FailureDetectChannel> channel(new FailureDetectChannel());
283
45
        int ret_code = 0;
284
45
        if (host_port.find("://") == std::string::npos) {
285
45
            ret_code = channel->Init(host_port.c_str(), &options);
286
45
        } else {
287
0
            ret_code =
288
0
                    channel->Init(host_port.c_str(), config::rpc_load_balancer.c_str(), &options);
289
0
        }
290
45
        if (ret_code) {
291
1
            LOG(WARNING) << "Failed to initialize brpc Channel to " << host_port;
292
1
            return nullptr;
293
1
        }
294
44
        return std::make_shared<T>(channel.release(), google::protobuf::Service::STUB_OWNS_CHANNEL);
295
45
    }
Unexecuted instantiation: _ZN5doris15BrpcClientCacheINS_21PFunctionService_StubEE23get_new_client_no_cacheERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESA_SA_SA_
296
297
1
    size_t size() { return _stub_map.size(); }
298
299
0
    void clear() { _stub_map.clear(); }
300
301
0
    size_t erase(const std::string& host_port) { return _stub_map.erase(host_port); }
302
303
0
    size_t erase(const std::string& host, int port) {
304
0
        std::string host_port = fmt::format("{}:{}", host, port);
305
0
        return erase(host_port);
306
0
    }
307
308
0
    size_t erase(const butil::EndPoint& endpoint) {
309
0
        return _stub_map.erase(butil::endpoint2str(endpoint).c_str());
310
0
    }
311
312
0
    bool exist(const std::string& host_port) {
313
0
        return _stub_map.find(host_port) != _stub_map.end();
314
0
    }
315
316
0
    void get_all(std::vector<std::string>* endpoints) {
317
0
        for (auto it = _stub_map.begin(); it != _stub_map.end(); ++it) {
318
0
            endpoints->emplace_back(it->first.c_str());
319
0
        }
320
0
    }
321
322
    bool available(std::shared_ptr<T> stub, const butil::EndPoint& endpoint) {
323
        return available(stub, butil::endpoint2str(endpoint).c_str());
324
    }
325
326
1
    bool available(std::shared_ptr<T> stub, const std::string& host_port) {
327
1
        if (!stub) {
328
0
            LOG(WARNING) << "stub is null to: " << host_port;
329
0
            return false;
330
0
        }
331
1
        std::string message = "hello doris!";
332
1
        PHandShakeRequest request;
333
1
        request.set_hello(message);
334
1
        PHandShakeResponse response;
335
1
        brpc::Controller cntl;
336
1
        stub->hand_shake(&cntl, &request, &response, nullptr);
337
1
        if (cntl.Failed()) {
338
1
            LOG(WARNING) << "open brpc connection to " << host_port
339
1
                         << " failed: " << cntl.ErrorText();
340
1
            return false;
341
1
        } else if (response.has_status() && response.has_hello() && response.hello() == message &&
342
0
                   response.status().status_code() == 0) {
343
0
            return true;
344
0
        } else {
345
0
            LOG(WARNING) << "open brpc connection to " << host_port
346
0
                         << " failed: " << response.DebugString();
347
0
            return false;
348
0
        }
349
1
    }
350
351
1
    bool available(std::shared_ptr<T> stub, const std::string& host, int port) {
352
1
        std::string host_port = fmt::format("{}:{}", host, port);
353
1
        return available(stub, host_port);
354
1
    }
355
356
private:
357
    StubMap<T> _stub_map;
358
    const std::string _protocol;
359
    const std::string _connection_type;
360
    const std::string _connection_group;
361
    // use to generate unique connection id for each connection
362
    // to prevent the connection problem of brpc: https://github.com/apache/brpc/issues/2146
363
    std::atomic<int64_t> _connection_id {0};
364
};
365
366
using InternalServiceClientCache = BrpcClientCache<PBackendService_Stub>;
367
using FunctionServiceClientCache = BrpcClientCache<PFunctionService_Stub>;
368
} // namespace doris