Clean up EdsUpdate data structure.

pull/24135/head
Mark D. Roth 5 years ago
parent 3daba88553
commit 021651631e
  1. 64
      src/core/ext/filters/client_channel/lb_policy/xds/eds.cc
  2. 105
      src/core/ext/xds/xds_api.cc
  3. 143
      src/core/ext/xds/xds_api.h
  4. 66
      src/core/ext/xds/xds_client.cc
  5. 1
      src/core/lib/channel/channel_args.h

@ -120,7 +120,7 @@ class EdsLb : public LoadBalancingPolicy {
PickResult Pick(PickArgs args) override;
private:
RefCountedPtr<XdsApi::DropConfig> drop_config_;
RefCountedPtr<XdsApi::EdsUpdate::DropConfig> drop_config_;
RefCountedPtr<XdsClusterDropStats> drop_stats_;
RefCountedPtr<ChildPickerWrapper> child_picker_;
};
@ -152,7 +152,7 @@ class EdsLb : public LoadBalancingPolicy {
void MaybeDestroyChildPolicyLocked();
void UpdatePriorityList(XdsApi::PriorityListUpdate priority_list_update);
void UpdatePriorityList(XdsApi::EdsUpdate::PriorityList priority_list);
void UpdateChildPolicyLocked();
OrphanablePtr<LoadBalancingPolicy> CreateChildPolicyLocked(
const grpc_channel_args* args);
@ -204,11 +204,11 @@ class EdsLb : public LoadBalancingPolicy {
// Note that this is not owned, so this pointer must never be derefernced.
EndpointWatcher* endpoint_watcher_ = nullptr;
// The latest data from the endpoint watcher.
XdsApi::PriorityListUpdate priority_list_update_;
XdsApi::EdsUpdate::PriorityList priority_list_;
// State used to retain child policy names for priority policy.
std::vector<size_t /*child_number*/> priority_child_numbers_;
RefCountedPtr<XdsApi::DropConfig> drop_config_;
RefCountedPtr<XdsApi::EdsUpdate::DropConfig> drop_config_;
RefCountedPtr<XdsClusterDropStats> drop_stats_;
OrphanablePtr<LoadBalancingPolicy> child_policy_;
@ -326,12 +326,12 @@ class EdsLb::EndpointWatcher : public XdsClient::EndpointWatcherInterface {
}
// Update priority and locality info.
if (eds_policy_->child_policy_ == nullptr ||
eds_policy_->priority_list_update_ != update.priority_list_update) {
eds_policy_->priority_list_ != update.priorities) {
if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_eds_trace)) {
gpr_log(GPR_INFO, "[edslb %p] Updating priority list",
eds_policy_.get());
}
eds_policy_->UpdatePriorityList(std::move(update.priority_list_update));
eds_policy_->UpdatePriorityList(std::move(update.priorities));
} else if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_eds_trace)) {
gpr_log(GPR_INFO, "[edslb %p] Priority list unchanged, ignoring",
eds_policy_.get());
@ -510,35 +510,30 @@ void EdsLb::ResetBackoffLocked() {
// child policy-related methods
//
void EdsLb::UpdatePriorityList(
XdsApi::PriorityListUpdate priority_list_update) {
void EdsLb::UpdatePriorityList(XdsApi::EdsUpdate::PriorityList priority_list) {
// Build some maps from locality to child number and the reverse from
// the old data in priority_list_update_ and priority_child_numbers_.
// the old data in priority_list_ and priority_child_numbers_.
std::map<XdsLocalityName*, size_t /*child_number*/, XdsLocalityName::Less>
locality_child_map;
std::map<size_t, std::set<XdsLocalityName*>> child_locality_map;
for (uint32_t priority = 0; priority < priority_list_update_.size();
++priority) {
auto* locality_map = priority_list_update_.Find(priority);
GPR_ASSERT(locality_map != nullptr);
for (size_t priority = 0; priority < priority_list_.size(); ++priority) {
size_t child_number = priority_child_numbers_[priority];
for (const auto& p : locality_map->localities) {
XdsLocalityName* locality_name = p.first.get();
const auto& localities = priority_list_[priority].localities;
for (const auto& p : localities) {
XdsLocalityName* locality_name = p.first;
locality_child_map[locality_name] = child_number;
child_locality_map[child_number].insert(locality_name);
}
}
// Construct new list of children.
std::vector<size_t> priority_child_numbers;
for (uint32_t priority = 0; priority < priority_list_update.size();
++priority) {
auto* locality_map = priority_list_update.Find(priority);
GPR_ASSERT(locality_map != nullptr);
for (size_t priority = 0; priority < priority_list.size(); ++priority) {
const auto& localities = priority_list[priority].localities;
absl::optional<size_t> child_number;
// If one of the localities in this priority already existed, reuse its
// child number.
for (const auto& p : locality_map->localities) {
XdsLocalityName* locality_name = p.first.get();
for (const auto& p : localities) {
XdsLocalityName* locality_name = p.first;
if (!child_number.has_value()) {
auto it = locality_child_map.find(locality_name);
if (it != locality_child_map.end()) {
@ -572,7 +567,7 @@ void EdsLb::UpdatePriorityList(
priority_child_numbers.push_back(*child_number);
}
// Save update.
priority_list_update_ = std::move(priority_list_update);
priority_list_ = std::move(priority_list);
priority_child_numbers_ = std::move(priority_child_numbers);
// Update child policy.
UpdateChildPolicyLocked();
@ -580,23 +575,20 @@ void EdsLb::UpdatePriorityList(
ServerAddressList EdsLb::CreateChildPolicyAddressesLocked() {
ServerAddressList addresses;
for (uint32_t priority = 0; priority < priority_list_update_.size();
++priority) {
for (size_t priority = 0; priority < priority_list_.size(); ++priority) {
const auto& localities = priority_list_[priority].localities;
std::string priority_child_name =
absl::StrCat("child", priority_child_numbers_[priority]);
const auto* locality_map = priority_list_update_.Find(priority);
GPR_ASSERT(locality_map != nullptr);
for (const auto& p : locality_map->localities) {
for (const auto& p : localities) {
const auto& locality_name = p.first;
const auto& locality = p.second;
std::vector<std::string> hierarchical_path = {
priority_child_name, locality_name->AsHumanReadableString()};
for (size_t i = 0; i < locality.serverlist.size(); ++i) {
const ServerAddress& address = locality.serverlist[i];
for (const auto& endpoint : locality.endpoints) {
grpc_arg new_arg = MakeHierarchicalPathArg(hierarchical_path);
grpc_channel_args* args =
grpc_channel_args_copy_and_add(address.args(), &new_arg, 1);
addresses.emplace_back(address.address(), args);
grpc_channel_args_copy_and_add(endpoint.args(), &new_arg, 1);
addresses.emplace_back(endpoint.address(), args);
}
}
}
@ -607,13 +599,11 @@ RefCountedPtr<LoadBalancingPolicy::Config>
EdsLb::CreateChildPolicyConfigLocked() {
Json::Object priority_children;
Json::Array priority_priorities;
for (uint32_t priority = 0; priority < priority_list_update_.size();
++priority) {
const auto* locality_map = priority_list_update_.Find(priority);
GPR_ASSERT(locality_map != nullptr);
for (size_t priority = 0; priority < priority_list_.size(); ++priority) {
const auto& localities = priority_list_[priority].localities;
Json::Object weighted_targets;
for (const auto& p : locality_map->localities) {
XdsLocalityName* locality_name = p.first.get();
for (const auto& p : localities) {
XdsLocalityName* locality_name = p.first;
const auto& locality = p.second;
// Construct JSON object containing locality name.
Json::Object locality_name_json;

@ -375,49 +375,31 @@ XdsApi::RdsUpdate::FindVirtualHostForDomain(const std::string& domain) const {
}
//
// XdsApi::PriorityListUpdate
// XdsApi::EdsUpdate
//
bool XdsApi::PriorityListUpdate::operator==(
const XdsApi::PriorityListUpdate& other) const {
if (priorities_.size() != other.priorities_.size()) return false;
for (size_t i = 0; i < priorities_.size(); ++i) {
if (priorities_[i].localities != other.priorities_[i].localities) {
return false;
}
std::string XdsApi::EdsUpdate::Priority::Locality::ToString() const {
std::vector<std::string> endpoint_strings;
for (const ServerAddress& endpoint : endpoints) {
endpoint_strings.emplace_back(
absl::StrCat(grpc_sockaddr_to_string(&endpoint.address(), false), "{",
grpc_channel_args_string(endpoint.args()), "}"));
}
return true;
return absl::StrCat("{name=", name->AsHumanReadableString(),
", lb_weight=", lb_weight, ", endpoints=[",
absl::StrJoin(endpoint_strings, ", "), "]}");
}
void XdsApi::PriorityListUpdate::Add(
XdsApi::PriorityListUpdate::LocalityMap::Locality locality) {
// Pad the missing priorities in case the localities are not ordered by
// priority.
if (!Contains(locality.priority)) priorities_.resize(locality.priority + 1);
LocalityMap& locality_map = priorities_[locality.priority];
locality_map.localities.emplace(locality.name, std::move(locality));
}
const XdsApi::PriorityListUpdate::LocalityMap* XdsApi::PriorityListUpdate::Find(
uint32_t priority) const {
if (!Contains(priority)) return nullptr;
return &priorities_[priority];
}
bool XdsApi::PriorityListUpdate::Contains(
const RefCountedPtr<XdsLocalityName>& name) {
for (size_t i = 0; i < priorities_.size(); ++i) {
const LocalityMap& locality_map = priorities_[i];
if (locality_map.Contains(name)) return true;
std::string XdsApi::EdsUpdate::Priority::ToString() const {
std::vector<std::string> locality_strings;
for (const auto& p : localities) {
locality_strings.emplace_back(p.second.ToString());
}
return false;
return absl::StrCat("[", absl::StrJoin(locality_strings, ", "), "]");
}
//
// XdsApi::DropConfig
//
bool XdsApi::DropConfig::ShouldDrop(const std::string** category_name) const {
bool XdsApi::EdsUpdate::DropConfig::ShouldDrop(
const std::string** category_name) const {
for (size_t i = 0; i < drop_category_list_.size(); ++i) {
const auto& drop_category = drop_category_list_[i];
// Generate a random number in [0, 1000000).
@ -430,6 +412,27 @@ bool XdsApi::DropConfig::ShouldDrop(const std::string** category_name) const {
return false;
}
std::string XdsApi::EdsUpdate::DropConfig::ToString() const {
std::vector<std::string> category_strings;
for (const DropCategory& category : drop_category_list_) {
category_strings.emplace_back(
absl::StrCat(category.name, "=", category.parts_per_million));
}
return absl::StrCat("{[", absl::StrJoin(category_strings, ", "),
"], drop_all=", drop_all_, "}");
}
std::string XdsApi::EdsUpdate::ToString() const {
std::vector<std::string> priority_strings;
for (size_t i = 0; i < priorities.size(); ++i) {
const Priority& priority = priorities[i];
priority_strings.emplace_back(
absl::StrCat("priority ", i, ": ", priority.ToString()));
}
return absl::StrCat("priorities=[", absl::StrJoin(priority_strings, ", "),
"], drop_config=", drop_config->ToString());
}
//
// XdsApi
//
@ -1858,7 +1861,7 @@ grpc_error* ServerAddressParseAndAppend(
grpc_error* LocalityParse(
const envoy_config_endpoint_v3_LocalityLbEndpoints* locality_lb_endpoints,
XdsApi::PriorityListUpdate::LocalityMap::Locality* output_locality) {
XdsApi::EdsUpdate::Priority::Locality* output_locality, size_t* priority) {
// Parse LB weight.
const google_protobuf_UInt32Value* lb_weight =
envoy_config_endpoint_v3_LocalityLbEndpoints_load_balancing_weight(
@ -1888,20 +1891,19 @@ grpc_error* LocalityParse(
locality_lb_endpoints, &size);
for (size_t i = 0; i < size; ++i) {
grpc_error* error = ServerAddressParseAndAppend(
lb_endpoints[i], &output_locality->serverlist);
lb_endpoints[i], &output_locality->endpoints);
if (error != GRPC_ERROR_NONE) return error;
}
// Parse the priority.
output_locality->priority =
envoy_config_endpoint_v3_LocalityLbEndpoints_priority(
locality_lb_endpoints);
*priority = envoy_config_endpoint_v3_LocalityLbEndpoints_priority(
locality_lb_endpoints);
return GRPC_ERROR_NONE;
}
grpc_error* DropParseAndAppend(
const envoy_config_endpoint_v3_ClusterLoadAssignment_Policy_DropOverload*
drop_overload,
XdsApi::DropConfig* drop_config) {
XdsApi::EdsUpdate::DropConfig* drop_config) {
// Get the category.
std::string category = UpbStringToStdString(
envoy_config_endpoint_v3_ClusterLoadAssignment_Policy_DropOverload_category(
@ -1986,23 +1988,28 @@ grpc_error* EdsResponseParse(
envoy_config_endpoint_v3_ClusterLoadAssignment_endpoints(
cluster_load_assignment, &locality_size);
for (size_t j = 0; j < locality_size; ++j) {
XdsApi::PriorityListUpdate::LocalityMap::Locality locality;
grpc_error* error = LocalityParse(endpoints[j], &locality);
size_t priority;
XdsApi::EdsUpdate::Priority::Locality locality;
grpc_error* error = LocalityParse(endpoints[j], &locality, &priority);
if (error != GRPC_ERROR_NONE) return error;
// Filter out locality with weight 0.
if (locality.lb_weight == 0) continue;
eds_update.priority_list_update.Add(locality);
// Make sure prorities is big enough. Note that they might not
// arrive in priority order.
while (eds_update.priorities.size() < priority + 1) {
eds_update.priorities.emplace_back();
}
eds_update.priorities[priority].localities.emplace(locality.name.get(),
std::move(locality));
}
for (uint32_t priority = 0;
priority < eds_update.priority_list_update.size(); ++priority) {
auto* locality_map = eds_update.priority_list_update.Find(priority);
if (locality_map == nullptr || locality_map->size() == 0) {
for (const auto& priority : eds_update.priorities) {
if (priority.localities.empty()) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"EDS update includes sparse priority list");
}
}
// Get the drop config.
eds_update.drop_config = MakeRefCounted<XdsApi::DropConfig>();
eds_update.drop_config = MakeRefCounted<XdsApi::EdsUpdate::DropConfig>();
const envoy_config_endpoint_v3_ClusterLoadAssignment_Policy* policy =
envoy_config_endpoint_v3_ClusterLoadAssignment_policy(
cluster_load_assignment);

@ -183,110 +183,85 @@ class XdsApi {
using CdsUpdateMap = std::map<std::string /*cluster_name*/, CdsUpdate>;
class PriorityListUpdate {
public:
struct LocalityMap {
struct EdsUpdate {
struct Priority {
struct Locality {
bool operator==(const Locality& other) const {
return *name == *other.name && serverlist == other.serverlist &&
lb_weight == other.lb_weight && priority == other.priority;
}
// This comparator only compares the locality names.
struct Less {
bool operator()(const Locality& lhs, const Locality& rhs) const {
return XdsLocalityName::Less()(lhs.name, rhs.name);
}
};
RefCountedPtr<XdsLocalityName> name;
ServerAddressList serverlist;
uint32_t lb_weight;
uint32_t priority;
};
ServerAddressList endpoints;
bool Contains(const RefCountedPtr<XdsLocalityName>& name) const {
return localities.find(name) != localities.end();
}
bool operator==(const Locality& other) const {
return *name == *other.name && lb_weight == other.lb_weight &&
endpoints == other.endpoints;
}
std::string ToString() const;
};
size_t size() const { return localities.size(); }
std::map<XdsLocalityName*, Locality, XdsLocalityName::Less> localities;
std::map<RefCountedPtr<XdsLocalityName>, Locality, XdsLocalityName::Less>
localities;
bool operator==(const Priority& other) const {
return localities == other.localities;
}
std::string ToString() const;
};
using PriorityList = absl::InlinedVector<Priority, 2>;
// There are two phases of accessing this class's content:
// 1. to initialize in the control plane combiner;
// 2. to use in the data plane combiner.
// So no additional synchronization is needed.
class DropConfig : public RefCounted<DropConfig> {
public:
struct DropCategory {
bool operator==(const DropCategory& other) const {
return name == other.name &&
parts_per_million == other.parts_per_million;
}
bool operator==(const PriorityListUpdate& other) const;
bool operator!=(const PriorityListUpdate& other) const {
return !(*this == other);
}
void Add(LocalityMap::Locality locality);
const LocalityMap* Find(uint32_t priority) const;
bool Contains(uint32_t priority) const {
return priority < priorities_.size();
}
bool Contains(const RefCountedPtr<XdsLocalityName>& name);
std::string name;
const uint32_t parts_per_million;
};
bool empty() const { return priorities_.empty(); }
size_t size() const { return priorities_.size(); }
using DropCategoryList = absl::InlinedVector<DropCategory, 2>;
// Callers should make sure the priority list is non-empty.
uint32_t LowestPriority() const {
return static_cast<uint32_t>(priorities_.size()) - 1;
}
void AddCategory(std::string name, uint32_t parts_per_million) {
drop_category_list_.emplace_back(
DropCategory{std::move(name), parts_per_million});
if (parts_per_million == 1000000) drop_all_ = true;
}
private:
absl::InlinedVector<LocalityMap, 2> priorities_;
};
// The only method invoked from outside the WorkSerializer (used in
// the data plane).
bool ShouldDrop(const std::string** category_name) const;
// There are two phases of accessing this class's content:
// 1. to initialize in the control plane combiner;
// 2. to use in the data plane combiner.
// So no additional synchronization is needed.
class DropConfig : public RefCounted<DropConfig> {
public:
struct DropCategory {
bool operator==(const DropCategory& other) const {
return name == other.name &&
parts_per_million == other.parts_per_million;
const DropCategoryList& drop_category_list() const {
return drop_category_list_;
}
std::string name;
const uint32_t parts_per_million;
};
using DropCategoryList = absl::InlinedVector<DropCategory, 2>;
bool drop_all() const { return drop_all_; }
void AddCategory(std::string name, uint32_t parts_per_million) {
drop_category_list_.emplace_back(
DropCategory{std::move(name), parts_per_million});
if (parts_per_million == 1000000) drop_all_ = true;
}
bool operator==(const DropConfig& other) const {
return drop_category_list_ == other.drop_category_list_;
}
bool operator!=(const DropConfig& other) const {
return !(*this == other);
}
// The only method invoked from the data plane combiner.
bool ShouldDrop(const std::string** category_name) const;
std::string ToString() const;
const DropCategoryList& drop_category_list() const {
return drop_category_list_;
}
private:
DropCategoryList drop_category_list_;
bool drop_all_ = false;
};
bool drop_all() const { return drop_all_; }
PriorityList priorities;
RefCountedPtr<DropConfig> drop_config;
bool operator==(const DropConfig& other) const {
return drop_category_list_ == other.drop_category_list_;
bool operator==(const EdsUpdate& other) const {
return priorities == other.priorities &&
*drop_config == *other.drop_config;
}
bool operator!=(const DropConfig& other) const { return !(*this == other); }
private:
DropCategoryList drop_category_list_;
bool drop_all_ = false;
};
struct EdsUpdate {
PriorityListUpdate priority_list_update;
RefCountedPtr<DropConfig> drop_config;
std::string ToString() const;
};
using EdsUpdateMap = std::map<std::string /*eds_service_name*/, EdsUpdate>;

@ -1006,6 +1006,12 @@ void XdsClient::ChannelState::AdsCallState::AcceptRdsUpdate(
void XdsClient::ChannelState::AdsCallState::AcceptCdsUpdate(
XdsApi::CdsUpdateMap cds_update_map) {
if (GRPC_TRACE_FLAG_ENABLED(grpc_xds_client_trace)) {
gpr_log(GPR_INFO,
"[xds_client %p] CDS update received containing %" PRIuPTR
" resources",
xds_client(), cds_update_map.size());
}
auto& cds_state = state_map_[XdsApi::kCdsTypeUrl];
std::set<std::string> eds_resource_names_seen;
for (auto& p : cds_update_map) {
@ -1015,8 +1021,8 @@ void XdsClient::ChannelState::AdsCallState::AcceptCdsUpdate(
if (state != nullptr) state->Finish();
if (GRPC_TRACE_FLAG_ENABLED(grpc_xds_client_trace)) {
gpr_log(GPR_INFO,
"[xds_client %p] CDS update (cluster=%s) received: "
"eds_service_name=%s, lrs_load_reporting_server_name=%s",
"[xds_client %p] cluster=%s: eds_service_name=%s, "
"lrs_load_reporting_server_name=%s",
xds_client(), cluster_name, cds_update.eds_service_name.c_str(),
cds_update.lrs_load_reporting_server_name.has_value()
? cds_update.lrs_load_reporting_server_name.value().c_str()
@ -1085,6 +1091,12 @@ void XdsClient::ChannelState::AdsCallState::AcceptCdsUpdate(
void XdsClient::ChannelState::AdsCallState::AcceptEdsUpdate(
XdsApi::EdsUpdateMap eds_update_map) {
if (GRPC_TRACE_FLAG_ENABLED(grpc_xds_client_trace)) {
gpr_log(GPR_INFO,
"[xds_client %p] EDS update received containing %" PRIuPTR
" resources",
xds_client(), eds_update_map.size());
}
auto& eds_state = state_map_[XdsApi::kEdsTypeUrl];
for (auto& p : eds_update_map) {
const char* eds_service_name = p.first.c_str();
@ -1092,52 +1104,8 @@ void XdsClient::ChannelState::AdsCallState::AcceptEdsUpdate(
auto& state = eds_state.subscribed_resources[eds_service_name];
if (state != nullptr) state->Finish();
if (GRPC_TRACE_FLAG_ENABLED(grpc_xds_client_trace)) {
gpr_log(GPR_INFO,
"[xds_client %p] EDS response with %" PRIuPTR
" priorities and %" PRIuPTR
" drop categories received (drop_all=%d)",
xds_client(), eds_update.priority_list_update.size(),
eds_update.drop_config->drop_category_list().size(),
eds_update.drop_config->drop_all());
for (size_t priority = 0;
priority < eds_update.priority_list_update.size(); ++priority) {
const auto* locality_map_update = eds_update.priority_list_update.Find(
static_cast<uint32_t>(priority));
gpr_log(GPR_INFO,
"[xds_client %p] Priority %" PRIuPTR " contains %" PRIuPTR
" localities",
xds_client(), priority, locality_map_update->size());
size_t locality_count = 0;
for (const auto& p : locality_map_update->localities) {
const auto& locality = p.second;
gpr_log(GPR_INFO,
"[xds_client %p] Priority %" PRIuPTR ", locality %" PRIuPTR
" %s has weight %d, contains %" PRIuPTR " server addresses",
xds_client(), priority, locality_count,
locality.name->AsHumanReadableString().c_str(),
locality.lb_weight, locality.serverlist.size());
for (size_t i = 0; i < locality.serverlist.size(); ++i) {
std::string ipport = grpc_sockaddr_to_string(
&locality.serverlist[i].address(), false);
gpr_log(GPR_INFO,
"[xds_client %p] Priority %" PRIuPTR ", locality %" PRIuPTR
" %s, server address %" PRIuPTR ": %s",
xds_client(), priority, locality_count,
locality.name->AsHumanReadableString().c_str(), i,
ipport.c_str());
}
++locality_count;
}
}
for (size_t i = 0;
i < eds_update.drop_config->drop_category_list().size(); ++i) {
const XdsApi::DropConfig::DropCategory& drop_category =
eds_update.drop_config->drop_category_list()[i];
gpr_log(GPR_INFO,
"[xds_client %p] Drop category %s has drop rate %d per million",
xds_client(), drop_category.name.c_str(),
drop_category.parts_per_million);
}
gpr_log(GPR_INFO, "[xds_client %p] EDS resource %s: %s", xds_client(),
eds_service_name, eds_update.ToString().c_str());
}
EndpointState& endpoint_state =
xds_client()->endpoint_map_[eds_service_name];
@ -1145,7 +1113,7 @@ void XdsClient::ChannelState::AdsCallState::AcceptEdsUpdate(
if (endpoint_state.update.has_value()) {
const XdsApi::EdsUpdate& prev_update = endpoint_state.update.value();
const bool priority_list_changed =
prev_update.priority_list_update != eds_update.priority_list_update;
prev_update.priorities != eds_update.priorities;
const bool drop_config_changed =
prev_update.drop_config == nullptr ||
*prev_update.drop_config != *eds_update.drop_config;

@ -117,7 +117,6 @@ grpc_arg grpc_channel_arg_pointer_create(char* name, void* value,
const grpc_arg_pointer_vtable* vtable);
// Returns a string representing channel args in human-readable form.
// Callers takes ownership of result.
std::string grpc_channel_args_string(const grpc_channel_args* args);
// Takes ownership of the old_args

Loading…
Cancel
Save