round_robin.cc
Go to the documentation of this file.
1 //
2 // Copyright 2015 gRPC authors.
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 // http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 //
16 
18 
19 #include <inttypes.h>
20 #include <stdlib.h>
21 
22 #include <algorithm>
23 #include <memory>
24 #include <string>
25 #include <utility>
26 #include <vector>
27 
28 #include "absl/memory/memory.h"
29 #include "absl/status/status.h"
30 #include "absl/status/statusor.h"
31 #include "absl/strings/str_cat.h"
32 #include "absl/types/optional.h"
33 
36 #include <grpc/support/log.h>
37 
48 #include "src/core/lib/json/json.h"
51 
52 namespace grpc_core {
53 
54 TraceFlag grpc_lb_round_robin_trace(false, "round_robin");
55 
56 namespace {
57 
58 //
59 // round_robin LB policy
60 //
61 
62 constexpr char kRoundRobin[] = "round_robin";
63 
64 class RoundRobin : public LoadBalancingPolicy {
65  public:
66  explicit RoundRobin(Args args);
67 
68  const char* name() const override { return kRoundRobin; }
69 
70  void UpdateLocked(UpdateArgs args) override;
71  void ResetBackoffLocked() override;
72 
73  private:
74  ~RoundRobin() override;
75 
76  // Forward declaration.
77  class RoundRobinSubchannelList;
78 
79  // Data for a particular subchannel in a subchannel list.
80  // This subclass adds the following functionality:
81  // - Tracks the previous connectivity state of the subchannel, so that
82  // we know how many subchannels are in each state.
83  class RoundRobinSubchannelData
84  : public SubchannelData<RoundRobinSubchannelList,
85  RoundRobinSubchannelData> {
86  public:
87  RoundRobinSubchannelData(
88  SubchannelList<RoundRobinSubchannelList, RoundRobinSubchannelData>*
89  subchannel_list,
90  const ServerAddress& address,
91  RefCountedPtr<SubchannelInterface> subchannel)
92  : SubchannelData(subchannel_list, address, std::move(subchannel)) {}
93 
94  absl::optional<grpc_connectivity_state> connectivity_state() const {
96  }
97 
98  private:
99  // Performs connectivity state updates that need to be done only
100  // after we have started watching.
101  void ProcessConnectivityChangeLocked(
103  grpc_connectivity_state new_state) override;
104 
105  // Updates the logical connectivity state.
106  void UpdateLogicalConnectivityStateLocked(
107  grpc_connectivity_state connectivity_state);
108 
109  // The logical connectivity state of the subchannel.
110  // Note that the logical connectivity state may differ from the
111  // actual reported state in some cases (e.g., after we see
112  // TRANSIENT_FAILURE, we ignore any subsequent state changes until
113  // we see READY).
115  };
116 
117  // A list of subchannels.
118  class RoundRobinSubchannelList
119  : public SubchannelList<RoundRobinSubchannelList,
120  RoundRobinSubchannelData> {
121  public:
122  RoundRobinSubchannelList(RoundRobin* policy, ServerAddressList addresses,
123  const grpc_channel_args& args)
124  : SubchannelList(policy,
126  ? "RoundRobinSubchannelList"
127  : nullptr),
128  std::move(addresses), policy->channel_control_helper(),
129  args) {
130  // Need to maintain a ref to the LB policy as long as we maintain
131  // any references to subchannels, since the subchannels'
132  // pollset_sets will include the LB policy's pollset_set.
133  policy->Ref(DEBUG_LOCATION, "subchannel_list").release();
134  }
135 
136  ~RoundRobinSubchannelList() override {
137  RoundRobin* p = static_cast<RoundRobin*>(policy());
138  p->Unref(DEBUG_LOCATION, "subchannel_list");
139  }
140 
141  // Updates the counters of subchannels in each state when a
142  // subchannel transitions from old_state to new_state.
143  void UpdateStateCountersLocked(
145  grpc_connectivity_state new_state);
146 
147  // Ensures that the right subchannel list is used and then updates
148  // the RR policy's connectivity state based on the subchannel list's
149  // state counters.
150  void MaybeUpdateRoundRobinConnectivityStateLocked(
151  absl::Status status_for_tf);
152 
153  private:
154  std::string CountersString() const {
155  return absl::StrCat("num_subchannels=", num_subchannels(),
156  " num_ready=", num_ready_,
157  " num_connecting=", num_connecting_,
158  " num_transient_failure=", num_transient_failure_);
159  }
160 
161  size_t num_ready_ = 0;
162  size_t num_connecting_ = 0;
164 
166  };
167 
168  class Picker : public SubchannelPicker {
169  public:
170  Picker(RoundRobin* parent, RoundRobinSubchannelList* subchannel_list);
171 
172  PickResult Pick(PickArgs args) override;
173 
174  private:
175  // Using pointer value only, no ref held -- do not dereference!
176  RoundRobin* parent_;
177 
179  std::vector<RefCountedPtr<SubchannelInterface>> subchannels_;
180  };
181 
182  void ShutdownLocked() override;
183 
184  // List of subchannels.
185  OrphanablePtr<RoundRobinSubchannelList> subchannel_list_;
186  // Latest pending subchannel list.
187  // When we get an updated address list, we create a new subchannel list
188  // for it here, and we wait to swap it into subchannel_list_ until the new
189  // list becomes READY.
190  OrphanablePtr<RoundRobinSubchannelList> latest_pending_subchannel_list_;
191 
192  bool shutdown_ = false;
193 };
194 
195 //
196 // RoundRobin::Picker
197 //
198 
199 RoundRobin::Picker::Picker(RoundRobin* parent,
200  RoundRobinSubchannelList* subchannel_list)
201  : parent_(parent) {
202  for (size_t i = 0; i < subchannel_list->num_subchannels(); ++i) {
203  RoundRobinSubchannelData* sd = subchannel_list->subchannel(i);
204  if (sd->connectivity_state().value_or(GRPC_CHANNEL_IDLE) ==
206  subchannels_.push_back(sd->subchannel()->Ref());
207  }
208  }
209  // For discussion on why we generate a random starting index for
210  // the picker, see https://github.com/grpc/grpc-go/issues/2580.
211  // TODO(roth): rand(3) is not thread-safe. This should be replaced with
212  // something better as part of https://github.com/grpc/grpc/issues/17891.
213  last_picked_index_ = rand() % subchannels_.size();
216  "[RR %p picker %p] created picker from subchannel_list=%p "
217  "with %" PRIuPTR " READY subchannels; last_picked_index_=%" PRIuPTR,
218  parent_, this, subchannel_list, subchannels_.size(),
220  }
221 }
222 
223 RoundRobin::PickResult RoundRobin::Picker::Pick(PickArgs /*args*/) {
227  "[RR %p picker %p] returning index %" PRIuPTR ", subchannel=%p",
230  }
231  return PickResult::Complete(subchannels_[last_picked_index_]);
232 }
233 
234 //
235 // RoundRobin
236 //
237 
238 RoundRobin::RoundRobin(Args args) : LoadBalancingPolicy(std::move(args)) {
240  gpr_log(GPR_INFO, "[RR %p] Created", this);
241  }
242 }
243 
244 RoundRobin::~RoundRobin() {
246  gpr_log(GPR_INFO, "[RR %p] Destroying Round Robin policy", this);
247  }
248  GPR_ASSERT(subchannel_list_ == nullptr);
250 }
251 
252 void RoundRobin::ShutdownLocked() {
254  gpr_log(GPR_INFO, "[RR %p] Shutting down", this);
255  }
256  shutdown_ = true;
257  subchannel_list_.reset();
259 }
260 
261 void RoundRobin::ResetBackoffLocked() {
262  subchannel_list_->ResetBackoffLocked();
263  if (latest_pending_subchannel_list_ != nullptr) {
264  latest_pending_subchannel_list_->ResetBackoffLocked();
265  }
266 }
267 
268 void RoundRobin::UpdateLocked(UpdateArgs args) {
269  ServerAddressList addresses;
270  if (args.addresses.ok()) {
272  gpr_log(GPR_INFO, "[RR %p] received update with %" PRIuPTR " addresses",
273  this, args.addresses->size());
274  }
275  addresses = std::move(*args.addresses);
276  } else {
278  gpr_log(GPR_INFO, "[RR %p] received update with address error: %s", this,
279  args.addresses.status().ToString().c_str());
280  }
281  // If we already have a subchannel list, then ignore the resolver
282  // failure and keep using the existing list.
283  if (subchannel_list_ != nullptr) return;
284  }
285  // Create new subchannel list, replacing the previous pending list, if any.
287  latest_pending_subchannel_list_ != nullptr) {
288  gpr_log(GPR_INFO, "[RR %p] replacing previous pending subchannel list %p",
289  this, latest_pending_subchannel_list_.get());
290  }
291  latest_pending_subchannel_list_ = MakeOrphanable<RoundRobinSubchannelList>(
292  this, std::move(addresses), *args.args);
293  latest_pending_subchannel_list_->StartWatchingLocked();
294  // If the new list is empty, immediately promote it to
295  // subchannel_list_ and report TRANSIENT_FAILURE.
296  if (latest_pending_subchannel_list_->num_subchannels() == 0) {
298  subchannel_list_ != nullptr) {
299  gpr_log(GPR_INFO, "[RR %p] replacing previous subchannel list %p", this,
300  subchannel_list_.get());
301  }
304  args.addresses.ok() ? absl::UnavailableError(absl::StrCat(
305  "empty address list: ", args.resolution_note))
306  : args.addresses.status();
307  channel_control_helper()->UpdateState(
309  absl::make_unique<TransientFailurePicker>(status));
310  }
311  // Otherwise, if this is the initial update, immediately promote it to
312  // subchannel_list_ and report CONNECTING.
313  else if (subchannel_list_.get() == nullptr) {
315  channel_control_helper()->UpdateState(
317  absl::make_unique<QueuePicker>(Ref(DEBUG_LOCATION, "QueuePicker")));
318  }
319 }
320 
321 //
322 // RoundRobinSubchannelList
323 //
324 
325 void RoundRobin::RoundRobinSubchannelList::UpdateStateCountersLocked(
327  grpc_connectivity_state new_state) {
328  if (old_state.has_value()) {
329  GPR_ASSERT(*old_state != GRPC_CHANNEL_SHUTDOWN);
330  if (*old_state == GRPC_CHANNEL_READY) {
331  GPR_ASSERT(num_ready_ > 0);
332  --num_ready_;
333  } else if (*old_state == GRPC_CHANNEL_CONNECTING) {
335  --num_connecting_;
336  } else if (*old_state == GRPC_CHANNEL_TRANSIENT_FAILURE) {
339  }
340  }
341  GPR_ASSERT(new_state != GRPC_CHANNEL_SHUTDOWN);
342  if (new_state == GRPC_CHANNEL_READY) {
343  ++num_ready_;
344  } else if (new_state == GRPC_CHANNEL_CONNECTING) {
345  ++num_connecting_;
346  } else if (new_state == GRPC_CHANNEL_TRANSIENT_FAILURE) {
348  }
349 }
350 
351 void RoundRobin::RoundRobinSubchannelList::
352  MaybeUpdateRoundRobinConnectivityStateLocked(absl::Status status_for_tf) {
353  RoundRobin* p = static_cast<RoundRobin*>(policy());
354  // If this is latest_pending_subchannel_list_, then swap it into
355  // subchannel_list_ in the following cases:
356  // - subchannel_list_ has no READY subchannels.
357  // - This list has at least one READY subchannel.
358  // - All of the subchannels in this list are in TRANSIENT_FAILURE.
359  // (This may cause the channel to go from READY to TRANSIENT_FAILURE,
360  // but we're doing what the control plane told us to do.)
361  if (p->latest_pending_subchannel_list_.get() == this &&
362  (p->subchannel_list_->num_ready_ == 0 || num_ready_ > 0 ||
363  num_transient_failure_ == num_subchannels())) {
365  const std::string old_counters_string =
366  p->subchannel_list_ != nullptr ? p->subchannel_list_->CountersString()
367  : "";
368  gpr_log(
369  GPR_INFO,
370  "[RR %p] swapping out subchannel list %p (%s) in favor of %p (%s)", p,
371  p->subchannel_list_.get(), old_counters_string.c_str(), this,
372  CountersString().c_str());
373  }
374  p->subchannel_list_ = std::move(p->latest_pending_subchannel_list_);
375  }
376  // Only set connectivity state if this is the current subchannel list.
377  if (p->subchannel_list_.get() != this) return;
378  // First matching rule wins:
379  // 1) ANY subchannel is READY => policy is READY.
380  // 2) ANY subchannel is CONNECTING => policy is CONNECTING.
381  // 3) ALL subchannels are TRANSIENT_FAILURE => policy is TRANSIENT_FAILURE.
382  if (num_ready_ > 0) {
384  gpr_log(GPR_INFO, "[RR %p] reporting READY with subchannel list %p", p,
385  this);
386  }
387  p->channel_control_helper()->UpdateState(
388  GRPC_CHANNEL_READY, absl::Status(), absl::make_unique<Picker>(p, this));
389  } else if (num_connecting_ > 0) {
391  gpr_log(GPR_INFO, "[RR %p] reporting CONNECTING with subchannel list %p",
392  p, this);
393  }
394  p->channel_control_helper()->UpdateState(
396  absl::make_unique<QueuePicker>(p->Ref(DEBUG_LOCATION, "QueuePicker")));
397  } else if (num_transient_failure_ == num_subchannels()) {
400  "[RR %p] reporting TRANSIENT_FAILURE with subchannel list %p: %s",
401  p, this, status_for_tf.ToString().c_str());
402  }
403  if (!status_for_tf.ok()) {
405  absl::StrCat("connections to all backends failing; last error: ",
406  status_for_tf.ToString()));
407  }
408  p->channel_control_helper()->UpdateState(
410  absl::make_unique<TransientFailurePicker>(last_failure_));
411  }
412 }
413 
414 //
415 // RoundRobinSubchannelData
416 //
417 
418 void RoundRobin::RoundRobinSubchannelData::ProcessConnectivityChangeLocked(
420  grpc_connectivity_state new_state) {
421  RoundRobin* p = static_cast<RoundRobin*>(subchannel_list()->policy());
422  GPR_ASSERT(subchannel() != nullptr);
423  // If this is not the initial state notification and the new state is
424  // TRANSIENT_FAILURE or IDLE, re-resolve.
425  // Note that we don't want to do this on the initial state notification,
426  // because that would result in an endless loop of re-resolution.
427  if (old_state.has_value() && (new_state == GRPC_CHANNEL_TRANSIENT_FAILURE ||
428  new_state == GRPC_CHANNEL_IDLE)) {
431  "[RR %p] Subchannel %p reported %s; requesting re-resolution", p,
432  subchannel(), ConnectivityStateName(new_state));
433  }
434  p->channel_control_helper()->RequestReresolution();
435  }
436  if (new_state == GRPC_CHANNEL_IDLE) {
439  "[RR %p] Subchannel %p reported IDLE; requesting connection", p,
440  subchannel());
441  }
442  subchannel()->RequestConnection();
443  }
444  // Update logical connectivity state.
445  UpdateLogicalConnectivityStateLocked(new_state);
446  // Update the policy state.
447  subchannel_list()->MaybeUpdateRoundRobinConnectivityStateLocked(
448  connectivity_status());
449 }
450 
451 void RoundRobin::RoundRobinSubchannelData::UpdateLogicalConnectivityStateLocked(
452  grpc_connectivity_state connectivity_state) {
453  RoundRobin* p = static_cast<RoundRobin*>(subchannel_list()->policy());
455  gpr_log(
456  GPR_INFO,
457  "[RR %p] connectivity changed for subchannel %p, subchannel_list %p "
458  "(index %" PRIuPTR " of %" PRIuPTR "): prev_state=%s new_state=%s",
459  p, subchannel(), subchannel_list(), Index(),
460  subchannel_list()->num_subchannels(),
463  : "N/A"),
464  ConnectivityStateName(connectivity_state));
465  }
466  // Decide what state to report for aggregation purposes.
467  // If the last logical state was TRANSIENT_FAILURE, then ignore the
468  // state change unless the new state is READY.
471  connectivity_state != GRPC_CHANNEL_READY) {
472  return;
473  }
474  // If the new state is IDLE, treat it as CONNECTING, since it will
475  // immediately transition into CONNECTING anyway.
476  if (connectivity_state == GRPC_CHANNEL_IDLE) {
479  "[RR %p] subchannel %p, subchannel_list %p (index %" PRIuPTR
480  " of %" PRIuPTR "): treating IDLE as CONNECTING",
481  p, subchannel(), subchannel_list(), Index(),
482  subchannel_list()->num_subchannels());
483  }
484  connectivity_state = GRPC_CHANNEL_CONNECTING;
485  }
486  // If no change, return false.
488  *logical_connectivity_state_ == connectivity_state) {
489  return;
490  }
491  // Otherwise, update counters and logical state.
492  subchannel_list()->UpdateStateCountersLocked(logical_connectivity_state_,
493  connectivity_state);
494  logical_connectivity_state_ = connectivity_state;
495 }
496 
497 //
498 // factory
499 //
500 
501 class RoundRobinConfig : public LoadBalancingPolicy::Config {
502  public:
503  const char* name() const override { return kRoundRobin; }
504 };
505 
506 class RoundRobinFactory : public LoadBalancingPolicyFactory {
507  public:
508  OrphanablePtr<LoadBalancingPolicy> CreateLoadBalancingPolicy(
509  LoadBalancingPolicy::Args args) const override {
510  return MakeOrphanable<RoundRobin>(std::move(args));
511  }
512 
513  const char* name() const override { return kRoundRobin; }
514 
515  RefCountedPtr<LoadBalancingPolicy::Config> ParseLoadBalancingConfig(
516  const Json& /*json*/, grpc_error_handle* /*error*/) const override {
517  return MakeRefCounted<RoundRobinConfig>();
518  }
519 };
520 
521 } // namespace
522 
523 } // namespace grpc_core
524 
528  absl::make_unique<grpc_core::RoundRobinFactory>());
529 }
530 
trace.h
last_picked_index_
size_t last_picked_index_
Definition: round_robin.cc:178
GPR_INFO
#define GPR_INFO
Definition: include/grpc/impl/codegen/log.h:56
GRPC_CHANNEL_READY
@ GRPC_CHANNEL_READY
Definition: include/grpc/impl/codegen/connectivity_state.h:36
grpc_core::LoadBalancingPolicyRegistry::Builder::RegisterLoadBalancingPolicyFactory
static void RegisterLoadBalancingPolicyFactory(std::unique_ptr< LoadBalancingPolicyFactory > factory)
Definition: lb_policy_registry.cc:87
num_ready_
size_t num_ready_
Definition: round_robin.cc:161
orphanable.h
log.h
get
absl::string_view get(const Cont &c)
Definition: abseil-cpp/absl/strings/str_replace_test.cc:185
subchannel_list_
OrphanablePtr< RoundRobinSubchannelList > subchannel_list_
Definition: round_robin.cc:185
absl::Status::ToString
std::string ToString(StatusToStringMode mode=StatusToStringMode::kDefault) const
Definition: third_party/abseil-cpp/absl/status/status.h:821
absl::StrCat
std::string StrCat(const AlphaNum &a, const AlphaNum &b)
Definition: abseil-cpp/absl/strings/str_cat.cc:98
connectivity_state.h
grpc_core
Definition: call_metric_recorder.h:31
latest_pending_subchannel_list_
OrphanablePtr< RoundRobinSubchannelList > latest_pending_subchannel_list_
Definition: round_robin.cc:190
num_transient_failure_
size_t num_transient_failure_
Definition: round_robin.cc:163
subchannel
RingHashSubchannelData * subchannel
Definition: ring_hash.cc:285
testing::internal::string
::std::string string
Definition: bloaty/third_party/protobuf/third_party/googletest/googletest/include/gtest/internal/gtest-port.h:881
lb_policy.h
lb_policy_factory.h
GRPC_CHANNEL_TRANSIENT_FAILURE
@ GRPC_CHANNEL_TRANSIENT_FAILURE
Definition: include/grpc/impl/codegen/connectivity_state.h:38
status
absl::Status status
Definition: rls.cc:251
setup.name
name
Definition: setup.py:542
xds_manager.p
p
Definition: xds_manager.py:60
grpc_channel_args
Definition: grpc_types.h:132
GRPC_TRACE_FLAG_ENABLED
#define GRPC_TRACE_FLAG_ENABLED(f)
Definition: debug/trace.h:114
subchannel_interface.h
grpc_lb_policy_round_robin_shutdown
void grpc_lb_policy_round_robin_shutdown()
Definition: round_robin.cc:531
grpc_connectivity_state
grpc_connectivity_state
Definition: include/grpc/impl/codegen/connectivity_state.h:30
grpc_types.h
shutdown_
bool shutdown_
Definition: round_robin.cc:192
DEBUG_LOCATION
#define DEBUG_LOCATION
Definition: debug_location.h:41
asyncio_get_stats.args
args
Definition: asyncio_get_stats.py:40
absl::move
constexpr absl::remove_reference_t< T > && move(T &&t) noexcept
Definition: abseil-cpp/absl/utility/utility.h:221
GPR_ASSERT
#define GPR_ASSERT(x)
Definition: include/grpc/impl/codegen/log.h:94
hpack_encoder_fixtures::Args
Args({0, 16384})
absl::optional::has_value
constexpr bool has_value() const noexcept
Definition: abseil-cpp/absl/types/optional.h:461
gen_stats_data.c_str
def c_str(s, encoding='ascii')
Definition: gen_stats_data.py:38
Json
JSON (JavaScript Object Notation).
Definition: third_party/bloaty/third_party/protobuf/conformance/third_party/jsoncpp/json.h:227
gpr_log
GPRAPI void gpr_log(const char *file, int line, gpr_log_severity severity, const char *format,...) GPR_PRINT_FORMAT_CHECK(4
connectivity_state.h
absl::optional< grpc_connectivity_state >
GRPC_CHANNEL_IDLE
@ GRPC_CHANNEL_IDLE
Definition: include/grpc/impl/codegen/connectivity_state.h:32
server_address.h
error.h
json.h
grpc_core::ServerAddressList
std::vector< ServerAddress > ServerAddressList
Definition: server_address.h:120
GRPC_CHANNEL_CONNECTING
@ GRPC_CHANNEL_CONNECTING
Definition: include/grpc/impl/codegen/connectivity_state.h:34
grpc_core::ConnectivityStateName
const char * ConnectivityStateName(grpc_connectivity_state state)
Definition: connectivity_state.cc:38
debug_location.h
lb_policy_registry.h
last_failure_
absl::Status last_failure_
Definition: round_robin.cc:165
absl::Status
Definition: third_party/abseil-cpp/absl/status/status.h:424
std
Definition: grpcpp/impl/codegen/async_unary_call.h:407
subchannel_list.h
absl::Status::ok
ABSL_MUST_USE_RESULT bool ok() const
Definition: third_party/abseil-cpp/absl/status/status.h:802
absl::UnavailableError
Status UnavailableError(absl::string_view message)
Definition: third_party/abseil-cpp/absl/status/status.cc:375
num_connecting_
size_t num_connecting_
Definition: round_robin.cc:162
grpc_core::grpc_lb_round_robin_trace
TraceFlag grpc_lb_round_robin_trace(false, "round_robin")
ref_counted_ptr.h
check_redundant_namespace_qualifiers.Config
Config
Definition: check_redundant_namespace_qualifiers.py:142
grpc_lb_policy_round_robin_init
void grpc_lb_policy_round_robin_init()
Definition: round_robin.cc:525
GRPC_CHANNEL_SHUTDOWN
@ GRPC_CHANNEL_SHUTDOWN
Definition: include/grpc/impl/codegen/connectivity_state.h:40
parent_
RoundRobin * parent_
Definition: round_robin.cc:176
google::protobuf::python::descriptor::Index
static PyObject * Index(PyContainer *self, PyObject *item)
Definition: bloaty/third_party/protobuf/python/google/protobuf/pyext/descriptor_containers.cc:672
grpc_error
Definition: error_internal.h:42
testing::Ref
internal::RefMatcher< T & > Ref(T &x)
Definition: cares/cares/test/gmock-1.8.0/gmock/gmock.h:8628
logical_connectivity_state_
absl::optional< grpc_connectivity_state > logical_connectivity_state_
Definition: round_robin.cc:114
i
uint64_t i
Definition: abseil-cpp/absl/container/btree_benchmark.cc:230
subchannels_
std::vector< RefCountedPtr< SubchannelInterface > > subchannels_
Definition: round_robin.cc:179
port_platform.h


grpc
Author(s):
autogenerated on Fri May 16 2025 03:00:06